class MeshVertexBuffer(object): def __init__(self, vertices=0): self.update(vertices) self.uniforms = dict() self.buffer = None def update(self, vertices): self.vertices = vertices self.attributes = dict() self.offsets = dict() self.size = 0 self.changed = True def add(self, name, data, count=3, datatype=GL.GL_FLOAT): self.attributes[name] = MeshVertexAttribute(name, data, count, datatype) self.changed = True def remove(self, name): if name in self.attributes: del self.attributes[name] self.changed = True def create(self, gl): if not self.buffer: self.buffer = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer) if not self.buffer.isCreated() and not self.buffer.create(): raise Exception("buffer creation failed!") self.buffer.bind() self.offsets = dict() self.size = 0 for item in self.attributes.values(): self.offsets[item.name] = self.size self.size += self.vertices * item.count * item.datasize self.buffer.allocate(self.size) for item in self.attributes.values(): self.buffer.write(self.offsets[item.name], item.data.data, self.vertices * item.count * item.datasize) self.buffer.release() self.changed = False def destroy(self, gl): if not self.buffer: return self.buffer.destroy() self.buffer = None self.changed = True def enable(self, gl, shader): if self.changed: self.create(gl) shader.setUniforms(self.uniforms) self.buffer.bind() for item in self.attributes.values(): if item.enabled: shader.setAttributeArray(item.name, item.datatype, self.offsets[item.name], item.count) self.buffer.release() def disable(self, gl, shader): for item in self.attributes.values(): if item.enabled: shader.unsetAttributeArray(item.name)
class MeshIndexBuffer(object): def __init__(self, elements=0, data=np.zeros((0), dtype=np.uint32), primitives=GL.GL_POINTS, datatype=GL.GL_UNSIGNED_INT): self.update(elements, data, primitives, datatype) self.uniforms = dict() self.buffer = None def update(self, elements, data, primitives=GL.GL_POINTS, datatype=GL.GL_UNSIGNED_INT): self.elements = elements self.data = data self.primitives = primitives self.datatype = datatype if datatype == GL.GL_UNSIGNED_BYTE: self.datasize = 1 elif datatype == GL.GL_UNSIGNED_SHORT: self.datasize = 2 elif datatype == GL.GL_UNSIGNED_INT: self.datasize = 4 else: raise Exception("unsupported index type") self.size = 0 self.changed = True def create(self, gl): if not self.buffer: self.buffer = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer) if not self.buffer.isCreated() and not self.buffer.create(): raise Exception("buffer creation failed!") self.buffer.bind() self.size = self.elements * self.datasize self.buffer.allocate(self.size) self.buffer.write(0, self.data.data, self.elements * self.datasize) self.buffer.release() self.changed = False def destroy(self, gl): if not self.buffer: return self.buffer.destroy() self.buffer = None self.changed = True def enable(self, gl, shader): if self.changed: self.create(gl) shader.setUniforms(self.uniforms) self.buffer.bind() def disable(self, gl, shader): self.buffer.release()
class RenderWindow(QWindow): def __init__(self, format): super(RenderWindow, self).__init__() self.setSurfaceType(QWindow.OpenGLSurface) self.setFormat(format) self.context = QOpenGLContext(self) self.context.setFormat(self.requestedFormat()) if not self.context.create(): raise Exception("Unable to create GL context") self.program = None self.timer = None self.angle = 0 def initGl(self): self.program = QOpenGLShaderProgram(self) self.vao = QOpenGLVertexArrayObject() self.vbo = QOpenGLBuffer() format = self.context.format() useNewStyleShader = format.profile() == QSurfaceFormat.CoreProfile # Try to handle 3.0 & 3.1 that do not have the core/compatibility profile # concept 3.2+ has. This may still fail since version 150 (3.2) is # specified in the sources but it's worth a try. if (format.renderableType() == QSurfaceFormat.OpenGL and format.majorVersion() == 3 and format.minorVersion() <= 1): useNewStyleShader = not format.testOption(QSurfaceFormat.DeprecatedFunctions) vertexShader = vertexShaderSource if useNewStyleShader else vertexShaderSource110 fragmentShader = fragmentShaderSource if useNewStyleShader else fragmentShaderSource110 if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertexShader): raise Exception("Vertex shader could not be added: {} ({})".format(self.program.log(), vertexShader)) if not self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragmentShader): raise Exception("Fragment shader could not be added: {} ({})".format(self.program.log(), fragmentShader)) if not self.program.link(): raise Exception("Could not link shaders: {}".format(self.program.log())) self.posAttr = self.program.attributeLocation("posAttr") self.colAttr = self.program.attributeLocation("colAttr") self.matrixUniform = self.program.uniformLocation("matrix") self.vbo.create() self.vbo.bind() self.verticesData = vertices.tobytes() self.colorsData = colors.tobytes() verticesSize = 4 * vertices.size colorsSize = 4 * colors.size self.vbo.allocate(VoidPtr(self.verticesData), verticesSize + colorsSize) self.vbo.write(verticesSize, VoidPtr(self.colorsData), colorsSize) self.vbo.release() vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) if self.vao.isCreated(): # have VAO support, use it self.setupVertexAttribs() def setupVertexAttribs(self): self.vbo.bind() self.program.setAttributeBuffer(self.posAttr, GL.GL_FLOAT, 0, 2) self.program.setAttributeBuffer(self.colAttr, GL.GL_FLOAT, 4 * vertices.size, 3) self.program.enableAttributeArray(self.posAttr) self.program.enableAttributeArray(self.colAttr) self.vbo.release() def exposeEvent(self, event): if self.isExposed(): self.render() if self.timer is None: self.timer = QTimer(self) self.timer.timeout.connect(self.slotTimer) if not self.timer.isActive(): self.timer.start(10) else: if self.timer and self.timer.isActive(): self.timer.stop() def render(self): if not self.context.makeCurrent(self): raise Exception("makeCurrent() failed") functions = self.context.functions() if self.program is None: functions.glEnable(GL.GL_DEPTH_TEST) functions.glClearColor(0, 0, 0, 1) self.initGl() retinaScale = self.devicePixelRatio() functions.glViewport(0, 0, self.width() * retinaScale, self.height() * retinaScale) functions.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) self.program.bind() matrix = QMatrix4x4() matrix.perspective(60, 4 / 3, 0.1, 100) matrix.translate(0, 0, -2) matrix.rotate(self.angle, 0, 1, 0) self.program.setUniformValue(self.matrixUniform, matrix) if self.vao.isCreated(): self.vao.bind() else: # no VAO support, set the vertex attribute arrays now self.setupVertexAttribs() functions.glDrawArrays(GL.GL_TRIANGLES, 0, 3) self.vao.release() self.program.release() # swapInterval is 1 by default which means that swapBuffers() will (hopefully) block # and wait for vsync. self.context.swapBuffers(self) self.context.doneCurrent() def slotTimer(self): self.render() self.angle += 1 def glInfo(self): if not self.context.makeCurrent(self): raise Exception("makeCurrent() failed") functions = self.context.functions() text = """Vendor: {}\nRenderer: {}\nVersion: {}\nShading language: {} \nContext Format: {}\n\nSurface Format: {}""".format( functions.glGetString(GL.GL_VENDOR), functions.glGetString(GL.GL_RENDERER), functions.glGetString(GL.GL_VERSION), functions.glGetString(GL.GL_SHADING_LANGUAGE_VERSION), print_surface_format(self.context.format()), print_surface_format(self.format())) self.context.doneCurrent() return text
class SquareObject(QObject): def __init__(self, parent=None): super(SquareObject, self).__init__(parent=parent) self.initialize_shader_program() self.initialize_geometry() self.initialize_geometry_on_gpu() self.initialize_texture_on_gpu() def destroy(self): self.vao.release() self.vao.destroy() self.vbo.release() self.vbo.destroy() self.ebo.release() self.ebo.destroy() self.texture0.release() self.texture0.destroy() self.texture1.release() self.texture1.destroy() def initialize_shader_program(self): self.vertex_shader = """ #version 330 core layout (location = 0) in vec3 in_coords; layout (location = 1) in vec3 in_color; layout (location = 2) in vec2 in_tex_coords; uniform mat4 transform; out vec3 out_color; out vec2 out_tex_coords; void main() { gl_Position = transform * vec4(in_coords, 1.0); out_color = in_color; out_tex_coords = vec2(in_tex_coords.x, in_tex_coords.y); } """ self.fragment_shader = """ #version 330 core in vec3 out_color; in vec2 out_tex_coords; out vec4 frag_color; uniform sampler2D texture0; uniform sampler2D texture1; void main() { if (out_tex_coords.x > 0.5) frag_color = texture(texture0, out_tex_coords); else frag_color = texture(texture1, out_tex_coords); //frag_color = mix(texture(texture0, out_tex_coords), // texture(texture1, out_tex_coords), 0.5) // * vec4(out_color, 1.0); } """ self.arg_pos = { 'in_coords': 0, 'in_color': 1, 'in_tex_coords': 2, 'out_color': 0 } self.program = QOpenGLShaderProgram(parent=self.parent()) self.program.addCacheableShaderFromSourceCode(QOpenGLShader.Vertex, self.vertex_shader) self.program.addCacheableShaderFromSourceCode(QOpenGLShader.Fragment, self.fragment_shader) self.program.link() self.program.bind() def initialize_geometry(self): self.vertices = np.array( [ # coords color texture coords [0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0], # bottom-right [0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0], # top-right [-0.5, 0.5, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0], # top-let [-0.5, -0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0] # bottom-let ], dtype=np.float32) self.triangles = np.array([[0, 1, 2], [2, 3, 0]], dtype=np.int32) def initialize_geometry_on_gpu(self): self.vao = QOpenGLVertexArrayObject(self.parent()) if not self.vao.create(): raise ValueError('Could not create VAO') self.vao.bind() self.vbo = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer) if not self.vbo.create(): raise ValueError('Could not create VBO') self.vbo.bind() self.vbo.setUsagePattern(QOpenGLBuffer.StaticDraw) vertices_data = self.vertices.tostring() self.vbo.allocate(len(vertices_data)) self.vbo.write(0, vertices_data, len(vertices_data)) self.ebo = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer) if not self.ebo.create(): raise ValueError('Could not create EBO') self.ebo.bind() self.ebo.setUsagePattern(QOpenGLBuffer.StaticDraw) triangles_data = self.triangles.tostring() self.ebo.allocate(len(triangles_data)) self.ebo.write(0, triangles_data, len(triangles_data)) self.program.enableAttributeArray(self.arg_pos['in_coords']) self.program.setAttributeBuffer( self.arg_pos['in_coords'], gl.GL_FLOAT, 0, 3, self.vertices.shape[1] * self.vertices.dtype.itemsize) self.program.enableAttributeArray(self.arg_pos['in_color']) self.program.setAttributeBuffer( self.arg_pos['in_color'], gl.GL_FLOAT, 3 * self.vertices.dtype.itemsize, 3, self.vertices.shape[1] * self.vertices.dtype.itemsize) self.program.enableAttributeArray(self.arg_pos['in_tex_coords']) self.program.setAttributeBuffer( self.arg_pos['in_tex_coords'], gl.GL_FLOAT, 6 * self.vertices.dtype.itemsize, 2, self.vertices.shape[1] * self.vertices.dtype.itemsize) self.vao.release() self.vbo.release() self.ebo.release() def initialize_texture_on_gpu(self): # Texture 0. image0 = QImage(path.join(DATA_DIR, 'ksmall.jpg')).mirrored() self.texture0 = QOpenGLTexture(image0) self.texture0.setMinificationFilter(QOpenGLTexture.LinearMipMapLinear) self.texture0.setMagnificationFilter(QOpenGLTexture.Linear) self.texture0.setWrapMode(QOpenGLTexture.Repeat) self.texture0.bind(0) self.program.setUniformValue(self.program.uniformLocation('texture0'), 0) # Texture 1. image1 = QImage(path.join(DATA_DIR, 'sunflowerField.jpg')).mirrored() self.texture1 = QOpenGLTexture(image1) self.texture1.setMinificationFilter(QOpenGLTexture.LinearMipMapLinear) self.texture1.setMagnificationFilter(QOpenGLTexture.Linear) self.texture1.setWrapMode(QOpenGLTexture.Repeat) self.texture1.bind(1) self.program.setUniformValue(self.program.uniformLocation('texture1'), 1) def render(self, transform): self.program.bind() self.program.setUniformValue('transform', transform) self.vao.bind() gl.glDrawElements(gl.GL_TRIANGLES, self.triangles.size, gl.GL_UNSIGNED_INT, None) self.program.release()
class RenderWindow(QWindow): def __init__(self, format): super(RenderWindow, self).__init__() self.setSurfaceType(QWindow.OpenGLSurface) self.setFormat(format) self.context = QOpenGLContext(self) self.context.setFormat(self.requestedFormat()) if not self.context.create(): raise Exception("Unable to create GL context") self.program = None self.timer = None self.angle = 0 def initGl(self): self.program = QOpenGLShaderProgram(self) self.vao = QOpenGLVertexArrayObject() self.vbo = QOpenGLBuffer() format = self.context.format() useNewStyleShader = format.profile() == QSurfaceFormat.CoreProfile # Try to handle 3.0 & 3.1 that do not have the core/compatibility profile # concept 3.2+ has. This may still fail since version 150 (3.2) is # specified in the sources but it's worth a try. if (format.renderableType() == QSurfaceFormat.OpenGL and format.majorVersion() == 3 and format.minorVersion() <= 1): useNewStyleShader = not format.testOption(QSurfaceFormat.DeprecatedFunctions) vertexShader = vertexShaderSource if useNewStyleShader else vertexShaderSource110 fragmentShader = fragmentShaderSource if useNewStyleShader else fragmentShaderSource110 if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertexShader): raise Exception("Vertex shader could not be added: {} ({})".format(self.program.log(), vertexShader)) if not self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragmentShader): raise Exception("Fragment shader could not be added: {} ({})".format(self.program.log(), fragmentShader)) if not self.program.link(): raise Exception("Could not link shaders: {}".format(self.program.log())) self.posAttr = self.program.attributeLocation("posAttr") self.colAttr = self.program.attributeLocation("colAttr") self.matrixUniform = self.program.uniformLocation("matrix") self.vbo.create() self.vbo.bind() self.verticesData = vertices.tobytes() self.colorsData = colors.tobytes() verticesSize = 4 * vertices.size colorsSize = 4 * colors.size self.vbo.allocate(VoidPtr(self.verticesData), verticesSize + colorsSize) self.vbo.write(verticesSize, VoidPtr(self.colorsData), colorsSize) self.vbo.release() vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) if self.vao.isCreated(): # have VAO support, use it self.setupVertexAttribs() def setupVertexAttribs(self): self.vbo.bind() self.program.setAttributeBuffer(self.posAttr, GL.GL_FLOAT, 0, 2) self.program.setAttributeBuffer(self.colAttr, GL.GL_FLOAT, 4 * vertices.size, 3) self.program.enableAttributeArray(self.posAttr) self.program.enableAttributeArray(self.colAttr) self.vbo.release() def exposeEvent(self, event): if self.isExposed(): self.render() if self.timer is None: self.timer = QTimer(self) self.timer.timeout.connect(self.slotTimer) self.timer.start(10) def render(self): if not self.context.makeCurrent(self): raise Exception("makeCurrent() failed") functions = self.context.functions() if self.program is None: functions.glEnable(GL.GL_DEPTH_TEST) functions.glClearColor(0, 0, 0, 1) self.initGl() functions.glViewport(0, 0, self.width(), self.height()) functions.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) self.program.bind() matrix = QMatrix4x4() matrix.perspective(60, 4 / 3, 0.1, 100) matrix.translate(0, 0, -2) matrix.rotate(self.angle, 0, 1, 0) self.program.setUniformValue(self.matrixUniform, matrix) if self.vao.isCreated(): self.vao.bind() else: # no VAO support, set the vertex attribute arrays now self.setupVertexAttribs() functions.glDrawArrays(GL.GL_TRIANGLES, 0, 3) self.vao.release() self.program.release() # swapInterval is 1 by default which means that swapBuffers() will (hopefully) block # and wait for vsync. self.context.swapBuffers(self) self.context.doneCurrent() def slotTimer(self): self.render() self.angle += 1 def glInfo(self): if not self.context.makeCurrent(self): raise Exception("makeCurrent() failed") functions = self.context.functions() text = "Vendor: {}\nRenderer: {}\nVersion: {}\nShading language: {}".format( functions.glGetString(GL.GL_VENDOR), functions.glGetString(GL.GL_RENDERER), functions.glGetString(GL.GL_VERSION), functions.glGetString(GL.GL_SHADING_LANGUAGE_VERSION)) self.context.doneCurrent() return text
class SquareObject(QObject): def __init__(self, parent=None): super(SquareObject, self).__init__(parent=parent) self.initialize_shader_program() self.initialize_geometry() self.initialize_geometry_on_gpu() def initialize_shader_program(self): self.vertex_shader = """ #version 330 core layout (location = 0) in vec3 in_coords; layout (location = 1) in vec3 in_color; out vec3 out_color; void main() { gl_Position = vec4(in_coords, 1.0); gl_PointSize = 200.0; out_color = in_color; } """ self.fragment_shader = """ #version 330 core in vec3 out_color; out vec4 frag_color; void main() { frag_color = vec4(out_color, 1.0); } """ self.arg_pos = {'in_coords': 0, 'in_color': 1, 'out_color': 0} self.program = QOpenGLShaderProgram(parent=self.parent()) self.program.addCacheableShaderFromSourceCode(QOpenGLShader.Vertex, self.vertex_shader) self.program.addCacheableShaderFromSourceCode(QOpenGLShader.Fragment, self.fragment_shader) self.program.link() def initialize_geometry(self): self.vertices = np.array( [ # coords color [0.5, 0.5, 0.0, 1.0, 0.0, 0.0], # top-right [0.5, -0.5, 0.0, 0.0, 1.0, 0.0], # bottom-right [-0.5, -0.5, 0.0, 0.0, 0.0, 1.0], # bottom-left [-0.5, 0.5, 0.0, 1.0, 1.0, 0.0] # top-left ], dtype=np.float32) self.triangles = np.array([[0, 1, 2], [2, 3, 0]], dtype=np.int32) def initialize_geometry_on_gpu(self): self.vao = QOpenGLVertexArrayObject(self.parent()) if not self.vao.create(): raise ValueError('Could not create VAO') self.vao.bind() self.vbo = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer) if not self.vbo.create(): raise ValueError('Could not create VBO') self.vbo.bind() self.vbo.setUsagePattern(QOpenGLBuffer.StaticDraw) vertices_data = self.vertices.tostring() self.vbo.allocate(len(vertices_data)) self.vbo.write(0, vertices_data, len(vertices_data)) self.ebo = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer) if not self.ebo.create(): raise ValueError('Could not create EBO') self.ebo.bind() self.ebo.setUsagePattern(QOpenGLBuffer.StaticDraw) triangles_data = self.triangles.tostring() self.ebo.allocate(len(triangles_data)) self.ebo.write(0, triangles_data, len(triangles_data)) self.program.enableAttributeArray(self.arg_pos['in_coords']) self.program.setAttributeBuffer( self.arg_pos['in_coords'], gl.GL_FLOAT, 0, 3, self.vertices.shape[1] * self.vertices.dtype.itemsize) self.program.enableAttributeArray(self.arg_pos['in_color']) self.program.setAttributeBuffer( self.arg_pos['in_color'], gl.GL_FLOAT, 3 * self.vertices.dtype.itemsize, 3, self.vertices.shape[1] * self.vertices.dtype.itemsize) self.vao.release() self.vbo.release() self.ebo.release() def render(self): self.program.bind() self.vao.bind() gl.glDrawElements(gl.GL_TRIANGLES, self.triangles.size, gl.GL_UNSIGNED_INT, None) self.program.release()