Last active
December 21, 2019 09:45
-
-
Save karlrwjohnson/d4aaf475ce8d179cdc100ab481d750e5 to your computer and use it in GitHub Desktop.
Python + QT + OpenGL 3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Minimal demo of OpenGL >= 3.0, Qt >= 5.0 in Python 3.6 | |
Uses a programmable shader pipeline to draw a pair of triangles. | |
Eschews PyQt's wrapper functions in favor of PyOpenGL's because I couldn't get it to draw | |
anything other than a black screen (which means any one of a hundred details were incorrect). | |
Besides, you'd have to fall back on PyOpenGL to get glDrawArrays anyway, so we may as well | |
use it for everything. | |
(pip install PyOpenGL==3.1.0 PyQt5==5.9) | |
Reference Documentation: | |
Qt: https://doc.qt.io/qt-5/qtgui-module.html | |
PyOpenGL: http://pyopengl.sourceforge.net/documentation/manual-3.0/index.html | |
""" | |
import sys | |
from ctypes import * | |
from logging import getLogger | |
from textwrap import dedent | |
from typing import * | |
from OpenGL.GL import * | |
from PyQt5.QtCore import * | |
from PyQt5.QtGui import * | |
from PyQt5.QtWidgets import * | |
logger = getLogger(__name__) | |
def ctype_array(type: Type, elements): | |
array_type: Type = type * len(elements) | |
return array_type(*elements) | |
position = ctype_array(c_float, [ | |
0.0,0.0, 1.0,0.0, 0.5,1.0, | |
0.0,0.0, -1.0,-0.0, -0.5,-1.0, | |
]) | |
color = ctype_array(c_float, [ | |
1.0,0.0,0.0, 0.0,1.0,0.0, 0.0,0.0,1.0, | |
0.5,0.5,0.0, 0.0,0.5,0.5, 0.5,0.0,0.5, | |
]) | |
vertex = dedent('''\ | |
#version 130 | |
in vec2 vPosition; | |
in vec3 vColor; | |
out vec3 ffColor; | |
void main() { | |
gl_Position = vec4(vPosition, 0, 1); | |
ffColor = vColor; | |
} | |
''') | |
fragment = dedent('''\ | |
#version 130 | |
in vec3 ffColor; | |
out vec4 fColor; | |
void main() { | |
fColor = vec4(ffColor, 1); | |
} | |
''') | |
bindings = ( | |
('vPosition', 2, position), | |
('vColor', 3, color), | |
) | |
class MyGLWidget(QOpenGLWindow): | |
def __init__(self, *args): | |
super().__init__(*args) | |
# Indicate that the window is to be used for OpenGL rendering and not for rendering raster content with QPainter | |
self.setSurfaceType(QWindow.OpenGLSurface) | |
self.ctx: QOpenGLContext = None | |
def initializeGL(self): | |
print('initializeGL()') | |
self.makeCurrent() | |
self.ctx: QOpenGLContext = self.context() | |
program_id = glCreateProgram() | |
print(f'Created program id={program_id}') | |
for name, type_, source in ( | |
('Vertex', GL_VERTEX_SHADER, vertex), | |
('Fragment', GL_FRAGMENT_SHADER, fragment), | |
): | |
shader_id = glCreateShader(type_) | |
print(f'Created {name} shader id={shader_id}') | |
glShaderSource(shader_id, source) | |
glCompileShader(shader_id) | |
assert glGetShaderiv(shader_id, GL_COMPILE_STATUS), \ | |
f'{name} Shader compilation failed:\n{glGetShaderInfoLog(shader_id).decode()}' | |
glAttachShader(program_id, shader_id) | |
glLinkProgram(program_id) | |
assert glGetProgramiv(program_id, GL_LINK_STATUS), \ | |
f'Shader link failed:\n{glGetProgramInfoLog(program_id).decode()}' | |
glUseProgram(program_id) | |
vao = glGenVertexArrays(1) | |
print(f'Created vertex attribute object id={vao}') | |
glBindVertexArray(vao) | |
for name, elements_per_vertex, data in bindings: | |
vbo = glGenBuffers(1) | |
print(f'Created vertex buffer for {name!r}; object id={vbo}') | |
glBindBuffer(GL_ARRAY_BUFFER, vbo) | |
glBufferData(GL_ARRAY_BUFFER, data, GL_STATIC_DRAW) | |
attribute_id = glGetAttribLocation(program_id, name) | |
print(f'Location of {name!r} = {attribute_id}') | |
assert attribute_id >= 0, 'Attribute `{name}` is not defined??!?' | |
glVertexAttribPointer(attribute_id, elements_per_vertex, GL_FLOAT, GL_FALSE, 0, None) | |
glEnableVertexAttribArray(attribute_id) | |
# ^ Notice that the last parameter of `glVertexAttribPointer` is None instead of 0. | |
# Under ctypes, pointer arguments handle None and zero are handled differently. | |
# None becomes a void pointer; zero likely becomes a pointer to the value 0. | |
# (https://stackoverflow.com/questions/43539610#43541948) | |
def paintGL(self): | |
print('paintGL()') | |
self.makeCurrent() | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) | |
glDrawArrays(GL_TRIANGLES, 0, 6) | |
glFlush() | |
if self.isExposed(): | |
print('swapping') | |
self.ctx.swapBuffers(self) | |
else: | |
print('not swapping: not yet exposed') | |
def resizeGL(self, w: int, h: int): | |
pass | |
if __name__ == '__main__': | |
app = QApplication(sys.argv) | |
window = MyGLWidget() | |
window.resize(800, 600) | |
window.setTitle('asdf') | |
window.show() | |
logger.info("entering main loop") | |
sys.exit(app.exec_()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Got a black screen..