diff options
Diffstat (limited to 'examples/opengl/threadedqopenglwidget/renderer.py')
-rw-r--r-- | examples/opengl/threadedqopenglwidget/renderer.py | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/examples/opengl/threadedqopenglwidget/renderer.py b/examples/opengl/threadedqopenglwidget/renderer.py new file mode 100644 index 000000000..81ec63cbb --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/renderer.py @@ -0,0 +1,326 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import ctypes +import math +import numpy + +from OpenGL import GL + +from PySide6.QtOpenGL import QOpenGLShader, QOpenGLShaderProgram, QOpenGLBuffer +from PySide6.QtGui import (QGuiApplication, QOpenGLFunctions, QVector3D, + QMatrix4x4) +from PySide6.QtCore import (QElapsedTimer, QObject, QMetaObject, QMutex, + QMutexLocker, QThread, QWaitCondition, Signal, Slot) + +# Some OpenGL implementations have serious issues with compiling and linking +# shaders on multiple threads concurrently. Avoid self. +init_mutex = QMutex() + + +VERTEX_SHADER = """attribute highp vec4 vertex; +attribute mediump vec3 normal; +uniform mediump mat4 matrix; +varying mediump vec4 color; +void main(void) +{ + vec3 toLight = normalize(vec3(0.0, 0.3, 1.0)); + float angle = max(dot(normal, toLight), 0.0); + vec3 col = vec3(0.40, 1.0, 0.0); + color = vec4(col * 0.2 + col * 0.8 * angle, 1.0); + color = clamp(color, 0.0, 1.0); + gl_Position = matrix * vertex; +} +""" + + +FRAGMENT_SHADER = """varying mediump vec4 color; +void main(void) +{ + gl_FragColor = color; +} +""" + + +class Renderer(QObject, QOpenGLFunctions): + + context_wanted = Signal() + + def __init__(self, widget): + QObject.__init__(self) + QOpenGLFunctions.__init__(self) + self._glwidget = widget + self._inited = False + self._fAngle = 0 + self._fScale = 1 + + self._vertices = [] + self._normals = [] + self._program = QOpenGLShaderProgram() + self._vbo = QOpenGLBuffer() + self._vertex_attr = 0 + self._normal_attr = 0 + self._matrix_uniform = 0 + self._renderMutex = QMutex() + self._elapsed = QElapsedTimer() + self._grabMutex = QMutex() + self._grab_condition = QWaitCondition() + self._exiting = False + + def lock_renderer(self): + self._renderMutex.lock() + + def unlock_renderer(self): + self._renderMutex.unlock() + + def grab_mutex(self): + return self._grabMutex + + def grab_condition(self): + return self._grab_condition + + def prepare_exit(self): + self._exiting = True + self._grab_condition.wakeAll() + + def paint_Qt_logo(self): + self._vbo.bind() + self._program.setAttributeBuffer(self._vertex_attr, GL.GL_FLOAT, 0, 3) + size = len(self._vertices) * 3 * ctypes.sizeof(ctypes.c_float) + self._program.setAttributeBuffer(self._normal_attr, GL.GL_FLOAT, size, 3) + self._vbo.release() + + self._program.enableAttributeArray(self._vertex_attr) + self._program.enableAttributeArray(self._normal_attr) + + self.glDrawArrays(GL.GL_TRIANGLES, 0, len(self._vertices)) + + self._program.disableAttributeArray(self._normal_attr) + self._program.disableAttributeArray(self._vertex_attr) + + @Slot() + def render(self): + global init_mutex + + if self._exiting: + return + + ctx = self._glwidget.context() + if not ctx: # QOpenGLWidget not yet initialized + return + + # Grab the context. + self._grabMutex.lock() + self.context_wanted.emit() + self._grab_condition.wait(self._grabMutex) + + with QMutexLocker(self._renderMutex): + self._grabMutex.unlock() + + if self._exiting: + return + + assert ctx.thread() == QThread.currentThread() + + # Make the context (and an offscreen surface) current for self thread. + # The QOpenGLWidget's fbo is bound in the context. + self._glwidget.makeCurrent() + + if not self._inited: + self._inited = True + self.initializeOpenGLFunctions() + with QMutexLocker(init_mutex): + self._init_gl() + self._elapsed.start() + + self._render_next() + + # Make no context current on self thread and move the + # QOpenGLWidget'scontext back to the gui thread. + self._glwidget.doneCurrent() + ctx.moveToThread(QGuiApplication.instance().thread()) + + # Schedule composition. Note that self will use QueuedConnection, + # meaning that update() will be invoked on the gui thread. + QMetaObject.invokeMethod(self._glwidget, "update") + + def _init_gl(self): + vshader = QOpenGLShader(QOpenGLShader.Vertex, self) + vshader.compileSourceCode(VERTEX_SHADER) + + fshader = QOpenGLShader(QOpenGLShader.Fragment, self) + fshader.compileSourceCode(FRAGMENT_SHADER) + + self._program.addShader(vshader) + self._program.addShader(fshader) + self._program.link() + + self._vertex_attr = self._program.attributeLocation("vertex") + self._normal_attr = self._program.attributeLocation("normal") + self._matrix_uniform = self._program.uniformLocation("matrix") + + self._fAngle = 0 + self._fScale = 1 + self.create_geometry() + + self._vbo.create() + self._vbo.bind() + + data_count = len(self._vertices) * 2 * 3 + data = numpy.empty(data_count, dtype=ctypes.c_float) + i = 0 + for v in self._vertices: + data[i] = v.x() + i += 1 + data[i] = v.y() + i += 1 + data[i] = v.z() + i += 1 + for n in self._normals: + data[i] = n.x() + i += 1 + data[i] = n.y() + i += 1 + data[i] = n.z() + i += 1 + + vertices_size = data_count * ctypes.sizeof(ctypes.c_float) + self._vbo.allocate(data.tobytes(), vertices_size) + + def _render_next(self): + self.glClearColor(0.1, 0.2, 0.2, 1.0) + self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) + + self.glFrontFace(GL.GL_CW) + self.glCullFace(GL.GL_FRONT) + self.glEnable(GL.GL_CULL_FACE) + self.glEnable(GL.GL_DEPTH_TEST) + + modelview = QMatrix4x4() + modelview.rotate(self._fAngle, 0.0, 1.0, 0.0) + modelview.rotate(self._fAngle, 1.0, 0.0, 0.0) + modelview.rotate(self._fAngle, 0.0, 0.0, 1.0) + modelview.scale(self._fScale) + modelview.translate(0.0, -0.2, 0.0) + + self._program.bind() + self._program.setUniformValue(self._matrix_uniform, modelview) + self.paint_Qt_logo() + self._program.release() + + self.glDisable(GL.GL_DEPTH_TEST) + self.glDisable(GL.GL_CULL_FACE) + + self._fAngle += 1.0 + + def create_geometry(self): + self._vertices = [] + self._normals = [] + + x1 = +0.06 + y1 = -0.14 + x2 = +0.14 + y2 = -0.06 + x3 = +0.08 + y3 = +0.00 + x4 = +0.30 + y4 = +0.22 + + self.quad(x1, y1, x2, y2, y2, x2, y1, x1) + self.quad(x3, y3, x4, y4, y4, x4, y3, x3) + + self.extrude(x1, y1, x2, y2) + self.extrude(x2, y2, y2, x2) + self.extrude(y2, x2, y1, x1) + self.extrude(y1, x1, x1, y1) + self.extrude(x3, y3, x4, y4) + self.extrude(x4, y4, y4, x4) + self.extrude(y4, x4, y3, x3) + + NUM_SECTORS = 100 + SECTOR_ANGLE = 2 * math.pi / NUM_SECTORS + + for i in range(NUM_SECTORS): + angle = i * SECTOR_ANGLE + sin_angle = math.sin(angle) + cos_angle = math.cos(angle) + x5 = 0.30 * sin_angle + y5 = 0.30 * cos_angle + x6 = 0.20 * sin_angle + y6 = 0.20 * cos_angle + + angle += SECTOR_ANGLE + sin_angle = math.sin(angle) + cos_angle = math.cos(angle) + x7 = 0.20 * sin_angle + y7 = 0.20 * cos_angle + x8 = 0.30 * sin_angle + y8 = 0.30 * cos_angle + + self.quad(x5, y5, x6, y6, x7, y7, x8, y8) + + self.extrude(x6, y6, x7, y7) + self.extrude(x8, y8, x5, y5) + + for i in range(len(self._vertices)): + self._vertices[i] *= 2.0 + + def quad(self, x1, y1, x2, y2, x3, y3, x4, y4): + + self._vertices.append(QVector3D(x1, y1, -0.05)) + self._vertices.append(QVector3D(x2, y2, -0.05)) + self._vertices.append(QVector3D(x4, y4, -0.05)) + + self._vertices.append(QVector3D(x3, y3, -0.05)) + self._vertices.append(QVector3D(x4, y4, -0.05)) + self._vertices.append(QVector3D(x2, y2, -0.05)) + + n = QVector3D.normal(QVector3D(x2 - x1, y2 - y1, 0.0), + QVector3D(x4 - x1, y4 - y1, 0.0)) + + self._normals.append(n) + self._normals.append(n) + self._normals.append(n) + + self._normals.append(n) + self._normals.append(n) + self._normals.append(n) + + self._vertices.append(QVector3D(x4, y4, 0.05)) + self._vertices.append(QVector3D(x2, y2, 0.05)) + self._vertices.append(QVector3D(x1, y1, 0.05)) + + self._vertices.append(QVector3D(x2, y2, 0.05)) + self._vertices.append(QVector3D(x4, y4, 0.05)) + self._vertices.append(QVector3D(x3, y3, 0.05)) + + n = QVector3D.normal(QVector3D(x2 - x4, y2 - y4, 0.0), + QVector3D(x1 - x4, y1 - y4, 0.0)) + + self._normals.append(n) + self._normals.append(n) + self._normals.append(n) + + self._normals.append(n) + self._normals.append(n) + self._normals.append(n) + + def extrude(self, x1, y1, x2, y2): + self._vertices.append(QVector3D(x1, y1, +0.05)) + self._vertices.append(QVector3D(x2, y2, +0.05)) + self._vertices.append(QVector3D(x1, y1, -0.05)) + + self._vertices.append(QVector3D(x2, y2, -0.05)) + self._vertices.append(QVector3D(x1, y1, -0.05)) + self._vertices.append(QVector3D(x2, y2, +0.05)) + + n = QVector3D.normal(QVector3D(x2 - x1, y2 - y1, 0.0), + QVector3D(0.0, 0.0, -0.1)) + + self._normals.append(n) + self._normals.append(n) + self._normals.append(n) + + self._normals.append(n) + self._normals.append(n) + self._normals.append(n) |