diff options
Diffstat (limited to 'examples/opengl')
31 files changed, 1796 insertions, 2622 deletions
diff --git a/examples/opengl/2dpainting.py b/examples/opengl/2dpainting.py deleted file mode 100644 index 4e4dc8aa4..000000000 --- a/examples/opengl/2dpainting.py +++ /dev/null @@ -1,173 +0,0 @@ - -############################################################################ -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################ - -"""PySide2 port of the opengl/legacy/2dpainting example from Qt v5.x""" - -import sys -import math -from PySide2.QtCore import * -from PySide2.QtGui import * -from PySide2.QtWidgets import * -from PySide2.QtOpenGL import * - -try: - from OpenGL import GL -except ImportError: - app = QApplication(sys.argv) - messageBox = QMessageBox(QMessageBox.Critical, "OpenGL 2dpainting", - "PyOpenGL must be installed to run this example.", - QMessageBox.Close) - messageBox.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - messageBox.exec_() - sys.exit(1) - - -class Helper: - def __init__(self): - gradient = QLinearGradient(QPointF(50, -20), QPointF(80, 20)) - gradient.setColorAt(0.0, Qt.white) - gradient.setColorAt(1.0, QColor(0xa6, 0xce, 0x39)) - - self.background = QBrush(QColor(64, 32, 64)) - self.circleBrush = QBrush(gradient) - self.circlePen = QPen(Qt.black) - self.circlePen.setWidth(1) - self.textPen = QPen(Qt.white) - self.textFont = QFont() - self.textFont.setPixelSize(50) - - def paint(self, painter, event, elapsed): - painter.fillRect(event.rect(), self.background) - painter.translate(100, 100) - - painter.save() - painter.setBrush(self.circleBrush) - painter.setPen(self.circlePen) - painter.rotate(elapsed * 0.030) - - r = elapsed/1000.0 - n = 30 - for i in range(n): - painter.rotate(30) - radius = 0 + 120.0*((i+r)/n) - circleRadius = 1 + ((i+r)/n)*20 - painter.drawEllipse(QRectF(radius, -circleRadius, - circleRadius*2, circleRadius*2)) - - painter.restore() - - painter.setPen(self.textPen) - painter.setFont(self.textFont) - painter.drawText(QRect(-50, -50, 100, 100), Qt.AlignCenter, "Qt") - - -class Widget(QWidget): - def __init__(self, helper, parent = None): - QWidget.__init__(self, parent) - - self.helper = helper - self.elapsed = 0 - self.setFixedSize(200, 200) - - def animate(self): - self.elapsed = (self.elapsed + self.sender().interval()) % 1000 - self.repaint() - - def paintEvent(self, event): - painter = QPainter() - painter.begin(self) - painter.setRenderHint(QPainter.Antialiasing) - self.helper.paint(painter, event, self.elapsed) - painter.end() - - -class GLWidget(QGLWidget): - def __init__(self, helper, parent = None): - QGLWidget.__init__(self, QGLFormat(QGL.SampleBuffers), parent) - - self.helper = helper - self.elapsed = 0 - self.setFixedSize(200, 200) - - def animate(self): - self.elapsed = (self.elapsed + self.sender().interval()) % 1000 - self.repaint() - - def paintEvent(self, event): - painter = QPainter() - painter.begin(self) - self.helper.paint(painter, event, self.elapsed) - painter.end() - - -class Window(QWidget): - def __init__(self, parent = None): - QWidget.__init__(self, parent) - - helper = Helper() - native = Widget(helper, self) - openGL = GLWidget(helper, self) - nativeLabel = QLabel(self.tr("Native")) - nativeLabel.setAlignment(Qt.AlignHCenter) - openGLLabel = QLabel(self.tr("OpenGL")) - openGLLabel.setAlignment(Qt.AlignHCenter) - - layout = QGridLayout() - layout.addWidget(native, 0, 0) - layout.addWidget(openGL, 0, 1) - layout.addWidget(nativeLabel, 1, 0) - layout.addWidget(openGLLabel, 1, 1) - self.setLayout(layout) - - timer = QTimer(self) - self.connect(timer, SIGNAL("timeout()"), native.animate) - self.connect(timer, SIGNAL("timeout()"), openGL.animate) - timer.start(50) - - self.setWindowTitle(self.tr("2D Painting on Native and OpenGL Widgets")) - - -if __name__ == '__main__': - app = QApplication(sys.argv) - window = Window() - window.show() - sys.exit(app.exec_()) diff --git a/examples/opengl/contextinfo.py b/examples/opengl/contextinfo.py deleted file mode 100644 index a963b8952..000000000 --- a/examples/opengl/contextinfo.py +++ /dev/null @@ -1,283 +0,0 @@ - -############################################################################# -## -## Copyright (C) 2017 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################# - -"""PySide2 port of the opengl/contextinfo example from Qt v5.x""" - -from argparse import ArgumentParser, RawTextHelpFormatter -import numpy -import sys -from textwrap import dedent - - -from PySide2.QtCore import QCoreApplication, QLibraryInfo, QSize, QTimer, Qt -from PySide2.QtGui import (QMatrix4x4, QOpenGLBuffer, QOpenGLContext, QOpenGLShader, - QOpenGLShaderProgram, QOpenGLVertexArrayObject, QSurfaceFormat, QWindow) -from PySide2.QtWidgets import (QApplication, QHBoxLayout, QMessageBox, QPlainTextEdit, - QWidget) -from PySide2.support import VoidPtr -try: - from OpenGL import GL -except ImportError: - app = QApplication(sys.argv) - messageBox = QMessageBox(QMessageBox.Critical, "ContextInfo", - "PyOpenGL must be installed to run this example.", - QMessageBox.Close) - messageBox.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - messageBox.exec_() - sys.exit(1) - -vertexShaderSource110 = dedent(""" - // version 110 - attribute highp vec4 posAttr; - attribute lowp vec4 colAttr; - varying lowp vec4 col; - uniform highp mat4 matrix; - void main() { - col = colAttr; - gl_Position = matrix * posAttr; - } - """) - -fragmentShaderSource110 = dedent(""" - // version 110 - varying lowp vec4 col; - void main() { - gl_FragColor = col; - } - """) - -vertexShaderSource = dedent(""" - // version 150 - in vec4 posAttr; - in vec4 colAttr; - out vec4 col; - uniform mat4 matrix; - void main() { - col = colAttr; - gl_Position = matrix * posAttr; - } - """) - -fragmentShaderSource = dedent(""" - // version 150 - in vec4 col; - out vec4 fragColor; - void main() { - fragColor = col; - } - """) - -vertices = numpy.array([0, 0.707, -0.5, -0.5, 0.5, -0.5], dtype = numpy.float32) -colors = numpy.array([1, 0, 0, 0, 1, 0, 0, 0, 1], dtype = numpy.float32) - - -def print_surface_format(surface_format): - profile_name = 'core' if surface_format.profile() == QSurfaceFormat.CoreProfile else 'compatibility' - return "{} version {}.{}".format(profile_name, - surface_format.majorVersion(), surface_format.minorVersion()) - -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 MainWindow(QWidget): - def __init__(self): - super(MainWindow, self).__init__() - hBoxLayout = QHBoxLayout(self) - self.plainTextEdit = QPlainTextEdit() - self.plainTextEdit.setMinimumWidth(400) - self.plainTextEdit.setReadOnly(True) - hBoxLayout.addWidget(self.plainTextEdit) - self.renderWindow = RenderWindow(QSurfaceFormat()) - container = QWidget.createWindowContainer(self.renderWindow) - container.setMinimumSize(QSize(400, 400)) - hBoxLayout.addWidget(container) - - def updateDescription(self): - text = "{}\n\nPython {}\n\n{}".format(QLibraryInfo.build(), sys.version, - self.renderWindow.glInfo()) - self.plainTextEdit.setPlainText(text) - -if __name__ == '__main__': - parser = ArgumentParser(description="contextinfo", formatter_class=RawTextHelpFormatter) - parser.add_argument('--gles', '-g', action='store_true', - help='Use OpenGL ES') - parser.add_argument('--software', '-s', action='store_true', - help='Use Software OpenGL') - parser.add_argument('--desktop', '-d', action='store_true', - help='Use Desktop OpenGL') - options = parser.parse_args() - if options.gles: - QCoreApplication.setAttribute(Qt.AA_UseOpenGLES) - elif options.software: - QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) - elif options.desktop: - QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL) - - app = QApplication(sys.argv) - mainWindow = MainWindow() - mainWindow.show() - mainWindow.updateDescription() - sys.exit(app.exec_()) diff --git a/examples/opengl/contextinfo/contextinfo.py b/examples/opengl/contextinfo/contextinfo.py new file mode 100644 index 000000000..311d5b765 --- /dev/null +++ b/examples/opengl/contextinfo/contextinfo.py @@ -0,0 +1,266 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the opengl/contextinfo example from Qt v5.x""" + +from argparse import ArgumentParser, RawTextHelpFormatter +import numpy +import sys +from textwrap import dedent + + +from PySide6.QtCore import (QCoreApplication, QLibraryInfo, QSize, QTimer, Qt, + Slot) +from PySide6.QtGui import (QMatrix4x4, QOpenGLContext, QSurfaceFormat, QWindow) +from PySide6.QtOpenGL import (QOpenGLBuffer, QOpenGLShader, + QOpenGLShaderProgram, QOpenGLVertexArrayObject) +from PySide6.QtWidgets import (QApplication, QHBoxLayout, QMessageBox, QPlainTextEdit, + QWidget) +from PySide6.support import VoidPtr +try: + from OpenGL import GL +except ImportError: + app = QApplication(sys.argv) + message_box = QMessageBox(QMessageBox.Critical, "ContextInfo", + "PyOpenGL must be installed to run this example.", QMessageBox.Close) + message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") + message_box.exec() + sys.exit(1) + +vertex_shader_source_110 = dedent(""" + // version 110 + attribute highp vec4 posAttr; + attribute lowp vec4 colAttr; + varying lowp vec4 col; + uniform highp mat4 matrix; + void main() { + col = colAttr; + gl_Position = matrix * posAttr; + } + """) + +fragment_shader_source_110 = dedent(""" + // version 110 + varying lowp vec4 col; + void main() { + gl_FragColor = col; + } + """) + +vertex_shader_source = dedent(""" + // version 150 + in vec4 posAttr; + in vec4 colAttr; + out vec4 col; + uniform mat4 matrix; + void main() { + col = colAttr; + gl_Position = matrix * posAttr; + } + """) + +fragment_shader_source = dedent(""" + // version 150 + in vec4 col; + out vec4 fragColor; + void main() { + fragColor = col; + } + """) + +vertices = numpy.array([0, 0.707, -0.5, -0.5, 0.5, -0.5], dtype=numpy.float32) +colors = numpy.array([1, 0, 0, 0, 1, 0, 0, 0, 1], dtype=numpy.float32) + + +def print_surface_format(surface_format): + if surface_format.profile() == QSurfaceFormat.CoreProfile: + profile_name = 'core' + else: + profile_name = 'compatibility' + major = surface_format.majorVersion() + minor = surface_format.minorVersion() + return f"{profile_name} version {major}.{minor}" + + +class RenderWindow(QWindow): + def __init__(self, fmt): + super().__init__() + self.setSurfaceType(QWindow.OpenGLSurface) + self.setFormat(fmt) + 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 init_gl(self): + self.program = QOpenGLShaderProgram(self) + self.vao = QOpenGLVertexArrayObject() + self.vbo = QOpenGLBuffer() + + fmt = self.context.format() + use_new_style_shader = fmt.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 (fmt.renderableType() == QSurfaceFormat.OpenGL and fmt.majorVersion() == 3 + and fmt.minorVersion() <= 1): + use_new_style_shader = not fmt.testOption(QSurfaceFormat.DeprecatedFunctions) + + vertex_shader = vertex_shader_source if use_new_style_shader else vertex_shader_source_110 + fragment_shader = (fragment_shader_source + if use_new_style_shader + else fragment_shader_source_110) + if not self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertex_shader): + log = self.program.log() + raise Exception("Vertex shader could not be added: {log} ({vertexShader})") + if not self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragment_shader): + log = self.program.log() + raise Exception(f"Fragment shader could not be added: {log} ({fragment_shader})") + if not self.program.link(): + log = self.program.log() + raise Exception(f"Could not link shaders: {log}") + + self._pos_attr = self.program.attributeLocation("posAttr") + self._col_attr = self.program.attributeLocation("colAttr") + self._matrix_uniform = self.program.uniformLocation("matrix") + + self.vbo.create() + self.vbo.bind() + self._vertices_data = vertices.tobytes() + self._colors_data = colors.tobytes() + vertices_size = 4 * vertices.size + colors_size = 4 * colors.size + self.vbo.allocate(VoidPtr(self._vertices_data), vertices_size + colors_size) + self.vbo.write(vertices_size, VoidPtr(self._colors_data), colors_size) + self.vbo.release() + + with QOpenGLVertexArrayObject.Binder(self.vao): + if self.vao.isCreated(): # have VAO support, use it + self.setup_vertex_attribs() + + def setup_vertex_attribs(self): + self.vbo.bind() + self.program.setAttributeBuffer(self._pos_attr, GL.GL_FLOAT, 0, 2) + self.program.setAttributeBuffer(self._col_attr, GL.GL_FLOAT, 4 * vertices.size, 3) + self.program.enableAttributeArray(self._pos_attr) + self.program.enableAttributeArray(self._col_attr) + 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.slot_timer) + 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.init_gl() + + retina_scale = self.devicePixelRatio() + functions.glViewport(0, 0, self.width() * retina_scale, + self.height() * retina_scale) + 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._matrix_uniform, matrix) + + if self.vao.isCreated(): + self.vao.bind() + else: # no VAO support, set the vertex attribute arrays now + self.setup_vertex_attribs() + + 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() + + @Slot() + def slot_timer(self): + self.render() + self.angle += 1 + + def glInfo(self): + if not self.context.makeCurrent(self): + raise Exception("makeCurrent() failed") + functions = self.context.functions() + gl_vendor = functions.glGetString(GL.GL_VENDOR) + gl_renderer = functions.glGetString(GL.GL_RENDERER) + gl_version = functions.glGetString(GL.GL_VERSION) + gl_lang_version = functions.glGetString(GL.GL_SHADING_LANGUAGE_VERSION) + context_surface_format = print_surface_format(self.context.format()) + surface_format = print_surface_format(self.format()) + + text = (f"Vendor: {gl_vendor}\n" + f"Renderer: {gl_renderer}\n" + f"Version: {gl_version}\n" + f"Shading language: {gl_lang_version}\n" + f"Context Format: {context_surface_format}\n\n" + f"Surface Format: {surface_format}") + self.context.doneCurrent() + return text + + +class MainWindow(QWidget): + def __init__(self): + super().__init__() + h_box_layout = QHBoxLayout(self) + self._plain_text_edit = QPlainTextEdit() + self._plain_text_edit.setMinimumWidth(400) + self._plain_text_edit.setReadOnly(True) + h_box_layout.addWidget(self._plain_text_edit) + self._render_window = RenderWindow(QSurfaceFormat()) + container = QWidget.createWindowContainer(self._render_window) + container.setMinimumSize(QSize(400, 400)) + h_box_layout.addWidget(container) + + def update_description(self): + build = QLibraryInfo.build() + gl = self._render_window.glInfo() + text = f"{build}\n\nPython {sys.version}\n\n{gl}" + self._plain_text_edit.setPlainText(text) + + +if __name__ == '__main__': + parser = ArgumentParser(description="contextinfo", formatter_class=RawTextHelpFormatter) + parser.add_argument('--gles', '-g', action='store_true', + help='Use OpenGL ES') + parser.add_argument('--software', '-s', action='store_true', + help='Use Software OpenGL') + parser.add_argument('--desktop', '-d', action='store_true', + help='Use Desktop OpenGL') + options = parser.parse_args() + if options.gles: + QCoreApplication.setAttribute(Qt.AA_UseOpenGLES) + elif options.software: + QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) + elif options.desktop: + QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL) + + app = QApplication(sys.argv) + main_window = MainWindow() + main_window.show() + main_window.update_description() + sys.exit(app.exec()) diff --git a/examples/opengl/contextinfo/contextinfo.pyproject b/examples/opengl/contextinfo/contextinfo.pyproject new file mode 100644 index 000000000..0758180ae --- /dev/null +++ b/examples/opengl/contextinfo/contextinfo.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["contextinfo.py"] +} diff --git a/examples/opengl/contextinfo/doc/contextinfo.png b/examples/opengl/contextinfo/doc/contextinfo.png Binary files differnew file mode 100644 index 000000000..c7911f162 --- /dev/null +++ b/examples/opengl/contextinfo/doc/contextinfo.png diff --git a/examples/opengl/contextinfo/doc/contextinfo.rst b/examples/opengl/contextinfo/doc/contextinfo.rst new file mode 100644 index 000000000..1fa9eb42d --- /dev/null +++ b/examples/opengl/contextinfo/doc/contextinfo.rst @@ -0,0 +1,10 @@ +Context Info Example +==================== + +The example shows the information about the OpenGL-related graphics +configuration of the current system. This can be useful as a +diagnostic tool. + +.. image:: contextinfo.png + :width: 400 + :alt: Context Simple Screenshot diff --git a/examples/opengl/grabber.py b/examples/opengl/grabber.py deleted file mode 100644 index c376ab7ae..000000000 --- a/examples/opengl/grabber.py +++ /dev/null @@ -1,435 +0,0 @@ - -############################################################################ -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################ - -"""PySide2 port of the opengl/legacy/grabber example from Qt v5.x""" - -import sys -import math - -from PySide2 import QtCore, QtGui, QtWidgets, QtOpenGL - -try: - from OpenGL.GL import * -except ImportError: - app = QtWidgets.QApplication(sys.argv) - messageBox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical, "OpenGL grabber", - "PyOpenGL must be installed to run this example.", - QtWidgets.QMessageBox.Close) - messageBox.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - messageBox.exec_() - sys.exit(1) - - -class GLWidget(QtOpenGL.QGLWidget): - xRotationChanged = QtCore.Signal(int) - yRotationChanged = QtCore.Signal(int) - zRotationChanged = QtCore.Signal(int) - - def __init__(self, parent=None): - super(GLWidget, self).__init__(parent) - - self.gear1 = 0 - self.gear2 = 0 - self.gear3 = 0 - self.xRot = 0 - self.yRot = 0 - self.zRot = 0 - self.gear1Rot = 0 - - timer = QtCore.QTimer(self) - timer.timeout.connect(self.advanceGears) - timer.start(20) - - def freeResources(self): - self.makeCurrent() - glDeleteLists(self.gear1, 1) - glDeleteLists(self.gear2, 1) - glDeleteLists(self.gear3, 1) - - def setXRotation(self, angle): - self.normalizeAngle(angle) - - if angle != self.xRot: - self.xRot = angle - self.xRotationChanged.emit(angle) - self.updateGL() - - def setYRotation(self, angle): - self.normalizeAngle(angle) - - if angle != self.yRot: - self.yRot = angle - self.yRotationChanged.emit(angle) - self.updateGL() - - def setZRotation(self, angle): - self.normalizeAngle(angle) - - if angle != self.zRot: - self.zRot = angle - self.zRotationChanged.emit(angle) - self.updateGL() - - def initializeGL(self): - lightPos = (5.0, 5.0, 10.0, 1.0) - reflectance1 = (0.8, 0.1, 0.0, 1.0) - reflectance2 = (0.0, 0.8, 0.2, 1.0) - reflectance3 = (0.2, 0.2, 1.0, 1.0) - - glLightfv(GL_LIGHT0, GL_POSITION, lightPos) - glEnable(GL_LIGHTING) - glEnable(GL_LIGHT0) - glEnable(GL_DEPTH_TEST) - - self.gear1 = self.makeGear(reflectance1, 1.0, 4.0, 1.0, 0.7, 20) - self.gear2 = self.makeGear(reflectance2, 0.5, 2.0, 2.0, 0.7, 10) - self.gear3 = self.makeGear(reflectance3, 1.3, 2.0, 0.5, 0.7, 10) - - glEnable(GL_NORMALIZE) - glClearColor(0.0, 0.0, 0.0, 1.0) - - def paintGL(self): - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - - glPushMatrix() - glRotated(self.xRot / 16.0, 1.0, 0.0, 0.0) - glRotated(self.yRot / 16.0, 0.0, 1.0, 0.0) - glRotated(self.zRot / 16.0, 0.0, 0.0, 1.0) - - self.drawGear(self.gear1, -3.0, -2.0, 0.0, self.gear1Rot / 16.0) - self.drawGear(self.gear2, +3.1, -2.0, 0.0, - -2.0 * (self.gear1Rot / 16.0) - 9.0) - - glRotated(+90.0, 1.0, 0.0, 0.0) - self.drawGear(self.gear3, -3.1, -1.8, -2.2, - +2.0 * (self.gear1Rot / 16.0) - 2.0) - - glPopMatrix() - - def resizeGL(self, width, height): - side = min(width, height) - if side < 0: - return - - glViewport(int((width - side) / 2), int((height - side) / 2), side, side) - - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - glFrustum(-1.0, +1.0, -1.0, 1.0, 5.0, 60.0) - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - glTranslated(0.0, 0.0, -40.0) - - def mousePressEvent(self, event): - self.lastPos = event.pos() - - def mouseMoveEvent(self, event): - dx = event.x() - self.lastPos.x() - dy = event.y() - self.lastPos.y() - - if event.buttons() & QtCore.Qt.LeftButton: - self.setXRotation(self.xRot + 8 * dy) - self.setYRotation(self.yRot + 8 * dx) - elif event.buttons() & QtCore.Qt.RightButton: - self.setXRotation(self.xRot + 8 * dy) - self.setZRotation(self.zRot + 8 * dx) - - self.lastPos = event.pos() - - def advanceGears(self): - self.gear1Rot += 2 * 16 - self.updateGL() - - def xRotation(self): - return self.xRot - - def yRotation(self): - return self.yRot - - def zRotation(self): - return self.zRot - - def makeGear(self, reflectance, innerRadius, outerRadius, thickness, toothSize, toothCount): - list = glGenLists(1) - glNewList(list, GL_COMPILE) - glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, reflectance) - - r0 = innerRadius - r1 = outerRadius - toothSize / 2.0 - r2 = outerRadius + toothSize / 2.0 - delta = (2.0 * math.pi / toothCount) / 4.0 - z = thickness / 2.0 - - glShadeModel(GL_FLAT) - - for i in range(2): - if i == 0: - sign = +1.0 - else: - sign = -1.0 - - glNormal3d(0.0, 0.0, sign) - - glBegin(GL_QUAD_STRIP) - - for j in range(toothCount+1): - angle = 2.0 * math.pi * j / toothCount - glVertex3d(r0 * math.cos(angle), r0 * math.sin(angle), sign * z) - glVertex3d(r1 * math.cos(angle), r1 * math.sin(angle), sign * z) - glVertex3d(r0 * math.cos(angle), r0 * math.sin(angle), sign * z) - glVertex3d(r1 * math.cos(angle + 3 * delta), r1 * math.sin(angle + 3 * delta), sign * z) - - glEnd() - - glBegin(GL_QUADS) - - for j in range(toothCount): - angle = 2.0 * math.pi * j / toothCount - glVertex3d(r1 * math.cos(angle), r1 * math.sin(angle), sign * z) - glVertex3d(r2 * math.cos(angle + delta), r2 * math.sin(angle + delta), sign * z) - glVertex3d(r2 * math.cos(angle + 2 * delta), r2 * math.sin(angle + 2 * delta), sign * z) - glVertex3d(r1 * math.cos(angle + 3 * delta), r1 * math.sin(angle + 3 * delta), sign * z) - - glEnd() - - glBegin(GL_QUAD_STRIP) - - for i in range(toothCount): - for j in range(2): - angle = 2.0 * math.pi * (i + (j / 2.0)) / toothCount - s1 = r1 - s2 = r2 - - if j == 1: - s1, s2 = s2, s1 - - glNormal3d(math.cos(angle), math.sin(angle), 0.0) - glVertex3d(s1 * math.cos(angle), s1 * math.sin(angle), +z) - glVertex3d(s1 * math.cos(angle), s1 * math.sin(angle), -z) - - glNormal3d(s2 * math.sin(angle + delta) - s1 * math.sin(angle), s1 * math.cos(angle) - s2 * math.cos(angle + delta), 0.0) - glVertex3d(s2 * math.cos(angle + delta), s2 * math.sin(angle + delta), +z) - glVertex3d(s2 * math.cos(angle + delta), s2 * math.sin(angle + delta), -z) - - glVertex3d(r1, 0.0, +z) - glVertex3d(r1, 0.0, -z) - glEnd() - - glShadeModel(GL_SMOOTH) - - glBegin(GL_QUAD_STRIP) - - for i in range(toothCount+1): - angle = i * 2.0 * math.pi / toothCount - glNormal3d(-math.cos(angle), -math.sin(angle), 0.0) - glVertex3d(r0 * math.cos(angle), r0 * math.sin(angle), +z) - glVertex3d(r0 * math.cos(angle), r0 * math.sin(angle), -z) - - glEnd() - - glEndList() - - return list - - def drawGear(self, gear, dx, dy, dz, angle): - glPushMatrix() - glTranslated(dx, dy, dz) - glRotated(angle, 0.0, 0.0, 1.0) - glCallList(gear) - glPopMatrix() - - def normalizeAngle(self, angle): - while (angle < 0): - angle += 360 * 16 - - while (angle > 360 * 16): - angle -= 360 * 16 - - -class MainWindow(QtWidgets.QMainWindow): - def __init__(self): - super(MainWindow, self).__init__() - - centralWidget = QtWidgets.QWidget() - self.setCentralWidget(centralWidget) - - self.glWidget = GLWidget() - self.pixmapLabel = QtWidgets.QLabel() - - self.glWidgetArea = QtWidgets.QScrollArea() - self.glWidgetArea.setWidget(self.glWidget) - self.glWidgetArea.setWidgetResizable(True) - self.glWidgetArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.glWidgetArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.glWidgetArea.setSizePolicy(QtWidgets.QSizePolicy.Ignored, - QtWidgets.QSizePolicy.Ignored) - self.glWidgetArea.setMinimumSize(50, 50) - - self.pixmapLabelArea = QtWidgets.QScrollArea() - self.pixmapLabelArea.setWidget(self.pixmapLabel) - self.pixmapLabelArea.setSizePolicy(QtWidgets.QSizePolicy.Ignored, - QtWidgets.QSizePolicy.Ignored) - self.pixmapLabelArea.setMinimumSize(50, 50) - - xSlider = self.createSlider(self.glWidget.xRotationChanged, - self.glWidget.setXRotation) - ySlider = self.createSlider(self.glWidget.yRotationChanged, - self.glWidget.setYRotation) - zSlider = self.createSlider(self.glWidget.zRotationChanged, - self.glWidget.setZRotation) - - self.createActions() - self.createMenus() - - centralLayout = QtWidgets.QGridLayout() - centralLayout.addWidget(self.glWidgetArea, 0, 0) - centralLayout.addWidget(self.pixmapLabelArea, 0, 1) - centralLayout.addWidget(xSlider, 1, 0, 1, 2) - centralLayout.addWidget(ySlider, 2, 0, 1, 2) - centralLayout.addWidget(zSlider, 3, 0, 1, 2) - centralWidget.setLayout(centralLayout) - - xSlider.setValue(15 * 16) - ySlider.setValue(345 * 16) - zSlider.setValue(0 * 16) - - self.setWindowTitle("Grabber") - self.resize(400, 300) - - def renderIntoPixmap(self): - size = self.getSize() - - if size.isValid(): - pixmap = self.glWidget.renderPixmap(size.width(), size.height()) - self.setPixmap(pixmap) - - def grabFrameBuffer(self): - image = self.glWidget.grabFrameBuffer() - self.setPixmap(QtGui.QPixmap.fromImage(image)) - - def clearPixmap(self): - self.setPixmap(QtGui.QPixmap()) - - def about(self): - QtWidgets.QMessageBox.about(self, "About Grabber", - "The <b>Grabber</b> example demonstrates two approaches for " - "rendering OpenGL into a Qt pixmap.") - - def createActions(self): - self.renderIntoPixmapAct = QtWidgets.QAction("&Render into Pixmap...", - self, shortcut="Ctrl+R", triggered=self.renderIntoPixmap) - - self.grabFrameBufferAct = QtWidgets.QAction("&Grab Frame Buffer", self, - shortcut="Ctrl+G", triggered=self.grabFrameBuffer) - - self.clearPixmapAct = QtWidgets.QAction("&Clear Pixmap", self, - shortcut="Ctrl+L", triggered=self.clearPixmap) - - self.exitAct = QtWidgets.QAction("E&xit", self, shortcut="Ctrl+Q", - triggered=self.close) - - self.aboutAct = QtWidgets.QAction("&About", self, triggered=self.about) - - self.aboutQtAct = QtWidgets.QAction("About &Qt", self, - triggered=QtWidgets.qApp.aboutQt) - - def createMenus(self): - self.fileMenu = self.menuBar().addMenu("&File") - self.fileMenu.addAction(self.renderIntoPixmapAct) - self.fileMenu.addAction(self.grabFrameBufferAct) - self.fileMenu.addAction(self.clearPixmapAct) - self.fileMenu.addSeparator() - self.fileMenu.addAction(self.exitAct) - - self.helpMenu = self.menuBar().addMenu("&Help") - self.helpMenu.addAction(self.aboutAct) - self.helpMenu.addAction(self.aboutQtAct) - - def createSlider(self, changedSignal, setterSlot): - slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) - slider.setRange(0, 360 * 16) - slider.setSingleStep(16) - slider.setPageStep(15 * 16) - slider.setTickInterval(15 * 16) - slider.setTickPosition(QtWidgets.QSlider.TicksRight) - - slider.valueChanged.connect(setterSlot) - changedSignal.connect(slider.setValue) - - return slider - - def setPixmap(self, pixmap): - self.pixmapLabel.setPixmap(pixmap) - size = pixmap.size() - - if size - QtCore.QSize(1, 0) == self.pixmapLabelArea.maximumViewportSize(): - size -= QtCore.QSize(1, 0) - - self.pixmapLabel.resize(size) - - def getSize(self): - text, ok = QtWidgets.QInputDialog.getText(self, "Grabber", - "Enter pixmap size:", QtWidgets.QLineEdit.Normal, - "%d x %d" % (self.glWidget.width(), self.glWidget.height())) - - if not ok: - return QtCore.QSize() - - regExp = QtCore.QRegExp("([0-9]+) *x *([0-9]+)") - - if regExp.exactMatch(text): - width = int(regExp.cap(1)) - height = int(regExp.cap(2)) - if width > 0 and width < 2048 and height > 0 and height < 2048: - return QtCore.QSize(width, height) - - return self.glWidget.size() - - -if __name__ == '__main__': - - app = QtWidgets.QApplication(sys.argv) - mainWin = MainWindow() - mainWin.show() - res = app.exec_() - mainWin.glWidget.freeResources() - sys.exit(res) diff --git a/examples/opengl/hellogl.py b/examples/opengl/hellogl.py deleted file mode 100644 index f3aa73ad5..000000000 --- a/examples/opengl/hellogl.py +++ /dev/null @@ -1,287 +0,0 @@ - -############################################################################ -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################ - -"""PySide2 port of the opengl/legacy/hellogl example from Qt v5.x""" - -import sys -import math -from PySide2 import QtCore, QtGui, QtWidgets, QtOpenGL - -try: - from OpenGL import GL -except ImportError: - app = QtWidgets.QApplication(sys.argv) - messageBox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical, "OpenGL hellogl", - "PyOpenGL must be installed to run this example.", - QtWidgets.QMessageBox.Close) - messageBox.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - messageBox.exec_() - sys.exit(1) - - -class Window(QtWidgets.QWidget): - def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) - - self.glWidget = GLWidget() - - self.xSlider = self.createSlider(QtCore.SIGNAL("xRotationChanged(int)"), - self.glWidget.setXRotation) - self.ySlider = self.createSlider(QtCore.SIGNAL("yRotationChanged(int)"), - self.glWidget.setYRotation) - self.zSlider = self.createSlider(QtCore.SIGNAL("zRotationChanged(int)"), - self.glWidget.setZRotation) - - mainLayout = QtWidgets.QHBoxLayout() - mainLayout.addWidget(self.glWidget) - mainLayout.addWidget(self.xSlider) - mainLayout.addWidget(self.ySlider) - mainLayout.addWidget(self.zSlider) - self.setLayout(mainLayout) - - self.xSlider.setValue(170 * 16) - self.ySlider.setValue(160 * 16) - self.zSlider.setValue(90 * 16) - - self.setWindowTitle(self.tr("Hello GL")) - - def createSlider(self, changedSignal, setterSlot): - slider = QtWidgets.QSlider(QtCore.Qt.Vertical) - - slider.setRange(0, 360 * 16) - slider.setSingleStep(16) - slider.setPageStep(15 * 16) - slider.setTickInterval(15 * 16) - slider.setTickPosition(QtWidgets.QSlider.TicksRight) - - self.glWidget.connect(slider, QtCore.SIGNAL("valueChanged(int)"), setterSlot) - self.connect(self.glWidget, changedSignal, slider, QtCore.SLOT("setValue(int)")) - - return slider - - -class GLWidget(QtOpenGL.QGLWidget): - xRotationChanged = QtCore.Signal(int) - yRotationChanged = QtCore.Signal(int) - zRotationChanged = QtCore.Signal(int) - - def __init__(self, parent=None): - QtOpenGL.QGLWidget.__init__(self, parent) - - self.object = 0 - self.xRot = 0 - self.yRot = 0 - self.zRot = 0 - - self.lastPos = QtCore.QPoint() - - self.trolltechGreen = QtGui.QColor.fromCmykF(0.40, 0.0, 1.0, 0.0) - self.trolltechPurple = QtGui.QColor.fromCmykF(0.39, 0.39, 0.0, 0.0) - - def xRotation(self): - return self.xRot - - def yRotation(self): - return self.yRot - - def zRotation(self): - return self.zRot - - def minimumSizeHint(self): - return QtCore.QSize(50, 50) - - def sizeHint(self): - return QtCore.QSize(400, 400) - - def setXRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.xRot: - self.xRot = angle - self.emit(QtCore.SIGNAL("xRotationChanged(int)"), angle) - self.updateGL() - - def setYRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.yRot: - self.yRot = angle - self.emit(QtCore.SIGNAL("yRotationChanged(int)"), angle) - self.updateGL() - - def setZRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.zRot: - self.zRot = angle - self.emit(QtCore.SIGNAL("zRotationChanged(int)"), angle) - self.updateGL() - - def initializeGL(self): - self.qglClearColor(self.trolltechPurple.darker()) - self.object = self.makeObject() - GL.glShadeModel(GL.GL_FLAT) - GL.glEnable(GL.GL_DEPTH_TEST) - GL.glEnable(GL.GL_CULL_FACE) - - def paintGL(self): - GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) - GL.glLoadIdentity() - GL.glTranslated(0.0, 0.0, -10.0) - GL.glRotated(self.xRot / 16.0, 1.0, 0.0, 0.0) - GL.glRotated(self.yRot / 16.0, 0.0, 1.0, 0.0) - GL.glRotated(self.zRot / 16.0, 0.0, 0.0, 1.0) - GL.glCallList(self.object) - - def resizeGL(self, width, height): - side = min(width, height) - GL.glViewport(int((width - side) / 2),int((height - side) / 2), side, side) - - GL.glMatrixMode(GL.GL_PROJECTION) - GL.glLoadIdentity() - GL.glOrtho(-0.5, +0.5, -0.5, +0.5, 4.0, 15.0) - GL.glMatrixMode(GL.GL_MODELVIEW) - - def mousePressEvent(self, event): - self.lastPos = QtCore.QPoint(event.pos()) - - def mouseMoveEvent(self, event): - dx = event.x() - self.lastPos.x() - dy = event.y() - self.lastPos.y() - - if event.buttons() & QtCore.Qt.LeftButton: - self.setXRotation(self.xRot + 8 * dy) - self.setYRotation(self.yRot + 8 * dx) - elif event.buttons() & QtCore.Qt.RightButton: - self.setXRotation(self.xRot + 8 * dy) - self.setZRotation(self.zRot + 8 * dx) - - self.lastPos = QtCore.QPoint(event.pos()) - - def makeObject(self): - genList = GL.glGenLists(1) - GL.glNewList(genList, GL.GL_COMPILE) - - GL.glBegin(GL.GL_QUADS) - - 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) - - Pi = 3.14159265358979323846 - NumSectors = 200 - - for i in range(NumSectors): - angle1 = (i * 2 * Pi) / NumSectors - x5 = 0.30 * math.sin(angle1) - y5 = 0.30 * math.cos(angle1) - x6 = 0.20 * math.sin(angle1) - y6 = 0.20 * math.cos(angle1) - - angle2 = ((i + 1) * 2 * Pi) / NumSectors - x7 = 0.20 * math.sin(angle2) - y7 = 0.20 * math.cos(angle2) - x8 = 0.30 * math.sin(angle2) - y8 = 0.30 * math.cos(angle2) - - self.quad(x5, y5, x6, y6, x7, y7, x8, y8) - - self.extrude(x6, y6, x7, y7) - self.extrude(x8, y8, x5, y5) - - GL.glEnd() - GL.glEndList() - - return genList - - def quad(self, x1, y1, x2, y2, x3, y3, x4, y4): - self.qglColor(self.trolltechGreen) - - GL.glVertex3d(x1, y1, +0.05) - GL.glVertex3d(x2, y2, +0.05) - GL.glVertex3d(x3, y3, +0.05) - GL.glVertex3d(x4, y4, +0.05) - - GL.glVertex3d(x4, y4, -0.05) - GL.glVertex3d(x3, y3, -0.05) - GL.glVertex3d(x2, y2, -0.05) - GL.glVertex3d(x1, y1, -0.05) - - def extrude(self, x1, y1, x2, y2): - self.qglColor(self.trolltechGreen.darker(250 + int(100 * x1))) - - GL.glVertex3d(x1, y1, -0.05) - GL.glVertex3d(x2, y2, -0.05) - GL.glVertex3d(x2, y2, +0.05) - GL.glVertex3d(x1, y1, +0.05) - - def normalizeAngle(self, angle): - while angle < 0: - angle += 360 * 16 - while angle > 360 * 16: - angle -= 360 * 16 - return angle - - def freeResources(self): - self.makeCurrent() - GL.glDeleteLists(self.object, 1) - -if __name__ == '__main__': - app = QtWidgets.QApplication(sys.argv) - window = Window() - window.show() - res = app.exec_() - window.glWidget.freeResources() - sys.exit(res) diff --git a/examples/opengl/hellogl2.py b/examples/opengl/hellogl2.py deleted file mode 100644 index ad3562f0b..000000000 --- a/examples/opengl/hellogl2.py +++ /dev/null @@ -1,472 +0,0 @@ - -############################################################################ -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2018 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################ - -"""PySide2 port of the opengl/hellogl2 example from Qt v5.x""" - -import sys -import math -import numpy -import ctypes -from PySide2.QtCore import QCoreApplication, Signal, SIGNAL, SLOT, Qt, QSize, QPoint -from PySide2.QtGui import (QVector3D, QOpenGLFunctions, QOpenGLVertexArrayObject, QOpenGLBuffer, - QOpenGLShaderProgram, QMatrix4x4, QOpenGLShader, QOpenGLContext, QSurfaceFormat) -from PySide2.QtWidgets import (QApplication, QWidget, QMessageBox, QHBoxLayout, QSlider, - QOpenGLWidget) -from shiboken2 import VoidPtr - -try: - from OpenGL import GL -except ImportError: - app = QApplication(sys.argv) - messageBox = QMessageBox(QMessageBox.Critical, "OpenGL hellogl", - "PyOpenGL must be installed to run this example.", - QMessageBox.Close) - messageBox.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - messageBox.exec_() - sys.exit(1) - - -class Window(QWidget): - def __init__(self, parent=None): - QWidget.__init__(self, parent) - - self.glWidget = GLWidget() - - self.xSlider = self.createSlider(SIGNAL("xRotationChanged(int)"), - self.glWidget.setXRotation) - self.ySlider = self.createSlider(SIGNAL("yRotationChanged(int)"), - self.glWidget.setYRotation) - self.zSlider = self.createSlider(SIGNAL("zRotationChanged(int)"), - self.glWidget.setZRotation) - - mainLayout = QHBoxLayout() - mainLayout.addWidget(self.glWidget) - mainLayout.addWidget(self.xSlider) - mainLayout.addWidget(self.ySlider) - mainLayout.addWidget(self.zSlider) - self.setLayout(mainLayout) - - self.xSlider.setValue(15 * 16) - self.ySlider.setValue(345 * 16) - self.zSlider.setValue(0 * 16) - - self.setWindowTitle(self.tr("Hello GL")) - - def createSlider(self, changedSignal, setterSlot): - slider = QSlider(Qt.Vertical) - - slider.setRange(0, 360 * 16) - slider.setSingleStep(16) - slider.setPageStep(15 * 16) - slider.setTickInterval(15 * 16) - slider.setTickPosition(QSlider.TicksRight) - - self.glWidget.connect(slider, SIGNAL("valueChanged(int)"), setterSlot) - self.connect(self.glWidget, changedSignal, slider, SLOT("setValue(int)")) - - return slider - - def keyPressEvent(self, event): - if event.key() == Qt.Key_Escape: - self.close() - else: - super(Window, self).keyPressEvent(event) - -class Logo(): - def __init__(self): - self.m_count = 0 - self.i = 0 - self.m_data = numpy.empty(2500 * 6, dtype = ctypes.c_float) - - 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) - - Pi = 3.14159265358979323846 - NumSectors = 100 - - for i in range(NumSectors): - angle = (i * 2 * Pi) / NumSectors - x5 = 0.30 * math.sin(angle) - y5 = 0.30 * math.cos(angle) - x6 = 0.20 * math.sin(angle) - y6 = 0.20 * math.cos(angle) - - angle = ((i + 1) * 2 * Pi) / NumSectors - x7 = 0.20 * math.sin(angle) - y7 = 0.20 * math.cos(angle) - x8 = 0.30 * math.sin(angle) - y8 = 0.30 * math.cos(angle) - - self.quad(x5, y5, x6, y6, x7, y7, x8, y8) - - self.extrude(x6, y6, x7, y7) - self.extrude(x8, y8, x5, y5) - - def constData(self): - return self.m_data.tobytes() - - def count(self): - return self.m_count - - def vertexCount(self): - return self.m_count / 6 - - def quad(self, x1, y1, x2, y2, x3, y3, x4, y4): - n = QVector3D.normal(QVector3D(x4 - x1, y4 - y1, 0), QVector3D(x2 - x1, y2 - y1, 0)) - - self.add(QVector3D(x1, y1, -0.05), n) - self.add(QVector3D(x4, y4, -0.05), n) - self.add(QVector3D(x2, y2, -0.05), n) - - self.add(QVector3D(x3, y3, -0.05), n) - self.add(QVector3D(x2, y2, -0.05), n) - self.add(QVector3D(x4, y4, -0.05), n) - - n = QVector3D.normal(QVector3D(x1 - x4, y1 - y4, 0), QVector3D(x2 - x4, y2 - y4, 0)) - - self.add(QVector3D(x4, y4, 0.05), n) - self.add(QVector3D(x1, y1, 0.05), n) - self.add(QVector3D(x2, y2, 0.05), n) - - self.add(QVector3D(x2, y2, 0.05), n) - self.add(QVector3D(x3, y3, 0.05), n) - self.add(QVector3D(x4, y4, 0.05), n) - - def extrude(self, x1, y1, x2, y2): - n = QVector3D.normal(QVector3D(0, 0, -0.1), QVector3D(x2 - x1, y2 - y1, 0)) - - self.add(QVector3D(x1, y1, 0.05), n) - self.add(QVector3D(x1, y1, -0.05), n) - self.add(QVector3D(x2, y2, 0.05), n) - - self.add(QVector3D(x2, y2, -0.05), n) - self.add(QVector3D(x2, y2, 0.05), n) - self.add(QVector3D(x1, y1, -0.05), n) - - def add(self, v, n): - self.m_data[self.i] = v.x() - self.i += 1 - self.m_data[self.i] = v.y() - self.i += 1 - self.m_data[self.i] = v.z() - self.i += 1 - self.m_data[self.i] = n.x() - self.i += 1 - self.m_data[self.i] = n.y() - self.i += 1 - self.m_data[self.i] = n.z() - self.i += 1 - self.m_count += 6 - -class GLWidget(QOpenGLWidget, QOpenGLFunctions): - xRotationChanged = Signal(int) - yRotationChanged = Signal(int) - zRotationChanged = Signal(int) - - def __init__(self, parent=None): - QOpenGLWidget.__init__(self, parent) - QOpenGLFunctions.__init__(self) - - self.core = "--coreprofile" in QCoreApplication.arguments() - self.xRot = 0 - self.yRot = 0 - self.zRot = 0 - self.lastPos = 0 - self.logo = Logo() - self.vao = QOpenGLVertexArrayObject() - self.logoVbo = QOpenGLBuffer() - self.program = QOpenGLShaderProgram() - self.projMatrixLoc = 0 - self.mvMatrixLoc = 0 - self.normalMatrixLoc = 0 - self.lightPosLoc = 0 - self.proj = QMatrix4x4() - self.camera = QMatrix4x4() - self.world = QMatrix4x4() - self.transparent = "--transparent" in QCoreApplication.arguments() - if self.transparent: - fmt = self.format() - fmt.setAlphaBufferSize(8) - self.setFormat(fmt) - - def xRotation(self): - return self.xRot - - def yRotation(self): - return self.yRot - - def zRotation(self): - return self.zRot - - def minimumSizeHint(self): - return QSize(50, 50) - - def sizeHint(self): - return QSize(400, 400) - - def normalizeAngle(self, angle): - while angle < 0: - angle += 360 * 16 - while angle > 360 * 16: - angle -= 360 * 16 - return angle - - def setXRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.xRot: - self.xRot = angle - self.emit(SIGNAL("xRotationChanged(int)"), angle) - self.update() - - def setYRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.yRot: - self.yRot = angle - self.emit(SIGNAL("yRotationChanged(int)"), angle) - self.update() - - def setZRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.zRot: - self.zRot = angle - self.emit(SIGNAL("zRotationChanged(int)"), angle) - self.update() - - def cleanup(self): - self.makeCurrent() - self.logoVbo.destroy() - del self.program - self.program = None - self.doneCurrent() - - def vertexShaderSourceCore(self): - return """#version 150 - in vec4 vertex; - in vec3 normal; - out vec3 vert; - out vec3 vertNormal; - uniform mat4 projMatrix; - uniform mat4 mvMatrix; - uniform mat3 normalMatrix; - void main() { - vert = vertex.xyz; - vertNormal = normalMatrix * normal; - gl_Position = projMatrix * mvMatrix * vertex; - }""" - - def fragmentShaderSourceCore(self): - return """#version 150 - in highp vec3 vert; - in highp vec3 vertNormal; - out highp vec4 fragColor; - uniform highp vec3 lightPos; - void main() { - highp vec3 L = normalize(lightPos - vert); - highp float NL = max(dot(normalize(vertNormal), L), 0.0); - highp vec3 color = vec3(0.39, 1.0, 0.0); - highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); - fragColor = vec4(col, 1.0); - }""" - - - def vertexShaderSource(self): - return """attribute vec4 vertex; - attribute vec3 normal; - varying vec3 vert; - varying vec3 vertNormal; - uniform mat4 projMatrix; - uniform mat4 mvMatrix; - uniform mat3 normalMatrix; - void main() { - vert = vertex.xyz; - vertNormal = normalMatrix * normal; - gl_Position = projMatrix * mvMatrix * vertex; - }""" - - def fragmentShaderSource(self): - return """varying highp vec3 vert; - varying highp vec3 vertNormal; - uniform highp vec3 lightPos; - void main() { - highp vec3 L = normalize(lightPos - vert); - highp float NL = max(dot(normalize(vertNormal), L), 0.0); - highp vec3 color = vec3(0.39, 1.0, 0.0); - highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); - gl_FragColor = vec4(col, 1.0); - }""" - - def initializeGL(self): - self.context().aboutToBeDestroyed.connect(self.cleanup) - self.initializeOpenGLFunctions() - self.glClearColor(0, 0, 0, 1) - - self.program = QOpenGLShaderProgram() - - if self.core: - self.vertexShader = self.vertexShaderSourceCore() - self.fragmentShader = self.fragmentShaderSourceCore() - else: - self.vertexShader = self.vertexShaderSource() - self.fragmentShader = self.fragmentShaderSource() - - self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, self.vertexShader) - self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, self.fragmentShader) - self.program.bindAttributeLocation("vertex", 0) - self.program.bindAttributeLocation("normal", 1) - self.program.link() - - self.program.bind() - self.projMatrixLoc = self.program.uniformLocation("projMatrix") - self.mvMatrixLoc = self.program.uniformLocation("mvMatrix") - self.normalMatrixLoc = self.program.uniformLocation("normalMatrix") - self.lightPosLoc = self.program.uniformLocation("lightPos") - - self.vao.create() - vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) - - self.logoVbo.create() - self.logoVbo.bind() - float_size = ctypes.sizeof(ctypes.c_float) - self.logoVbo.allocate(self.logo.constData(), self.logo.count() * float_size) - - self.setupVertexAttribs() - - self.camera.setToIdentity() - self.camera.translate(0, 0, -1) - - self.program.setUniformValue(self.lightPosLoc, QVector3D(0, 0, 70)) - self.program.release() - vaoBinder = None - - def setupVertexAttribs(self): - self.logoVbo.bind() - f = QOpenGLContext.currentContext().functions() - f.glEnableVertexAttribArray(0) - f.glEnableVertexAttribArray(1) - float_size = ctypes.sizeof(ctypes.c_float) - - null = VoidPtr(0) - pointer = VoidPtr(3 * float_size) - f.glVertexAttribPointer(0, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, null) - f.glVertexAttribPointer(1, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, pointer) - self.logoVbo.release() - - def paintGL(self): - self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) - self.glEnable(GL.GL_DEPTH_TEST) - self.glEnable(GL.GL_CULL_FACE) - - self.world.setToIdentity() - self.world.rotate(180 - (self.xRot / 16), 1, 0, 0) - self.world.rotate(self.yRot / 16, 0, 1, 0) - self.world.rotate(self.zRot / 16, 0, 0, 1) - - vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) - self.program.bind() - self.program.setUniformValue(self.projMatrixLoc, self.proj) - self.program.setUniformValue(self.mvMatrixLoc, self.camera * self.world) - normalMatrix = self.world.normalMatrix() - self.program.setUniformValue(self.normalMatrixLoc, normalMatrix) - - self.glDrawArrays(GL.GL_TRIANGLES, 0, self.logo.vertexCount()) - self.program.release() - vaoBinder = None - - def resizeGL(self, width, height): - self.proj.setToIdentity() - self.proj.perspective(45, width / height, 0.01, 100) - - def mousePressEvent(self, event): - self.lastPos = QPoint(event.pos()) - - def mouseMoveEvent(self, event): - dx = event.x() - self.lastPos.x() - dy = event.y() - self.lastPos.y() - - if event.buttons() & Qt.LeftButton: - self.setXRotation(self.xRot + 8 * dy) - self.setYRotation(self.yRot + 8 * dx) - elif event.buttons() & Qt.RightButton: - self.setXRotation(self.xRot + 8 * dy) - self.setZRotation(self.zRot + 8 * dx) - - self.lastPos = QPoint(event.pos()) - -if __name__ == '__main__': - app = QApplication(sys.argv) - - fmt = QSurfaceFormat() - fmt.setDepthBufferSize(24) - if "--multisample" in QCoreApplication.arguments(): - fmt.setSamples(4) - if "--coreprofile" in QCoreApplication.arguments(): - fmt.setVersion(3, 2) - fmt.setProfile(QSurfaceFormat.CoreProfile) - QSurfaceFormat.setDefaultFormat(fmt) - - mainWindow = Window() - if "--transparent" in QCoreApplication.arguments(): - mainWindow.setAttribute(Qt.WA_TranslucentBackground) - mainWindow.setAttribute(Qt.WA_NoSystemBackground, False) - - mainWindow.resize(mainWindow.sizeHint()) - mainWindow.show() - - res = app.exec_() - sys.exit(res) diff --git a/examples/opengl/hellogl2/doc/hellogl2.png b/examples/opengl/hellogl2/doc/hellogl2.png Binary files differnew file mode 100644 index 000000000..674ea9fbe --- /dev/null +++ b/examples/opengl/hellogl2/doc/hellogl2.png diff --git a/examples/opengl/hellogl2/doc/hellogl2.rst b/examples/opengl/hellogl2/doc/hellogl2.rst new file mode 100644 index 000000000..3471ebf30 --- /dev/null +++ b/examples/opengl/hellogl2/doc/hellogl2.rst @@ -0,0 +1,23 @@ +Hello GL2 Example +================= + +The Hello GL2 example demonstrates the basic use of the OpenGL-related classes +provided with Qt. + +In this example the widget's corresponding top-level window can change several +times during the widget's lifetime. Whenever this happens, the QOpenGLWidget's +associated context is destroyed and a new one is created, requiring us to clean +up the GL resources. + +The equivalent C++ example does this cleanup on emission of the +QOpenGLContext.aboutToBeDestroyed() signal. However, in Qt for Python, we +cannot rely on this signal when it is emitted from the destructor. + +Therefore, we do the cleanup in GLWidget.hideEvent(). + +This will be followed by an invocation of initializeGL() where we can recreate +all resources. + +.. image:: hellogl2.png + :width: 400 + :alt: Hello GL2 Screenshot diff --git a/examples/opengl/hellogl2/glwidget.py b/examples/opengl/hellogl2/glwidget.py new file mode 100644 index 000000000..bbf200a6b --- /dev/null +++ b/examples/opengl/hellogl2/glwidget.py @@ -0,0 +1,272 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# Copyright (C) 2013 Riverbank Computing Limited. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import ctypes +from PySide6.QtCore import Signal, Slot, Qt, QSize, QPointF +from PySide6.QtGui import (QVector3D, QOpenGLFunctions, + QMatrix4x4, QOpenGLContext, QSurfaceFormat) +from PySide6.QtOpenGL import (QOpenGLVertexArrayObject, QOpenGLBuffer, + QOpenGLShaderProgram, QOpenGLShader) +from PySide6.QtOpenGLWidgets import QOpenGLWidget + +from OpenGL import GL + +from shiboken6 import VoidPtr +from logo import Logo + +FRAGMENT_SHADER_SOURCE_CORE = """#version 150 +in highp vec3 vert; +in highp vec3 vertNormal; +out highp vec4 fragColor; +uniform highp vec3 lightPos; +void main() { + highp vec3 L = normalize(lightPos - vert); + highp float NL = max(dot(normalize(vertNormal), L), 0.0); + highp vec3 color = vec3(0.39, 1.0, 0.0); + highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); + fragColor = vec4(col, 1.0); +}""" + + +FRAGMENT_SHADER_SOURCE = """varying highp vec3 vert; +varying highp vec3 vertNormal; +uniform highp vec3 lightPos; +void main() { + highp vec3 L = normalize(lightPos - vert); + highp float NL = max(dot(normalize(vertNormal), L), 0.0); + highp vec3 color = vec3(0.39, 1.0, 0.0); + highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); + gl_FragColor = vec4(col, 1.0); +}""" + + +VERTEX_SHADER_SOURCE_CORE = """#version 150 +in vec4 vertex; +in vec3 normal; +out vec3 vert; +out vec3 vertNormal; +uniform mat4 projMatrix; +uniform mat4 mvMatrix; +uniform mat3 normalMatrix; +void main() { + vert = vertex.xyz; + vertNormal = normalMatrix * normal; + gl_Position = projMatrix * mvMatrix * vertex; +}""" + + +VERTEX_SHADER_SOURCE = """attribute vec4 vertex; +attribute vec3 normal; +varying vec3 vert; +varying vec3 vertNormal; +uniform mat4 projMatrix; +uniform mat4 mvMatrix; +uniform mat3 normalMatrix; +void main() { + vert = vertex.xyz; + vertNormal = normalMatrix * normal; + gl_Position = projMatrix * mvMatrix * vertex; +}""" + + +class GLWidget(QOpenGLWidget, QOpenGLFunctions): + x_rotation_changed = Signal(int) + y_rotation_changed = Signal(int) + z_rotation_changed = Signal(int) + + _transparent = False + + def __init__(self, parent=None): + QOpenGLWidget.__init__(self, parent) + QOpenGLFunctions.__init__(self) + + self._core = QSurfaceFormat.defaultFormat().profile() == QSurfaceFormat.CoreProfile + + self._x_rot = 0 + self._y_rot = 0 + self._z_rot = 0 + self._last_pos = QPointF() + self.logo = Logo() + self.vao = QOpenGLVertexArrayObject() + self._logo_vbo = QOpenGLBuffer() + self.program = QOpenGLShaderProgram() + self._proj_matrix_loc = 0 + self._mv_matrix_loc = 0 + self._normal_matrix_loc = 0 + self._light_pos_loc = 0 + self.proj = QMatrix4x4() + self.camera = QMatrix4x4() + self.world = QMatrix4x4() + if self._transparent: + fmt = self.format() + fmt.setAlphaBufferSize(8) + self.setFormat(fmt) + + @staticmethod + def set_transparent(t): + GLWidget._transparent = t + + @staticmethod + def is_transparent(): + return GLWidget._transparent + + def x_rotation(self): + return self._x_rot + + def y_rotation(self): + return self._y_rot + + def z_rotation(self): + return self._z_rot + + def minimumSizeHint(self): + return QSize(50, 50) + + def sizeHint(self): + return QSize(400, 400) + + def normalize_angle(self, angle): + while angle < 0: + angle += 360 * 16 + while angle > 360 * 16: + angle -= 360 * 16 + return angle + + @Slot(int) + def set_xrotation(self, angle): + angle = self.normalize_angle(angle) + if angle != self._x_rot: + self._x_rot = angle + self.x_rotation_changed.emit(angle) + self.update() + + @Slot(int) + def set_yrotation(self, angle): + angle = self.normalize_angle(angle) + if angle != self._y_rot: + self._y_rot = angle + self.y_rotation_changed.emit(angle) + self.update() + + @Slot(int) + def set_zrotation(self, angle): + angle = self.normalize_angle(angle) + if angle != self._z_rot: + self._z_rot = angle + self.z_rotation_changed.emit(angle) + self.update() + + @Slot() + def cleanup(self): + if self.program: + self.makeCurrent() + self._logo_vbo.destroy() + del self.program + self.program = None + self.doneCurrent() + + def initializeGL(self): + self.initializeOpenGLFunctions() + self.glClearColor(0, 0, 0, 0 if self._transparent else 1) + + self.program = QOpenGLShaderProgram() + + if self._core: + self._vertex_shader = VERTEX_SHADER_SOURCE_CORE + self._fragment_shader = FRAGMENT_SHADER_SOURCE_CORE + else: + self._vertex_shader = VERTEX_SHADER_SOURCE + self._fragment_shader = FRAGMENT_SHADER_SOURCE + + self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, + self._vertex_shader) + self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, + self._fragment_shader) + self.program.bindAttributeLocation("vertex", 0) + self.program.bindAttributeLocation("normal", 1) + self.program.link() + + self.program.bind() + self._proj_matrix_loc = self.program.uniformLocation("projMatrix") + self._mv_matrix_loc = self.program.uniformLocation("mvMatrix") + self._normal_matrix_loc = self.program.uniformLocation("normalMatrix") + self._light_pos_loc = self.program.uniformLocation("lightPos") + + self.vao.create() + with QOpenGLVertexArrayObject.Binder(self.vao): + self._logo_vbo.create() + self._logo_vbo.bind() + float_size = ctypes.sizeof(ctypes.c_float) + self._logo_vbo.allocate(self.logo.const_data(), + self.logo.count() * float_size) + + self.setup_vertex_attribs() + + self.camera.setToIdentity() + self.camera.translate(0, 0, -1) + + self.program.setUniformValue(self._light_pos_loc, + QVector3D(0, 0, 70)) + self.program.release() + + def setup_vertex_attribs(self): + self._logo_vbo.bind() + f = QOpenGLContext.currentContext().functions() + f.glEnableVertexAttribArray(0) + f.glEnableVertexAttribArray(1) + float_size = ctypes.sizeof(ctypes.c_float) + + null = VoidPtr(0) + pointer = VoidPtr(3 * float_size) + f.glVertexAttribPointer(0, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), + 6 * float_size, null) + f.glVertexAttribPointer(1, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), + 6 * float_size, pointer) + self._logo_vbo.release() + + def paintGL(self): + self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) + self.glEnable(GL.GL_DEPTH_TEST) + self.glEnable(GL.GL_CULL_FACE) + + self.world.setToIdentity() + self.world.rotate(180 - (self._x_rot / 16), 1, 0, 0) + self.world.rotate(self._y_rot / 16, 0, 1, 0) + self.world.rotate(self._z_rot / 16, 0, 0, 1) + + with QOpenGLVertexArrayObject.Binder(self.vao): + self.program.bind() + self.program.setUniformValue(self._proj_matrix_loc, self.proj) + self.program.setUniformValue(self._mv_matrix_loc, + self.camera * self.world) + normal_matrix = self.world.normalMatrix() + self.program.setUniformValue(self._normal_matrix_loc, normal_matrix) + + self.glDrawArrays(GL.GL_TRIANGLES, 0, self.logo.vertex_count()) + self.program.release() + + def resizeGL(self, width, height): + self.proj.setToIdentity() + self.proj.perspective(45, width / height, 0.01, 100) + + def hideEvent(self, event): + self.cleanup() + super().hideEvent(event) + + def mousePressEvent(self, event): + self._last_pos = event.position() + + def mouseMoveEvent(self, event): + pos = event.position() + dx = pos.x() - self._last_pos.x() + dy = pos.y() - self._last_pos.y() + + if event.buttons() & Qt.LeftButton: + self.set_xrotation(self._x_rot + 8 * dy) + self.set_yrotation(self._y_rot + 8 * dx) + elif event.buttons() & Qt.RightButton: + self.set_xrotation(self._x_rot + 8 * dy) + self.set_zrotation(self._z_rot + 8 * dx) + + self._last_pos = pos diff --git a/examples/opengl/hellogl2/hellogl2.pyproject b/examples/opengl/hellogl2/hellogl2.pyproject new file mode 100644 index 000000000..d85a139e4 --- /dev/null +++ b/examples/opengl/hellogl2/hellogl2.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py", "glwidget.py", "logo.py", "mainwindow.py", "window.py"] +} diff --git a/examples/opengl/hellogl2/logo.py b/examples/opengl/hellogl2/logo.py new file mode 100644 index 000000000..c236a1ec9 --- /dev/null +++ b/examples/opengl/hellogl2/logo.py @@ -0,0 +1,101 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# Copyright (C) 2013 Riverbank Computing Limited. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import math + +from PySide6.QtGui import (QVector3D, QVector3DList) + + +class Logo(): + def __init__(self): + self.m_data = QVector3DList() + self.m_data.reserve(5000) + + 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 + + for i in range(NUM_SECTORS): + angle = (i * 2 * math.pi) / NUM_SECTORS + x5 = 0.30 * math.sin(angle) + y5 = 0.30 * math.cos(angle) + x6 = 0.20 * math.sin(angle) + y6 = 0.20 * math.cos(angle) + + angle = ((i + 1) * 2 * math.pi) / NUM_SECTORS + x7 = 0.20 * math.sin(angle) + y7 = 0.20 * math.cos(angle) + x8 = 0.30 * math.sin(angle) + y8 = 0.30 * math.cos(angle) + + self.quad(x5, y5, x6, y6, x7, y7, x8, y8) + + self.extrude(x6, y6, x7, y7) + self.extrude(x8, y8, x5, y5) + + def const_data(self): + return self.m_data.constData() + + def count(self): + return len(self.m_data) * 3 + + def vertex_count(self): + return self.count() / 6 + + def quad(self, x1, y1, x2, y2, x3, y3, x4, y4): + n = QVector3D.normal(QVector3D(x4 - x1, y4 - y1, 0), + QVector3D(x2 - x1, y2 - y1, 0)) + + self.add(QVector3D(x1, y1, -0.05), n) + self.add(QVector3D(x4, y4, -0.05), n) + self.add(QVector3D(x2, y2, -0.05), n) + + self.add(QVector3D(x3, y3, -0.05), n) + self.add(QVector3D(x2, y2, -0.05), n) + self.add(QVector3D(x4, y4, -0.05), n) + + n = QVector3D.normal(QVector3D(x1 - x4, y1 - y4, 0), + QVector3D(x2 - x4, y2 - y4, 0)) + + self.add(QVector3D(x4, y4, 0.05), n) + self.add(QVector3D(x1, y1, 0.05), n) + self.add(QVector3D(x2, y2, 0.05), n) + + self.add(QVector3D(x2, y2, 0.05), n) + self.add(QVector3D(x3, y3, 0.05), n) + self.add(QVector3D(x4, y4, 0.05), n) + + def extrude(self, x1, y1, x2, y2): + n = QVector3D.normal(QVector3D(0, 0, -0.1), + QVector3D(x2 - x1, y2 - y1, 0)) + + self.add(QVector3D(x1, y1, 0.05), n) + self.add(QVector3D(x1, y1, -0.05), n) + self.add(QVector3D(x2, y2, 0.05), n) + + self.add(QVector3D(x2, y2, -0.05), n) + self.add(QVector3D(x2, y2, 0.05), n) + self.add(QVector3D(x1, y1, -0.05), n) + + def add(self, v, n): + self.m_data.append(v) + self.m_data.append(n) diff --git a/examples/opengl/hellogl2/main.py b/examples/opengl/hellogl2/main.py new file mode 100644 index 000000000..c7eb78a82 --- /dev/null +++ b/examples/opengl/hellogl2/main.py @@ -0,0 +1,58 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# Copyright (C) 2013 Riverbank Computing Limited. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the opengl/hellogl2 example from Qt v6.x""" + +from argparse import ArgumentParser, RawTextHelpFormatter +import sys +from PySide6.QtCore import Qt +from PySide6.QtGui import QSurfaceFormat +from PySide6.QtWidgets import (QApplication, QMessageBox) + + +try: + from mainwindow import MainWindow + from glwidget import GLWidget +except ImportError: + app = QApplication(sys.argv) + message_box = QMessageBox(QMessageBox.Critical, "OpenGL hellogl", + "PyOpenGL must be installed to run this example.", + QMessageBox.Close) + message_box.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") + message_box.exec() + sys.exit(1) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + parser = ArgumentParser(description="hellogl2", + formatter_class=RawTextHelpFormatter) + parser.add_argument('--multisample', '-m', action='store_true', + help='Use Multisampling') + parser.add_argument('--coreprofile', '-c', action='store_true', + help='Use Core Profile') + parser.add_argument('--transparent', '-t', action='store_true', + help='Transparent Windows') + options = parser.parse_args() + + fmt = QSurfaceFormat() + fmt.setDepthBufferSize(24) + if options.multisample: + fmt.setSamples(4) + if options.coreprofile: + fmt.setVersion(3, 2) + fmt.setProfile(QSurfaceFormat.CoreProfile) + QSurfaceFormat.setDefaultFormat(fmt) + + GLWidget.set_transparent(options.transparent) + + main_window = MainWindow() + if options.transparent: + main_window.setAttribute(Qt.WA_TranslucentBackground) + main_window.setAttribute(Qt.WA_NoSystemBackground, False) + + main_window.show() + + res = app.exec() + sys.exit(res) diff --git a/examples/opengl/hellogl2/mainwindow.py b/examples/opengl/hellogl2/mainwindow.py new file mode 100644 index 000000000..69b9b66fe --- /dev/null +++ b/examples/opengl/hellogl2/mainwindow.py @@ -0,0 +1,29 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot, Qt +from PySide6.QtGui import QKeySequence +from PySide6.QtWidgets import QMainWindow, QMessageBox + +from window import Window + + +class MainWindow(QMainWindow): + + def __init__(self): + super().__init__() + menuWindow = self.menuBar().addMenu("Window") + menuWindow.addAction("Add new", QKeySequence(Qt.CTRL | Qt.Key_N), + self.onAddNew) + menuWindow.addAction("Quit", QKeySequence(Qt.CTRL | Qt.Key_Q), + qApp.closeAllWindows) # noqa: F821 + + self.onAddNew() + + @Slot() + def onAddNew(self): + if not self.centralWidget(): + self.setCentralWidget(Window(self)) + else: + QMessageBox.information(self, "Cannot Add Window()", + "Already occupied. Undock first.") diff --git a/examples/opengl/hellogl2/window.py b/examples/opengl/hellogl2/window.py new file mode 100644 index 000000000..ad61d2f97 --- /dev/null +++ b/examples/opengl/hellogl2/window.py @@ -0,0 +1,110 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# Copyright (C) 2013 Riverbank Computing Limited. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot, Qt +from PySide6.QtWidgets import (QHBoxLayout, QMainWindow, + QMessageBox, QPushButton, QSlider, + QVBoxLayout, QWidget) + +from glwidget import GLWidget + + +def _main_window(): + for t in qApp.topLevelWidgets(): # noqa: F821 + if isinstance(t, QMainWindow): + return t + return None + + +class Window(QWidget): + instances = [] # Keep references when undocked + + def __init__(self, parent=None): + super().__init__(parent) + self.instances.append(self) + + self._gl_widget = GLWidget() + + self._x_slider = self.create_slider() + self._x_slider.valueChanged.connect(self._gl_widget.set_xrotation) + self._gl_widget.x_rotation_changed.connect(self._x_slider.setValue) + + self._y_slider = self.create_slider() + self._y_slider.valueChanged.connect(self._gl_widget.set_yrotation) + self._gl_widget.y_rotation_changed.connect(self._y_slider.setValue) + + self._z_slider = self.create_slider() + self._z_slider.valueChanged.connect(self._gl_widget.set_zrotation) + self._gl_widget.z_rotation_changed.connect(self._z_slider.setValue) + + mainLayout = QVBoxLayout(self) + w = QWidget() + container = QHBoxLayout(w) + container.addWidget(self._gl_widget) + container.addWidget(self._x_slider) + container.addWidget(self._y_slider) + container.addWidget(self._z_slider) + + mainLayout.addWidget(w) + self._dock_btn = QPushButton("Undock") + self._dock_btn.clicked.connect(self.dock_undock) + mainLayout.addWidget(self._dock_btn) + + self._x_slider.setValue(15 * 16) + self._y_slider.setValue(345 * 16) + self._z_slider.setValue(0 * 16) + + self.setWindowTitle(self.tr("Hello GL")) + + def create_slider(self): + slider = QSlider(Qt.Vertical) + + slider.setRange(0, 360 * 16) + slider.setSingleStep(16) + slider.setPageStep(15 * 16) + slider.setTickInterval(15 * 16) + slider.setTickPosition(QSlider.TicksRight) + return slider + + def closeEvent(self, event): + self.instances.remove(self) + event.accept() + + def keyPressEvent(self, event): + if self.isWindow() and event.key() == Qt.Key_Escape: + self.close() + else: + super().keyPressEvent(event) + + @Slot() + def dock_undock(self): + if self.parent(): + self.undock() + else: + self.dock() + + def dock(self): + mainWindow = _main_window() + if not mainWindow or not mainWindow.isVisible(): + QMessageBox.information(self, "Cannot Dock", + "Main window already closed") + return + if mainWindow.centralWidget(): + QMessageBox.information(self, "Cannot Dock", + "Main window already occupied") + return + + self.setAttribute(Qt.WA_DeleteOnClose, False) + self._dock_btn.setText("Undock") + mainWindow.setCentralWidget(self) + + def undock(self): + self.setParent(None) + self.setAttribute(Qt.WA_DeleteOnClose) + geometry = self.screen().availableGeometry() + x = geometry.x() + (geometry.width() - self.width()) / 2 + y = geometry.y() + (geometry.height() - self.height()) / 2 + self.move(x, y) + self._dock_btn.setText("Dock") + self.show() diff --git a/examples/opengl/overpainting.py b/examples/opengl/overpainting.py deleted file mode 100644 index 786337f88..000000000 --- a/examples/opengl/overpainting.py +++ /dev/null @@ -1,385 +0,0 @@ - -############################################################################ -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################ - -"""PySide2 port of the opengl/legacy/overpainting example from Qt v5.x""" - -import sys -import math, random -from PySide2.QtCore import * -from PySide2.QtGui import * -from PySide2.QtWidgets import * -from PySide2.QtOpenGL import * - -try: - from OpenGL.GL import * -except ImportError: - app = QApplication(sys.argv) - messageBox = QMessageBox(QMessageBox.Critical, "OpenGL overpainting", - "PyOpenGL must be installed to run this example.", - QMessageBox.Close) - messageBox.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - messageBox.exec_() - sys.exit(1) - - -class Bubble: - def __init__(self, position, radius, velocity): - self.position = position - self.vel = velocity - self.radius = radius - self.innerColor = self.randomColor() - self.outerColor = self.randomColor() - self.updateBrush() - - def updateBrush(self): - gradient = QRadialGradient(QPointF(self.radius, self.radius), self.radius, - QPointF(self.radius*0.5, self.radius*0.5)) - - gradient.setColorAt(0, QColor(255, 255, 255, 255)) - gradient.setColorAt(0.25, self.innerColor) - gradient.setColorAt(1, self.outerColor) - self.brush = QBrush(gradient) - - def drawBubble(self, painter): - painter.save() - painter.translate(self.position.x() - self.radius, - self.position.y() - self.radius) - painter.setBrush(self.brush) - painter.drawEllipse(0, 0, int(2*self.radius), int(2*self.radius)) - painter.restore() - - def randomColor(self): - red = random.randrange(205, 256) - green = random.randrange(205, 256) - blue = random.randrange(205, 256) - alpha = random.randrange(91, 192) - - return QColor(red, green, blue, alpha) - - def move(self, bbox): - self.position += self.vel - leftOverflow = self.position.x() - self.radius - bbox.left() - rightOverflow = self.position.x() + self.radius - bbox.right() - topOverflow = self.position.y() - self.radius - bbox.top() - bottomOverflow = self.position.y() + self.radius - bbox.bottom() - - if leftOverflow < 0.0: - self.position.setX(self.position.x() - 2 * leftOverflow) - self.vel.setX(-self.vel.x()) - elif rightOverflow > 0.0: - self.position.setX(self.position.x() - 2 * rightOverflow) - self.vel.setX(-self.vel.x()) - - if topOverflow < 0.0: - self.position.setY(self.position.y() - 2 * topOverflow) - self.vel.setY(-self.vel.y()) - elif bottomOverflow > 0.0: - self.position.setY(self.position.y() - 2 * bottomOverflow) - self.vel.setY(-self.vel.y()) - - def rect(self): - return QRectF(self.position.x() - self.radius, - self.position.y() - self.radius, - 2 * self.radius, 2 * self.radius) - - -class GLWidget(QGLWidget): - def __init__(self, parent = None): - QGLWidget.__init__(self, QGLFormat(QGL.SampleBuffers), parent) - - midnight = QTime(0, 0, 0) - random.seed(midnight.secsTo(QTime.currentTime())) - - self.object = 0 - self.xRot = 0 - self.yRot = 0 - self.zRot = 0 - self.image = QImage() - self.bubbles = [] - self.lastPos = QPoint() - - self.trolltechGreen = QColor.fromCmykF(0.40, 0.0, 1.0, 0.0) - self.trolltechPurple = QColor.fromCmykF(0.39, 0.39, 0.0, 0.0) - - self.animationTimer = QTimer() - self.animationTimer.setSingleShot(False) - self.connect(self.animationTimer, SIGNAL("timeout()"), self.animate) - self.animationTimer.start(25) - - self.setAttribute(Qt.WA_NoSystemBackground) - self.setMinimumSize(200, 200) - self.setWindowTitle(self.tr("Overpainting a Scene")) - - def freeResources(self): - self.makeCurrent() - glDeleteLists(self.object, 1) - - def setXRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.xRot: - self.xRot = angle - self.emit(SIGNAL("xRotationChanged(int)"), angle) - - def setYRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.yRot: - self.yRot = angle - self.emit(SIGNAL("yRotationChanged(int)"), angle) - - def setZRotation(self, angle): - angle = self.normalizeAngle(angle) - if angle != self.zRot: - self.zRot = angle - self.emit(SIGNAL("zRotationChanged(int)"), angle) - - def initializeGL(self): - self.object = self.makeObject() - - def mousePressEvent(self, event): - self.lastPos = QPoint(event.pos()) - - def mouseMoveEvent(self, event): - dx = event.x() - self.lastPos.x() - dy = event.y() - self.lastPos.y() - - if event.buttons() & Qt.LeftButton: - self.setXRotation(self.xRot + 8 * dy) - self.setYRotation(self.yRot + 8 * dx) - elif event.buttons() & Qt.RightButton: - self.setXRotation(self.xRot + 8 * dy) - self.setZRotation(self.zRot + 8 * dx) - - self.lastPos = QPoint(event.pos()) - - def paintEvent(self, event): - painter = QPainter() - painter.begin(self) - painter.setRenderHint(QPainter.Antialiasing) - - glPushAttrib(GL_ALL_ATTRIB_BITS) - glMatrixMode(GL_PROJECTION) - glPushMatrix() - glMatrixMode(GL_MODELVIEW) - glPushMatrix() - - self.qglClearColor(self.trolltechPurple.darker()) - glShadeModel(GL_SMOOTH) - glEnable(GL_DEPTH_TEST) - glEnable(GL_CULL_FACE) - glEnable(GL_LIGHTING) - glEnable(GL_LIGHT0) - lightPosition = ( 0.5, 5.0, 7.0, 1.0 ) - glLightfv(GL_LIGHT0, GL_POSITION, lightPosition) - - self.resizeGL(self.width(), self.height()) - - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glLoadIdentity() - glTranslated(0.0, 0.0, -10.0) - glRotated(self.xRot / 16.0, 1.0, 0.0, 0.0) - glRotated(self.yRot / 16.0, 0.0, 1.0, 0.0) - glRotated(self.zRot / 16.0, 0.0, 0.0, 1.0) - glCallList(self.object) - - glPopAttrib() - glMatrixMode(GL_MODELVIEW) - glPopMatrix() - glMatrixMode(GL_PROJECTION) - glPopMatrix() - - glDisable(GL_CULL_FACE) ### not required if begin() also does it - - for bubble in self.bubbles: - if bubble.rect().intersects(QRectF(event.rect())): - bubble.drawBubble(painter) - - painter.drawImage((self.width() - self.image.width())/2, 0, self.image) - painter.end() - - def resizeGL(self, width, height): - side = min(width, height) - glViewport(int((width - side) / 2), int((height - side) / 2), side, side) - - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0) - glMatrixMode(GL_MODELVIEW) - - self.formatInstructions(width, height) - - def showEvent(self, event): - self.createBubbles(20 - len(self.bubbles)) - - def sizeHint(self): - return QSize(400, 400) - - def makeObject(self): - list = glGenLists(1) - glNewList(list, GL_COMPILE) - - glEnable(GL_NORMALIZE) - glBegin(GL_QUADS) - - logoDiffuseColor = (self.trolltechGreen.red()/255.0, - self.trolltechGreen.green()/255.0, - self.trolltechGreen.blue()/255.0, 1.0) - glMaterialfv(GL_FRONT, GL_DIFFUSE, logoDiffuseColor) - - 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) - - NumSectors = 200 - - for i in range(NumSectors): - angle1 = (i * 2 * math.pi) / NumSectors - x5 = 0.30 * math.sin(angle1) - y5 = 0.30 * math.cos(angle1) - x6 = 0.20 * math.sin(angle1) - y6 = 0.20 * math.cos(angle1) - - angle2 = ((i + 1) * 2 * math.pi) / NumSectors - x7 = 0.20 * math.sin(angle2) - y7 = 0.20 * math.cos(angle2) - x8 = 0.30 * math.sin(angle2) - y8 = 0.30 * math.cos(angle2) - - self.quad(x5, y5, x6, y6, x7, y7, x8, y8) - - self.extrude(x6, y6, x7, y7) - self.extrude(x8, y8, x5, y5) - - glEnd() - - glEndList() - return list - - def quad(self, x1, y1, x2, y2, x3, y3, x4, y4): - glNormal3d(0.0, 0.0, -1.0) - glVertex3d(x1, y1, -0.05) - glVertex3d(x2, y2, -0.05) - glVertex3d(x3, y3, -0.05) - glVertex3d(x4, y4, -0.05) - - glNormal3d(0.0, 0.0, 1.0) - glVertex3d(x4, y4, +0.05) - glVertex3d(x3, y3, +0.05) - glVertex3d(x2, y2, +0.05) - glVertex3d(x1, y1, +0.05) - - def extrude(self, x1, y1, x2, y2): - self.qglColor(self.trolltechGreen.darker(250 + int(100 * x1))) - - glNormal3d((x1 + x2)/2.0, (y1 + y2)/2.0, 0.0) - glVertex3d(x1, y1, +0.05) - glVertex3d(x2, y2, +0.05) - glVertex3d(x2, y2, -0.05) - glVertex3d(x1, y1, -0.05) - - def normalizeAngle(self, angle): - while angle < 0: - angle += 360 * 16 - while angle > 360 * 16: - angle -= 360 * 16 - return angle - - def createBubbles(self, number): - for i in range(number): - position = QPointF(self.width()*(0.1 + 0.8*random.random()), - self.height()*(0.1 + 0.8*random.random())) - radius = min(self.width(), self.height())*(0.0125 + 0.0875*random.random()) - velocity = QPointF(self.width()*0.0125*(-0.5 + random.random()), - self.height()*0.0125*(-0.5 + random.random())) - - self.bubbles.append(Bubble(position, radius, velocity)) - - def animate(self): - for bubble in self.bubbles: - self.update(bubble.rect().toRect()) - bubble.move(self.rect()) - self.update(bubble.rect().toRect()) - - def formatInstructions(self, width, height): - text = self.tr("Click and drag with the left mouse button " - "to rotate the Qt logo.") - metrics = QFontMetrics(self.font()) - border = max(4, metrics.leading()) - - rect = metrics.boundingRect(0, 0, width - 2*border, int(height*0.125), - Qt.AlignCenter | Qt.TextWordWrap, text) - self.image = QImage(width, rect.height() + 2*border, - QImage.Format_ARGB32_Premultiplied) - self.image.fill(qRgba(0, 0, 0, 127)) - - painter = QPainter() - painter.begin(self.image) - painter.setRenderHint(QPainter.TextAntialiasing) - painter.setPen(Qt.white) - painter.drawText((width - rect.width())/2, border, - rect.width(), rect.height(), - Qt.AlignCenter | Qt.TextWordWrap, text) - painter.end() - - -if __name__ == '__main__': - app = QApplication(sys.argv) - window = GLWidget() - window.show() - res = app.exec_() - window.freeResources() - sys.exit(res) diff --git a/examples/opengl/samplebuffers.py b/examples/opengl/samplebuffers.py deleted file mode 100644 index ad5b79466..000000000 --- a/examples/opengl/samplebuffers.py +++ /dev/null @@ -1,192 +0,0 @@ - -############################################################################ -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################ - -"""PySide2 port of the opengl/legacy/samplebuffers example from Qt v5.x""" - -import sys -import math -from PySide2 import QtCore, QtGui, QtWidgets, QtOpenGL - -try: - from OpenGL import GL -except ImportError: - app = QtWidgets.QApplication(sys.argv) - messageBox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical, "OpenGL samplebuffers", - "PyOpenGL must be installed to run this example.", - QtWidgets.QMessageBox.Close) - messageBox.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - messageBox.exec_() - sys.exit(1) - - -class GLWidget(QtOpenGL.QGLWidget): - GL_MULTISAMPLE = 0x809D - rot = 0.0 - - def __init__(self, parent=None): - QtOpenGL.QGLWidget.__init__(self, QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers), parent) - - self.list_ = [] - - self.startTimer(40) - self.setWindowTitle(self.tr("Sample Buffers")) - - def initializeGL(self): - GL.glMatrixMode(GL.GL_PROJECTION) - GL.glLoadIdentity() - GL.glOrtho( -.5, .5, .5, -.5, -1000, 1000) - GL.glMatrixMode(GL.GL_MODELVIEW) - GL.glLoadIdentity() - GL.glClearColor(1.0, 1.0, 1.0, 1.0) - - self.makeObject() - - def resizeGL(self, w, h): - GL.glViewport(0, 0, w, h) - - def paintGL(self): - GL.glClear(GL.GL_COLOR_BUFFER_BIT) - - GL.glMatrixMode(GL.GL_MODELVIEW) - GL.glPushMatrix() - GL.glEnable(GLWidget.GL_MULTISAMPLE) - GL.glTranslatef( -0.25, -0.10, 0.0) - GL.glScalef(0.75, 1.15, 0.0) - GL.glRotatef(GLWidget.rot, 0.0, 0.0, 1.0) - GL.glCallList(self.list_) - GL.glPopMatrix() - - GL.glPushMatrix() - GL.glDisable(GLWidget.GL_MULTISAMPLE) - GL.glTranslatef(0.25, -0.10, 0.0) - GL.glScalef(0.75, 1.15, 0.0) - GL.glRotatef(GLWidget.rot, 0.0, 0.0, 1.0) - GL.glCallList(self.list_) - GL.glPopMatrix() - - GLWidget.rot += 0.2 - - self.qglColor(QtCore.Qt.black) - self.renderText(-0.35, 0.4, 0.0, "Multisampling enabled") - self.renderText(0.15, 0.4, 0.0, "Multisampling disabled") - - def timerEvent(self, event): - self.update() - - def makeObject(self): - trolltechGreen = QtGui.QColor.fromCmykF(0.40, 0.0, 1.0, 0.0) - Pi = 3.14159265358979323846 - NumSectors = 15 - 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.list_ = GL.glGenLists(1) - GL.glNewList(self.list_, GL.GL_COMPILE) - - for i in range(NumSectors): - angle1 = float((i * 2 * Pi) / NumSectors) - x5 = 0.30 * math.sin(angle1) - y5 = 0.30 * math.cos(angle1) - x6 = 0.20 * math.sin(angle1) - y6 = 0.20 * math.cos(angle1) - - angle2 = float(((i + 1) * 2 * Pi) / NumSectors) - x7 = 0.20 * math.sin(angle2) - y7 = 0.20 * math.cos(angle2) - x8 = 0.30 * math.sin(angle2) - y8 = 0.30 * math.cos(angle2) - - self.qglColor(trolltechGreen) - self.quad(GL.GL_QUADS, x5, y5, x6, y6, x7, y7, x8, y8) - self.qglColor(QtCore.Qt.black) - self.quad(GL.GL_LINE_LOOP, x5, y5, x6, y6, x7, y7, x8, y8) - - self.qglColor(trolltechGreen) - self.quad(GL.GL_QUADS, x1, y1, x2, y2, y2, x2, y1, x1) - self.quad(GL.GL_QUADS, x3, y3, x4, y4, y4, x4, y3, x3) - - self.qglColor(QtCore.Qt.black) - self.quad(GL.GL_LINE_LOOP, x1, y1, x2, y2, y2, x2, y1, x1) - self.quad(GL.GL_LINE_LOOP, x3, y3, x4, y4, y4, x4, y3, x3) - - GL.glEndList() - - def quad(self, primitive, x1, y1, x2, y2, x3, y3, x4, y4): - GL.glBegin(primitive) - - GL.glVertex2d(x1, y1) - GL.glVertex2d(x2, y2) - GL.glVertex2d(x3, y3) - GL.glVertex2d(x4, y4) - - GL.glEnd() - - def freeResources(self): - self.makeCurrent() - GL.glDeleteLists(self.list_, 1) - - -if __name__ == '__main__': - app = QtWidgets.QApplication(sys.argv) - - if not QtOpenGL.QGLFormat.hasOpenGL(): - QMessageBox.information(0, "OpenGL pbuffers", - "This system does not support OpenGL.", - QMessageBox.Ok) - sys.exit(1) - - f = QtOpenGL.QGLFormat.defaultFormat() - f.setSampleBuffers(True) - QtOpenGL.QGLFormat.setDefaultFormat(f) - - widget = GLWidget() - widget.resize(640, 480) - widget.show() - res = app.exec_() - widget.freeResources() - sys.exit(res) diff --git a/examples/opengl/textures/doc/textures.png b/examples/opengl/textures/doc/textures.png Binary files differnew file mode 100644 index 000000000..ff80a7d8f --- /dev/null +++ b/examples/opengl/textures/doc/textures.png diff --git a/examples/opengl/textures/doc/textures.rst b/examples/opengl/textures/doc/textures.rst new file mode 100644 index 000000000..80bf20451 --- /dev/null +++ b/examples/opengl/textures/doc/textures.rst @@ -0,0 +1,9 @@ +Texture Example +=============== + +The Textures example demonstrates the use of Qt's image classes as textures in +applications that use both OpenGL and Qt to display graphics. + +.. image:: textures.png + :width: 400 + :alt: Textures Screenshot diff --git a/examples/opengl/textures/textures.py b/examples/opengl/textures/textures.py index c8b421749..87c1164b7 100644 --- a/examples/opengl/textures/textures.py +++ b/examples/opengl/textures/textures.py @@ -1,189 +1,170 @@ +# Copyright (C) 2013 Riverbank Computing Limited. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause -############################################################################ -## -## Copyright (C) 2013 Riverbank Computing Limited. -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: http://www.qt.io/licensing/ -## -## This file is part of the Qt for Python examples of the Qt Toolkit. -## -## $QT_BEGIN_LICENSE:BSD$ -## You may use this file under the terms of the BSD license as follows: -## -## "Redistribution and use in source and binary forms, with or without -## modification, are permitted provided that the following conditions are -## met: -## * Redistributions of source code must retain the above copyright -## notice, this list of conditions and the following disclaimer. -## * Redistributions in binary form must reproduce the above copyright -## notice, this list of conditions and the following disclaimer in -## the documentation and/or other materials provided with the -## distribution. -## * Neither the name of The Qt Company Ltd nor the names of its -## contributors may be used to endorse or promote products derived -## from this software without specific prior written permission. -## -## -## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -## -## $QT_END_LICENSE$ -## -############################################################################ - -"""PySide2 port of the opengl/textures example from Qt v5.x""" +"""PySide6 port of the opengl/textures example from Qt v6.x showing the use + of legacy OpenGL functions with QOpenGLVersionFunctionsFactory.""" import sys -from PySide2 import QtCore, QtGui, QtWidgets, QtOpenGL +from PySide6.QtCore import QPoint, QSize, Qt, QTimer, Signal +from PySide6.QtGui import QColor, QImage, QSurfaceFormat +from PySide6.QtWidgets import QApplication, QGridLayout, QMessageBox, QWidget +from PySide6.QtOpenGL import (QOpenGLTexture, QOpenGLVersionFunctionsFactory, + QOpenGLVersionProfile) +from PySide6.QtOpenGLWidgets import QOpenGLWidget try: - from OpenGL.GL import * + from OpenGL import GL except ImportError: - app = QtWidgets.QApplication(sys.argv) - messageBox = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Critical, "OpenGL textures", - "PyOpenGL must be installed to run this example.", - QtWidgets.QMessageBox.Close) + app = QApplication(sys.argv) + messageBox = QMessageBox(QMessageBox.Critical, "OpenGL textures", + "PyOpenGL must be installed to run this example.", + QMessageBox.Close) messageBox.setDetailedText("Run:\npip install PyOpenGL PyOpenGL_accelerate") - messageBox.exec_() + messageBox.exec() sys.exit(1) -import textures_rc +import textures_rc # noqa: F401 -class GLWidget(QtOpenGL.QGLWidget): +class GLWidget(QOpenGLWidget): sharedObject = 0 refCount = 0 coords = ( - ( ( +1, -1, -1 ), ( -1, -1, -1 ), ( -1, +1, -1 ), ( +1, +1, -1 ) ), - ( ( +1, +1, -1 ), ( -1, +1, -1 ), ( -1, +1, +1 ), ( +1, +1, +1 ) ), - ( ( +1, -1, +1 ), ( +1, -1, -1 ), ( +1, +1, -1 ), ( +1, +1, +1 ) ), - ( ( -1, -1, -1 ), ( -1, -1, +1 ), ( -1, +1, +1 ), ( -1, +1, -1 ) ), - ( ( +1, -1, +1 ), ( -1, -1, +1 ), ( -1, -1, -1 ), ( +1, -1, -1 ) ), - ( ( -1, -1, +1 ), ( +1, -1, +1 ), ( +1, +1, +1 ), ( -1, +1, +1 ) ) + ((+1, -1, -1), (-1, -1, -1), (-1, +1, -1), (+1, +1, -1)), + ((+1, +1, -1), (-1, +1, -1), (-1, +1, +1), (+1, +1, +1)), + ((+1, -1, +1), (+1, -1, -1), (+1, +1, -1), (+1, +1, +1)), + ((-1, -1, -1), (-1, -1, +1), (-1, +1, +1), (-1, +1, -1)), + ((+1, -1, +1), (-1, -1, +1), (-1, -1, -1), (+1, -1, -1)), + ((-1, -1, +1), (+1, -1, +1), (+1, +1, +1), (-1, +1, +1)) ) - clicked = QtCore.Signal() + clicked = Signal() - def __init__(self, parent, shareWidget): - QtOpenGL.QGLWidget.__init__(self, parent, shareWidget) + def __init__(self, parent): + super().__init__(parent) - self.clearColor = QtCore.Qt.black + self.clearColor = Qt.black self.xRot = 0 self.yRot = 0 self.zRot = 0 - self.clearColor = QtGui.QColor() - self.lastPos = QtCore.QPoint() + self.clearColor = QColor() + self.lastPos = QPoint() + self.funcs = None def freeGLResources(self): GLWidget.refCount -= 1 if GLWidget.refCount == 0: self.makeCurrent() - glDeleteLists(self.__class__.sharedObject, 1) + self.funcs.glDeleteLists(self.__class__.sharedObject, 1) def minimumSizeHint(self): - return QtCore.QSize(50, 50) + return QSize(50, 50) def sizeHint(self): - return QtCore.QSize(200, 200) + return QSize(200, 200) def rotateBy(self, xAngle, yAngle, zAngle): self.xRot = (self.xRot + xAngle) % 5760 self.yRot = (self.yRot + yAngle) % 5760 self.zRot = (self.zRot + zAngle) % 5760 - self.updateGL() + self.update() def setClearColor(self, color): self.clearColor = color - self.updateGL() + self.update() def initializeGL(self): + profile = QOpenGLVersionProfile() + profile.setVersion(3, 2) + profile.setProfile(QSurfaceFormat.CompatibilityProfile) + self.funcs = QOpenGLVersionFunctionsFactory.get(profile) + self.funcs.initializeOpenGLFunctions() + if not GLWidget.sharedObject: self.textures = [] for i in range(6): - self.textures.append(self.bindTexture(QtGui.QPixmap(":/images/side%d.png" % (i + 1)))) + image = QImage(f":/images/side{i + 1}.png") + self.textures.append(QOpenGLTexture(image)) GLWidget.sharedObject = self.makeObject() GLWidget.refCount += 1 - glEnable(GL_DEPTH_TEST) - glEnable(GL_CULL_FACE) - glEnable(GL_TEXTURE_2D) + self.funcs.glEnable(GL.GL_DEPTH_TEST) + self.funcs.glEnable(GL.GL_CULL_FACE) + self.funcs.glEnable(GL.GL_TEXTURE_2D) def paintGL(self): - self.qglClearColor(self.clearColor) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glLoadIdentity() - glTranslated(0.0, 0.0, -10.0) - glRotated(self.xRot / 16.0, 1.0, 0.0, 0.0) - glRotated(self.yRot / 16.0, 0.0, 1.0, 0.0) - glRotated(self.zRot / 16.0, 0.0, 0.0, 1.0) - glCallList(GLWidget.sharedObject) + self.funcs.glClearColor(self.clearColor.red(), self.clearColor.green(), + self.clearColor.blue(), 1) + self.funcs.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) + self.funcs.glLoadIdentity() + self.funcs.glTranslated(0.0, 0.0, -10.0) + self.funcs.glRotated(self.xRot / 16.0, 1.0, 0.0, 0.0) + self.funcs.glRotated(self.yRot / 16.0, 0.0, 1.0, 0.0) + self.funcs.glRotated(self.zRot / 16.0, 0.0, 0.0, 1.0) + self.funcs.glCallList(GLWidget.sharedObject) def resizeGL(self, width, height): side = min(width, height) - glViewport(int((width - side) / 2), int((height - side) / 2), side, side) + x = int((width - side) / 2) + y = int((height - side) / 2) + self.funcs.glViewport(x, y, side, side) - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0) - glMatrixMode(GL_MODELVIEW) + self.funcs.glMatrixMode(GL.GL_PROJECTION) + self.funcs.glLoadIdentity() + self.funcs.glOrtho(-0.5, +0.5, +0.5, -0.5, 4.0, 15.0) + self.funcs.glMatrixMode(GL.GL_MODELVIEW) def mousePressEvent(self, event): - self.lastPos = QtCore.QPoint(event.pos()) + self.lastPos = event.position().toPoint() def mouseMoveEvent(self, event): - dx = event.x() - self.lastPos.x() - dy = event.y() - self.lastPos.y() + pos = event.position().toPoint() + dx = pos.x() - self.lastPos.x() + dy = pos.y() - self.lastPos.y() - if event.buttons() & QtCore.Qt.LeftButton: + if event.buttons() & Qt.LeftButton: self.rotateBy(8 * dy, 8 * dx, 0) - elif event.buttons() & QtCore.Qt.RightButton: + elif event.buttons() & Qt.RightButton: self.rotateBy(8 * dy, 0, 8 * dx) - self.lastPos = QtCore.QPoint(event.pos()) + self.lastPos = pos def mouseReleaseEvent(self, event): self.clicked.emit() def makeObject(self): - dlist = glGenLists(1) - glNewList(dlist, GL_COMPILE) + dlist = self.funcs.glGenLists(1) + self.funcs.glNewList(dlist, GL.GL_COMPILE) for i in range(6): - glBindTexture(GL_TEXTURE_2D, self.textures[i]) + self.textures[i].bind() - glBegin(GL_QUADS) + self.funcs.glBegin(GL.GL_QUADS) for j in range(4): tx = {False: 0, True: 1}[j == 0 or j == 3] ty = {False: 0, True: 1}[j == 0 or j == 1] - glTexCoord2d(tx, ty) - glVertex3d(0.2 * GLWidget.coords[i][j][0], - 0.2 * GLWidget.coords[i][j][1], - 0.2 * GLWidget.coords[i][j][2]) + self.funcs.glTexCoord2d(tx, ty) + x = 0.2 * GLWidget.coords[i][j][0] + y = 0.2 * GLWidget.coords[i][j][1] + z = 0.2 * GLWidget.coords[i][j][2] + self.funcs.glVertex3d(x, y, z) - glEnd() + self.funcs.glEnd() - glEndList() + self.funcs.glEndList() return dlist -class Window(QtWidgets.QWidget): +class Window(QWidget): NumRows = 2 NumColumns = 3 def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) + QWidget.__init__(self, parent) - mainLayout = QtWidgets.QGridLayout() + mainLayout = QGridLayout(self) self.glWidgets = [] for i in range(Window.NumRows): @@ -191,26 +172,25 @@ class Window(QtWidgets.QWidget): for j in range(Window.NumColumns): self.glWidgets[i].append(None) + hue_div = (Window.NumRows * Window.NumColumns - 1) for i in range(Window.NumRows): for j in range(Window.NumColumns): - clearColor = QtGui.QColor() - clearColor.setHsv(((i * Window.NumColumns) + j) * 255 - / (Window.NumRows * Window.NumColumns - 1), - 255, 63) - - self.glWidgets[i][j] = GLWidget(self, self.glWidgets[0][0]) - self.glWidgets[i][j].setClearColor(clearColor) - self.glWidgets[i][j].rotateBy(+42 * 16, +42 * 16, -21 * 16) - mainLayout.addWidget(self.glWidgets[i][j], i, j) + clearColor = QColor() + hue = ((i * Window.NumColumns) + j) * 255 / hue_div + clearColor.setHsv(hue, 255, 63) - self.glWidgets[i][j].clicked.connect(self.setCurrentGlWidget) - QtWidgets.qApp.lastWindowClosed.connect(self.glWidgets[i][j].freeGLResources) + glw = GLWidget(self) + self.glWidgets[i][j] = glw + glw.setClearColor(clearColor) + glw.rotateBy(+42 * 16, +42 * 16, -21 * 16) + mainLayout.addWidget(glw, i, j) - self.setLayout(mainLayout) + glw.clicked.connect(self.setCurrentGlWidget) + qApp.lastWindowClosed.connect(glw.freeGLResources) # noqa: F821 self.currentGlWidget = self.glWidgets[0][0] - timer = QtCore.QTimer(self) + timer = QTimer(self) timer.timeout.connect(self.rotateOneStep) timer.start(20) @@ -225,7 +205,7 @@ class Window(QtWidgets.QWidget): if __name__ == "__main__": - app = QtWidgets.QApplication(sys.argv) + app = QApplication(sys.argv) window = Window() window.show() - sys.exit(app.exec_()) + sys.exit(app.exec()) diff --git a/examples/opengl/textures/textures.pyproject b/examples/opengl/textures/textures.pyproject new file mode 100644 index 000000000..1ad304324 --- /dev/null +++ b/examples/opengl/textures/textures.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["textures.qrc", "textures.py"] +} diff --git a/examples/opengl/textures/textures_rc.py b/examples/opengl/textures/textures_rc.py index 1e3923f80..a0676a335 100644 --- a/examples/opengl/textures/textures_rc.py +++ b/examples/opengl/textures/textures_rc.py @@ -1,9 +1,9 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 5.14.0 +# Created by: The Resource Compiler for Qt version 6.2.2 # WARNING! All changes made in this file will be lost! -from PySide2 import QtCore +from PySide6 import QtCore qt_resource_data = b"\ \x00\x00\x04\x14\ @@ -285,267 +285,6 @@ F\xd3\xfc\x15\x03\xac\x96\x07k1\xbdh?]\x19'\ \x80\x00\x04 \x00*\x00\xda\x22\x00\x01\x08p\x07\x80\xbb\ \x08@\x00\x02\x10\x00z\xfd\x06\x0eL\xb1gp\xf4v\ \x0b\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00\x09\x13\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x01\x00\x00\x00\x01\x00\x08\x03\x00\x00\x00k\xacXT\ -\x00\x00\x00\xedPLTEx\x00\xf8\x80\x00\xf8\x80\x04\ -\xf8\x80\x08\xf8\x80\x0c\xf8\x88\x10\xf8\x88\x14\xf8\x88\x18\xf8\ -\x88\x1c\xf8\x88 \xf8\x90 \xf8\x90$\xf8\x90(\xf8\x90\ -,\xf8\x900\xf8\x980\xf8\x984\xf8\x988\xf8\x98<\ -\xf8\x98@\xf8\xa0@\xf8\xa0D\xf8\xa0H\xf8\xa0L\xf8\ -\xa0P\xf8\xa8P\xf8\xa8T\xf8\xa8X\xf8\xa8\x5c\xf8\xa8\ -`\xf8\xb0`\xf8\xb0d\xf8\xb0h\xf8\xb0l\xf8\xb0p\ -\xf8\xb8p\xf8\xb8t\xf8\xb8x\xf8\xb8|\xf8\xb8\x80\xf8\ -\xc0\x80\xf8\xc0\x84\xf8\xc0\x88\xf8\xc0\x8c\xf8\xc0\x90\xf8\xc8\ -\x90\xf8\xc8\x94\xf8\xc8\x98\xf8\xc8\x9c\xf8\xc8\xa0\xf8\xd0\xa0\ -\xf8\xd0\xa4\xf8\xd0\xa8\xf8\xd0\xac\xf8\xd0\xb0\xf8\xd8\xb0\xf8\ -\xd8\xb4\xf8\xd8\xb8\xf8\xd8\xbc\xf8\xd8\xc0\xf8\xe0\xc0\xf8\xe0\ -\xc4\xf8\xe0\xc8\xf8\xe0\xcc\xf8\xe0\xd0\xf8\xe8\xd0\xf8\xe8\xd4\ -\xf8\xe8\xd8\xf8\xe8\xdc\xf8\xe8\xe0\xf8\xf0\xe0\xf8\xf0\xe4\xf8\ -\xf0\xe8\xf8\xf0\xec\xf8\xf0\xf0\xf8\xf8\xf0\xf8\xf8\xf4\xf8\xf8\ -\xf8\xf8\xf8\xfc\xf8\x09\xd19\xc7\x00\x00\x00\x09pHY\ -s\x00\x00\x00H\x00\x00\x00H\x00F\xc9k>\x00\x00\ -\x07\xccIDATx\xda\xed\xddiC\xdaL\x10\x00\ -\xe0\x1cP\x90\xa3(-R\x81\xaax\x03\xe5\xa8\x82\x22\ -E\xa8\x81\x0a\x91d\xfe\xff\xcf\xe9\x87\xbe\xafr\xe4\xce\ -&f6\xb3\xdf#\xd9G\xc81;3+@\xcc\x87\ -@\x00\x04@\x00\x04@\x00 \xc4p\x10\x00\x01\x10\xc0\ -&@\xac.}\x04@\x00\x04@\x00\x04@\x00\x04@\ -\x00\x04@\x00\x04@\x00\x04@\x00\x04@\x00\x04@\x00\ -\x04@\x00\x04@\x00\x04@\x00\x04@\x00\x04@\x00\x04\ -@\x00\x04@\x00\x04@\x00\x04@\x00\x04@\x00\xac\x87\ -\xae\x0cZg\x95/\xf9tR\x96\x04)\xf1)\x9d-\ -V\xce[}E\x8f\x03\x80\xd2=\xfe,\x99\xa4,J\ -\x85Zg\xc63\xc0\xea\xae\x96\xb2M\xdcLUo_\ -\xb9\x04\xd0\xee\x0e%\x87\xc9\xab\xd2\xb7\xbe\xc6\x1b\xc0\xe4\ -Xv\x95\xc0\x9b8\x9fs\x04\xa0\xff,\xb8\xcfa\x16\ -+\x13N\x00^\x1b\x9f<\xe6qW\xe7\x1c\x00\xa8W\ -\x09\xef\x99\xec\xd2\xf9\x0a9\xc0\xab\x9f\xe9\x0b\x82 d\ -\xc6\x98\x01\xb4V\xd2w9\x83x\xa6\xa1\x05\xb8K3\ -\xa9\xe88X\xe2\x04P\xf7Y\xd5\xb4\xa4\x9fQ\x02<\ -\xb0\xab\xea\x91\xc7\x18\x01\xae\x18\xd65%\x9e\x10\x02\x1c\ -\xb2\xac\xecJL\xf1\x01$Y\x02\x08\xa9%6\x809\ -\xe3\xea\xbe\xa2\x8e\x0c\xe0\x8eu}\xe3\x052\x80s\xd6\ -\x00\xe2\x04\x17\xc0\x01\xf3\x12\xd7}T\x00\xba\xcc\x1c@\ -\xe8`\x02\xf8\x1d@\x95sJG\x04\xd0\x0d\xa2\xce\xfb\ -\x16\x11\xc0I\x10\x00yD\x00\xf9@J\xfd'h\x00\ -4\xd1\xf4n\x96\xab\x5cw\x87OSE\x99\x8e\x07\x9d\ -\xcb\xf2\x9e\x1b\x80K4\x00c\x93\x17\xdb\xb3\xe1N\x8c\ -k\xd9?q\x1c3\xcc\xa1\x01h\x19\x86yGf\xf7\ -\xcca\xd9\xa1\xc0\x1c\x0b@\xd5 \xc4k\xb9\xe8\xa5T\ -\x1c\x01\x0c\xb0\x00dv\xbe\xbc\xb61\x8dQ\xd6\x01\xc0\ -\x15\x12\x80\xe5\xf6\x89\x9f8\x88l\xaej\xf6\x00e$\ -\x00\xc3\xad_\x7f\xcf\xd9a\x0d[\x80\xcfH\x00n6\ -\x977\x86N\x8fk\xdb.\x13 \x01\xd8\x08\x87\x89\xf7\ -\xce\x0f\xbc\xb0\x01H\x22\x01\xd8\xb8\xb1\xf7\xdc\x1c\xf9\xc5\ -&(\x80\x03\xe0\xcf\xc6\xf5\xcf]$\xcdz)M\xc6\ -\x01\xd0_\x7f\x81\xd1\xfc\x5c>\x90\xfe\x04\xd6~\xc9\xa2\ -\xdbx\xf6\xca2\x9a\x9c\xc6\x01P\xf4\x13\xca\xb4\x0c&\ -\x16q\x00\xbc\x87\xc3\x92\xee\xf3\x9d\x9e-S&P\x00\ -\xacM\xe1\x07\x8b\xc7\xe8\xb5q\x83\x02\xa0\xe7/\x8ag\ -\x15L\x1a\xa2\x008};\xdf\x06\xebp\xe2\x12\x05\xc0\ -[J\x98\xacz9\xfc\x97\xf9\xfc\x0b(\x02\x22\xda[\ ->\xe4wO\xc7\xbf\x84\xf76\x1c\x0c\xc0\xd3\xdb\xf9z\ -\xcbkP\xcd\x01~\xa3\x00h\xfb|u\xd3C\x5c\x1c\ -\x0b\x04\xa0\xeas=W3\x05\xe8\xe1\x00\xc8\xfa\x8c\xe2\ -/M\x9f\x83\x91,\x8d\xfd\xec\xfd\x1b?=\x1e?\xe3\ -bq\x94]<\xed=\xae\xaa\xc7\x04\xc0,.\xf6\x0b\ -b\x02pl<\xff\x13\x88\x0b\x80\xf1\xcbPN\x8b\x0b\ -\x80qz\x99\x1cL\xbal\x14\x01\x9a\x86\xc1\xd0\x07\x88\ -\x0d@\xce\x08\xe0\x07\xc4\x06`d4\xff\x06\xc4\x07\xa0\ -\x18B\x1c(\xca\x00\x06I\xf6b\x07\xe2\x03\xf0\xba[\ -d\x22\x0f F\x00\xbb+\xe4{S\x88\x11\xc0nf\ -MI\x85\x18\x01\x0c\xb6s\xcb\xa4V\xc0\x9f\x18-\x80\ -\xbb\xed\xf9\xe7~C\x9c\x00\x1a[\xf3\x97\xae\x83o\xa8\ -\x10!\x80\xe5v\xa6\x5cQ\x09\xe1S\xa3\x03\xd0\xdbZ\ -\x14N\xdf\x85\xf2\xb1Q\x01\x18l\xa5\x16\xcbW\xdc5\ -P\xb0\xfa\xf2\xb7\xb6\xde\x7f\xa4\xfa2\xac\xcf\xfex\x00\ -\xa5U\x92vj\xe6\x07S\x95S\x00u\xf9\xdfX\xcc\ -\x95\xa7A\xab^4\xcd\x08J\xee\xd7\x1a\xc3\x05o\x00\ -\xaa\xe8\xb6l\xba\xda}\xe1\x09`\xe0\xa5H\xa2\xd0\x5c\ -p\x03\xe0\xb1\x8eF\xac<q\x02\xb0'x\x1d\xa5\x09\ -\x0f\x003?\xe5B\xd5\x17\xfc\x00\x1d\x7f\x15\xf4=\xf4\ -\x00e\xc1\xdf8Z\xe1\x06\xd0}v\xd4\x11\x84\xac\x82\ -\x1a`\xec\xbfn0\xf9\x84\x19\x80ES\x11\xf9\x111\ -\x00\x93\xb6:\xf2\x08-\x80\xeb\xe7`\x93\x9b\x81\x82\x15\ -\xa0\xcf\xa8~8\xa3\x22\x05`VO^F\x0a\x90f\ -\x05 tQ\x02\xcc\x98\xcd_H,0\x02l\xa7>\ -\x89\x92w\x81\x1aF\x80\x7f\xa9O\x99\xf2E\xe7a2\ -W5\x00\x00m9\x1b\xf5\xae\xca)\xf7\x02S\x84\x00\ -\xfae\xf1\xfa\xd1\xb8\x80fq[q\xf9\x94\x5c\xe1*\ -*\x0c\x00\xa0\xdf\x1f\xbayL\x10\xe7\xbc\x01\x00\xc0\xfc\ -\xd8\x05\xc1%\x87\x00\x00\xb3#\xe7-\x85\xb8\x04\x00\x18\ -8\xee&2\xe6\x13\x00\x96\xa5p{\xcbE/IJ\ -\xaf;\x03\xf8\xcc+\x80I\xa6\xe8\xeeP\xb9\x05\xb0\xa9\ -\x1f\xff\x7f\x8c\xf8\x05X+\xbc\xb4\x18-\x8e\x01t'\ -\xcd\x18O9\x06\x80\x85\x83'\xe3\x12\xcf\x00N\x96P\ -\xf2\x5c\x038\x08\xa0\xa6\xf8\x06\x18\xd9GE\xf8\x06\xb0\ -\xff\x0aH\x9c\x03\xd8\xe6R\x88\x9c\x03\xe8v\xad\xc9\x93\ -\x9c\x03\xd8F\xd13\xbc\x03<\xda\xa5\x0e\xf1\x0e\xb0\xb2\ -\x89\x0f}\xe5\x1d\xc0\xee>p\xc4=@=\xc6\xef\x02\ -\x00`\xdb\x5c\xb1\xc3=\xc0\xd0\x1a\xe0\x89{\x00\xc5\xfa\ -9H\xe3\x1e`\x11F\x8f\xe9(\x03\xbc\x0a!\xb4\x95\ -\x8b2\x80n\x09\xd0\xe6\x1f@\xb3\x04x\x89,\x80\xfa\ -\xc0\xa8\xd6}i5\x7fV=\xa5\x18\x03\xa8\xf7\xe7\x05\ -\x91U\xd3?\xcb\x9d\x9a\x1aQ\x04\xb8\xcc\x8b,\xff=\ -\x13+\x80Y\x14\x01\xde[\x1f0I\xe7\xec\x87\xb1\xd1\ -\x02S\x80\xb3\xb7\xf3;`qn\x8d\xe0\xef\x01\x8c\x01\ -\xd6v\x17c\xd1\xf3\xe5\xbbE4H\x8b$\x80\xc26\ -h\x9f\x0d\xa3\xb1$\xdb\xbb\xc0\xda\xeeZ\xfe\xb7D\xb2\ -\xb8\x0bJQ\xcd\x13\x5c[\xd2K\xfb\xfe\x92Z\x84\x85\ -\x19v\x15c\x0bp\xca\xf2[j\xbe\xdf\x864\x8f*\ -\xc0zC`\xf9\x8f\xcf\x07\xe1D(\xade\xd9\x02L\ -\x18\xe6t\x9b\xff\x02\xd2Zd\x016\xf7\xd7\xf2\xd7\x01\ -\xe1K8[\x0d1~\x17\xd8\xb8s%\xfd4\x01\x98\ -\x06\x9c\x17\x10\x10\xc0\xe6nQ\x87\xc0\xea/\xad/\x0a\ -\xcf\xa2\x0cp\xbdy\xb2M\xcf\x7f\xc8\xbc\xc2\xae\x0fQ\ -\x06\xd8\xbar\x89\x9e3\xb9\x0a\xc1\xae\x06\x04\x060c\ -T\xe1e\x9a*X\xd0\xa2\x0d\xa0oW\x81\xecy\x8a\ -\x5cM\xcc\x8aI\x12\xacw\xdbc\x1e\x12\xdb\xd9h3\ -\xeb\xe1\xb1]5\xdbcCb\xbd\xbf\x04{\x80\xddm\ -\x06\xb3\xae\x9f\x085\xb3,A1\x80\xdeJ\xac\x01\x0c\ -\x82\x18)\x97\xfdp\xf5r\x88\xad\xc5\x99\x03\x18=\xc0\ -&\xee\xdd\xfc\x85U)\xe88h\xa0\x00\xc6\xc5\x81u\ -\xe7\xd7\xee\xc5~\xc0\x05\x02\x01\x03\x80\xf1\xf5;\xeb\xb4\ -/\xfa\xd0\xb4b\xa4\x098\x00\xcc\xf6[\xae:\xb9\x16\ -\xae\xceLC\x00A\xf5\x96c\x0eP5\x9dB\xdd\xf6\ -\x91\xe0\xd6\xb4\x8221\x02,\x00\x16\xb1l\xa9j\x95\ -\xd3\xa0\xf7r\xe6\x11\x80g@\x03`\x9d\xe0\x99\xbd1\ -y6\x9e^X\xd4\xcf\x1e\x06\xd8\x5c\x8f9\x80m\x8d\ -x\xe6\xe4v\xeb\x85v\xd1\xaf[\xed8,\xfd\x00@\ -\x04\x00Nj\xc2\xe5B\xe5\xac\xd1\xe9v\xdb\x8d\xcb\xea\ -\x81M\xa5`6\xd0\xbe\xca\x01\x00\xe4\x05\xa6\xe3x\x05\ -\xc8\x00\xaa,\xa7\x9f\x1f\x03`\x03h\xb0\x9b~\xa2\x8d\ -\xb1\xaf\xf0\x03\xb3\xf9\xd7\xc2\xe8\xac\xca\x1e`\xceh\xfa\ -GS\x00\x94\x00\xeb+\xa4\x9e\x87X}\x06\xc0\x0a\xe0\ -\xbfa\x96X\x9b\x01\xe0\x05\xf0\xdb/)\xdb\x5c\x00`\ -\x06\x80\xc7o\xde\x9b\x86\xc9\xdf\xc7\x10\xea\x08&Qr\ -q\x93\xf34\xfbro\x05\xc0\x03\x00\x00L/2.\ -[\xa4\xd5\x1f5\x08\x7f\x04\x99*\xab\xb4J\xce\xee\x08\ -b\xae\xd6V\xe0cF\xc0\xb9\xc2\xfa\xb4]\xcd[)\ -\xc8\xd9Js\xb4\x82\x8f\x1b\xa1$K\xcf\x87\xdd\xab\xe3\ -\xc3b>\x9dL\xc8\x92 %R\x99\xfc\xfe\xd7\xf2i\ -\xb3?Y\xc0G\x8f(g\x8b\x13\x00\x01\x10\x00\x01\x10\ -\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\ -\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\ -\x10\x00\x01\x10\x00\x01\x10@\x80\x00q\x1b\x04@\x00\x04\ -\xf0\x0e\x10\xdfA\x00\x04@\x00\x04\x10\xeb\xf1\x17\xe9\x89\ -Gh\xda\x1b|\x00\x00\x00\x00\x00IEND\xaeB\ -`\x82\ -\x00\x00\x06\xe8\ -\x89\ -PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ -\x00\x01\x00\x00\x00\x01\x00\x08\x03\x00\x00\x00k\xacXT\ -\x00\x00\x00\x8aPLTE\x00\xfcx\x00\xfc\x80\x08\xfc\ -\x80\x10\xfc\x80\x10\xfc\x88\x18\xfc\x88 \xfc\x88 \xfc\x90\ -(\xfc\x900\xfc\x900\xfc\x988\xfc\x98@\xfc\x98@\ -\xfc\xa0H\xfc\xa0P\xfc\xa0P\xfc\xa8X\xfc\xa8`\xfc\ -\xa8`\xfc\xb0h\xfc\xb0p\xfc\xb0p\xfc\xb8x\xfc\xb8\ -\x80\xfc\xb8\x80\xfc\xc0\x88\xfc\xc0\x90\xfc\xc0\x90\xfc\xc8\x98\ -\xfc\xc8\xa0\xfc\xd0\xa8\xfc\xd0\xb0\xfc\xd0\xb0\xfc\xd8\xb8\xfc\ -\xd8\xc0\xfc\xe0\xc8\xfc\xe0\xd0\xfc\xe0\xd0\xfc\xe8\xd8\xfc\xe8\ -\xe0\xfc\xe8\xe0\xfc\xf0\xe8\xfc\xf0\xf0\xfc\xf0\xf0\xfc\xf8\xf8\ -\xfc\xf8`;^\x10\x00\x00\x00\x09pHYs\x00\x00\ -\x00H\x00\x00\x00H\x00F\xc9k>\x00\x00\x06\x04I\ -DATx\xda\xed\xddaw\xa28\x14\x06`Ba\ -`\xa4\xba\xd0vtuQ*2Pb\xf8\xff\x7fo\ -;\x9d=g\xdb\x01\x14\x85$7\xe4\xcdw\xcf\xe9}\ -*\xe1\xe6\xe6&:\x8d\xe5\xc3\x01\x00\x00\x00\x00\x00\x00\ -4\x8e\x85\x03\x00\x00\x00\xc0W\x00\xab\xa6>\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00&\x1eU\x9e\xae\x93(\xf4]\x971\xd7\xf3\x830\ -J\xd6iV\x08\x1b\x00\xc4q\xf3\xe8\xf6u,\x06\xf1\ -\xf6\xc8\xe7\x0cP\xed\x1e\xd9\xd5\xbe\xcdp\x9d\x8bY\x02\ -\xf0\x7f\x16C{W\xd9\xea \xe6\x06P$\xec\xa6\xfe\ -]7\xc9\xe7\x04p\x8c\xee\xe8a\x0eR1\x13\x80\xe3\ -\xe2\xce6no\xcbg\x00PD#:\xd9\xdd\x9d\xe9\ -\x00u2\xb2\x99?8\x1a\x0d\x90\xba\xe3\xcf3,k\ -c\x01\xca\xc5$':\x1e2C\x016l\xaaC-\ -O\xc2@\x80\x89\xfe\xfd\xff\xcd\x04\x95q\x00S<\xfd\ -\x9f\xdf\x88\x85Y\x00<\x9e\xfah\x97{4\x0a\xe0\xdb\ -\xf4\x87\xdb\xd8\xc1 \x00.\xe3x\x1f\xcb\xcd\x01(\xa4\ -\x1cptO\xc6\x00\x1c\xe5\x1c\xf1\xf4*S\x00RI\ -\x87\x5c\xbf\x0bC\x006\xb2\x8e\xf9\xbe\x18\x02\x90\xc8\x02\ -p23\x00\x1e\xa5\x01x\xdc\x08\x80@\xdeY\xf7\x17\ -#\x00\x98<\x00V\x1a\x00\xc0\x1d\x89ci\x00\xc0\x80\ -<\x88\xb9w\x7fK\x0a\xfa\x00\x97\xf2 o\xb5\xcd\x8a\ -\x8f\x99L\xf0\x22\xdb\xae\xbc\x9b\x01\x12\xfa\x00\xbdy\xd0\ -b\xdb~\x82\xcbMx\xe3,\xc0\xc9\x03t\xe7A\xee\ -\xba/\x93=\xc57=\x0f\x7f\x93\x07\xe8\xca\x83\xbc\xdd\ -\xa54\xb6\x5c\xdeR\x1e\x22\x0f\xd0\xde\x09`?\xceW\ ->\x93\xdd0\x19T\xd4\x01ZyP8`\xe6\xae\x87\ -\x7f\x09v\xd4\x01\xfe|\xa2\xe3a\x8b\xb8\xe7\xa1\x00\x8f\ -\xc4\x01\xfe\xcc\x83\xd6C?\xb8\x1e\xfa\x1e \x0eP\xdc\ -\x19\xff\xf0U\xe4O\xda\x00\xaf_\xfe\xd8\xbf\xc6M\x9f\ -\x9d\xe3@\x1b\xe0K\x1e\xe4\xdf\x94\xb6\xd4\xc3v\x13\xd6\ -\xb4\x01\xbe<\xca\xfb\xdb>{\x18\x04\xb0\xa2\x0d\xf0\xf9\ -I\xf6\xc7\xe7\x10])5m\x80hL\xfd\xe24\x04\ - \xa4\x0d\xf09\x0f\xba}3cH>\xe4\xd3\x06\xf8\ -\x94\x07\xb1\xdb\xeb\xd8C\xf6\x14\x5c\xd2\x00|d\xf9\xc6\ -W\x99\x09\xc9\x00(Ff\xed/\xa6\x7f\x03^G\xa6\ -l\xf9\x80\xe28i\x80t\xec\x1f\xca\x14V\x04d\x00\ -\xac\xc7\x96\xef\xae\xa7\x02\x11i\x80dl\xce~}Q\ -\x98\x90\x06\xf8\xf4\x0f\xbc\xaf\xc3oo\xf8Z \x18\x9b\ -\xb0\xe5\x86\xaf\x06\xd9\xd8\x7fT\xa9poD\x02\x00\x1f\ -\x93\x07\xff^\x13\x9b]\x11*\xc6\xe4\xc1\x1fC\xa8{\ -\x09\xc8\x00\xc8\xc6oc^\x03\xd8\x90\x06H\xc7W\xaf\ -\xaf\x01\x1ci\x03\x04\xee\xc8\xa9\xea|m% H\x03\ -|Lc?\xf3C\xba\xbd\xfb\xd3\xca\x0abD\x8f\xce\ -VW\x00\xf6s\x078\x19\xbf=>rd\x97\x01\xe2\ -f\xee\x00;e\xef\x00\xa2\x00/\x8a\x0a\xa2d\x01.\ -\xd7\x03\xb6\xf3\x07\xb8\xd8*\xe1\xf2\xd9\x03\xd43\xe8\x14\ -\x95\xf7\x12`o\xf3\x07\xb88\x07>7\xf3\x07\xb8t\ -\xde\x90U\xf3\x07\xe0l\x06\x07&\xc6\x8c\xbd\xbaW\x00\ -M\x80XM\x7f\x1cY\x00\xe1\xaah\x0b \x0cp\xa9\ -G\xe6d\x03\xc0JQ\x9f<U\x80\x0b\xef\x00\x9f\xdb\ -\x00\xb0U\xb4\x0c&\x0b\xd0\xdf\x1e\xf2\xd4\xd8\x00\xd0\xbf\ -\x0e\x08\xceV\x00\xf4\x96\x02\x98a7H\xdc9r\x85\ -)\x10I\x80H\xc5V\x00a\x80\xdezx(\xec\x00\ -\xe8\xfb\x02\xb8Uc\x05@\xdf+\x80I\xbcN\x8c\x14\ -@\xdf\x99\xf3\xb4\xb1\x03`'\xbd#\x8a6@\xdfa\ -\x91\xb8\xb1\x04\xa0g\x19\xb8\x14\x96\x00\xf4\xcc\x80\x0b\xc9\ -\xf7\x8a\x92\x01\xe0\xdd\xab\xa0P\xf6\xa5\xa2d\x00\x12=\ -\xf1\x93\x01\xc84\xc5O\x05\xa0\xf64\xc5O\x05\xa0\xf3\ -\xde\xa1\x85\x8a{\xd6i\x00t\xde8\x11\x9d\x1b[\x00\ -r\xa6\xfe\xfdO\x09\xa0s\x02X)\xba^\x9e\x00\x80\ -X(\xcf\x7fi\x01$\x8a\xb6@\xa8\x02\xecTl\x82\ -\x13\x06\xc8\x98\xe2\xf5/1\x80\xc2\x95z\x1a\x80<@\ -\xe5\xc9\xee\x03\xa4\x0d\xc0\x03\xb5\xf5/j\x00\xe7\xf6\x0b\ -\x90\xed\x1b{\x00D\xa4\xe8\xfal\xaa\x00\xed\x1a\x18\xcb\ -\x1a\x8b\x00bU\x17\xc8\x13\x05\xe8\x88?o,\x02\x88\ -\x95\x5c\x9dN\x17 V\xf6\x13\x124\x01\xda\xf3\x9fW\ -6\xf6\x00\x88\xf6UA~\xd5\xd8\x03\xd0\x11\x7f\xa0+\ -~\x1d\x00\xe7v\xfe\x13\xd6\x8d=\x00|\xa1\xa5\xfcM\ -\x06\x80\x87z\xca\xdfT\x00\xeav\xfc\x91h\xec\x01\xa8\ -\x03]\xe5o\x1a\x00\xd57m\xe5o\x12\x00\x95\xaf\xaf\ -\xfcM\x01\xa0\xf45\x96\xbf\x09\x00\x14\x9e\xce\xf2\xb7~\ -\x80\xe2Ak\xf9[;\xc0\xc9\xd5[\xfe\xd6\x0d\x90\xbb\ -\xca\xfa\xbfI\x02\x1c]\xdd\xe5o\xbd\x00\xafL{\xf9\ -[+@{\xffO}\xf9['\xc0\x81\x11(\x7fk\ -\x04\xd83\x0a\xe5o}\x00)\x8d\xf2\xb76\x80\x1d\x91\ -\xf2\xb7.\x80-\x95\xf2\xb7&\x80v\x03\xa0?E\xf9\ -[pC\x00~\x8c:\xfe$\xf8[Y\x9c\xf2\xec\xb0\ -O\xb7\x9b\xf5K\x12\xaf\x96Q\x18\xf8\x0f\xec\xfe\xdbJ\ -\x15\x03t]\x07S\xb7\xc3\xac\xab\xf70_\xdf\xc3\xdc\ -\xbd\x87\xf9\xfc+\xcc\xef\x81\xef\xb9j\xae\x93\x92\x09\xf0\ -\xd4y\x0bJ\xba\xdb\xbe\x87\x99\xbc\x87\xb9\xb8\x1af\xff\ -H\x0d\x00\x90\xf7\xcb\xb3S\xde\xac,\x0f \x96\x19?\ -\xfd9@\xac\xa4\xc6?\xddm\x22\x92\x00\xc4Rn\xfc\ -NI\x1b@D\x92\xe3\x9f\xee\x17\x07\xe5\xfc\xda\xdcB\ -v\xfc\x0e\xa7\x0c\xa0 ~\xe7L\x18\x80\x87\xf2\xe3w\ -\x04]\x80:P\x10\xfft\xd9\xfa\xe4\x00oJ\xe2\xa7\ -\x0bP\xf9\x8e\xd5\x00\xa5\xa2\xf8\xa9\x02\x94\x9ec5@\ -\xa1,~\xa2\x00+\x07\x00\x00\x00\x00\x00\x00\xa0b\x08\ -\xdb\x018\x00,\x07\xa8m\x07\xa8l\x07(l\x07\xa0\ -^\x167g\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`+\x80\ -m\x03\x00\x00\x00\xc0\xff\x00\xf6\x0e\x00\x00\x00\x00\x00\xb0\ -z\xfc\x0bC\xd4\xc6\xc6D\x07\xe4\xaa\x00\x00\x00\x00I\ -END\xaeB`\x82\ \x00\x00\x09\x8e\ \x89\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ @@ -701,6 +440,267 @@ Q\xee\x10@\x8f\x84\x00\x08\x80\x00\x08\x80\x00\x08\x80\x00\ \x00\x08`*\x80i\x09\x01\x10\x00\x01~\x01\x98\x9b\x10\ \x00\x01\x10\x00\x01\x8cN\xff\x00\xf3k\xd4\xa5uQ\x85\ 3\x00\x00\x00\x00IEND\xaeB`\x82\ +\x00\x00\x06\xe8\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x01\x00\x00\x00\x01\x00\x08\x03\x00\x00\x00k\xacXT\ +\x00\x00\x00\x8aPLTE\x00\xfcx\x00\xfc\x80\x08\xfc\ +\x80\x10\xfc\x80\x10\xfc\x88\x18\xfc\x88 \xfc\x88 \xfc\x90\ +(\xfc\x900\xfc\x900\xfc\x988\xfc\x98@\xfc\x98@\ +\xfc\xa0H\xfc\xa0P\xfc\xa0P\xfc\xa8X\xfc\xa8`\xfc\ +\xa8`\xfc\xb0h\xfc\xb0p\xfc\xb0p\xfc\xb8x\xfc\xb8\ +\x80\xfc\xb8\x80\xfc\xc0\x88\xfc\xc0\x90\xfc\xc0\x90\xfc\xc8\x98\ +\xfc\xc8\xa0\xfc\xd0\xa8\xfc\xd0\xb0\xfc\xd0\xb0\xfc\xd8\xb8\xfc\ +\xd8\xc0\xfc\xe0\xc8\xfc\xe0\xd0\xfc\xe0\xd0\xfc\xe8\xd8\xfc\xe8\ +\xe0\xfc\xe8\xe0\xfc\xf0\xe8\xfc\xf0\xf0\xfc\xf0\xf0\xfc\xf8\xf8\ +\xfc\xf8`;^\x10\x00\x00\x00\x09pHYs\x00\x00\ +\x00H\x00\x00\x00H\x00F\xc9k>\x00\x00\x06\x04I\ +DATx\xda\xed\xddaw\xa28\x14\x06`Ba\ +`\xa4\xba\xd0vtuQ*2Pb\xf8\xff\x7fo\ +;\x9d=g\xdb\x01\x14\x85$7\xe4\xcdw\xcf\xe9}\ +*\xe1\xe6\xe6&:\x8d\xe5\xc3\x01\x00\x00\x00\x00\x00\x00\ +4\x8e\x85\x03\x00\x00\x00\xc0W\x00\xab\xa6>\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00&\x1eU\x9e\xae\x93(\xf4]\x971\xd7\xf3\x830\ +J\xd6iV\x08\x1b\x00\xc4q\xf3\xe8\xf6u,\x06\xf1\ +\xf6\xc8\xe7\x0cP\xed\x1e\xd9\xd5\xbe\xcdp\x9d\x8bY\x02\ +\xf0\x7f\x16C{W\xd9\xea \xe6\x06P$\xec\xa6\xfe\ +]7\xc9\xe7\x04p\x8c\xee\xe8a\x0eR1\x13\x80\xe3\ +\xe2\xce6no\xcbg\x00PD#:\xd9\xdd\x9d\xe9\ +\x00u2\xb2\x99?8\x1a\x0d\x90\xba\xe3\xcf3,k\ +c\x01\xca\xc5$':\x1e2C\x016l\xaaC-\ +O\xc2@\x80\x89\xfe\xfd\xff\xcd\x04\x95q\x00S<\xfd\ +\x9f\xdf\x88\x85Y\x00<\x9e\xfah\x97{4\x0a\xe0\xdb\ +\xf4\x87\xdb\xd8\xc1 \x00.\xe3x\x1f\xcb\xcd\x01(\xa4\ +\x1cptO\xc6\x00\x1c\xe5\x1c\xf1\xf4*S\x00RI\ +\x87\x5c\xbf\x0bC\x006\xb2\x8e\xf9\xbe\x18\x02\x90\xc8\x02\ +p23\x00\x1e\xa5\x01x\xdc\x08\x80@\xdeY\xf7\x17\ +#\x00\x98<\x00V\x1a\x00\xc0\x1d\x89ci\x00\xc0\x80\ +<\x88\xb9w\x7fK\x0a\xfa\x00\x97\xf2 o\xb5\xcd\x8a\ +\x8f\x99L\xf0\x22\xdb\xae\xbc\x9b\x01\x12\xfa\x00\xbdy\xd0\ +b\xdb~\x82\xcbMx\xe3,\xc0\xc9\x03t\xe7A\xee\ +\xba/\x93=\xc57=\x0f\x7f\x93\x07\xe8\xca\x83\xbc\xdd\ +\xa54\xb6\x5c\xdeR\x1e\x22\x0f\xd0\xde\x09`?\xceW\ +>\x93\xdd0\x19T\xd4\x01ZyP8`\xe6\xae\x87\ +\x7f\x09v\xd4\x01\xfe|\xa2\xe3a\x8b\xb8\xe7\xa1\x00\x8f\ +\xc4\x01\xfe\xcc\x83\xd6C?\xb8\x1e\xfa\x1e \x0eP\xdc\ +\x19\xff\xf0U\xe4O\xda\x00\xaf_\xfe\xd8\xbf\xc6M\x9f\ +\x9d\xe3@\x1b\xe0K\x1e\xe4\xdf\x94\xb6\xd4\xc3v\x13\xd6\ +\xb4\x01\xbe<\xca\xfb\xdb>{\x18\x04\xb0\xa2\x0d\xf0\xf9\ +I\xf6\xc7\xe7\x10])5m\x80hL\xfd\xe24\x04\ + \xa4\x0d\xf09\x0f\xba}3cH>\xe4\xd3\x06\xf8\ +\x94\x07\xb1\xdb\xeb\xd8C\xf6\x14\x5c\xd2\x00|d\xf9\xc6\ +W\x99\x09\xc9\x00(Ff\xed/\xa6\x7f\x03^G\xa6\ +l\xf9\x80\xe28i\x80t\xec\x1f\xca\x14V\x04d\x00\ +\xac\xc7\x96\xef\xae\xa7\x02\x11i\x80dl\xce~}Q\ +\x98\x90\x06\xf8\xf4\x0f\xbc\xaf\xc3oo\xf8Z \x18\x9b\ +\xb0\xe5\x86\xaf\x06\xd9\xd8\x7fT\xa9poD\x02\x00\x1f\ +\x93\x07\xff^\x13\x9b]\x11*\xc6\xe4\xc1\x1fC\xa8{\ +\x09\xc8\x00\xc8\xc6oc^\x03\xd8\x90\x06H\xc7W\xaf\ +\xaf\x01\x1ci\x03\x04\xee\xc8\xa9\xea|m% H\x03\ +|Lc?\xf3C\xba\xbd\xfb\xd3\xca\x0abD\x8f\xce\ +VW\x00\xf6s\x078\x19\xbf=>rd\x97\x01\xe2\ +f\xee\x00;e\xef\x00\xa2\x00/\x8a\x0a\xa2d\x01.\ +\xd7\x03\xb6\xf3\x07\xb8\xd8*\xe1\xf2\xd9\x03\xd43\xe8\x14\ +\x95\xf7\x12`o\xf3\x07\xb88\x07>7\xf3\x07\xb8t\ +\xde\x90U\xf3\x07\xe0l\x06\x07&\xc6\x8c\xbd\xbaW\x00\ +M\x80XM\x7f\x1cY\x00\xe1\xaah\x0b \x0cp\xa9\ +G\xe6d\x03\xc0JQ\x9f<U\x80\x0b\xef\x00\x9f\xdb\ +\x00\xb0U\xb4\x0c&\x0b\xd0\xdf\x1e\xf2\xd4\xd8\x00\xd0\xbf\ +\x0e\x08\xceV\x00\xf4\x96\x02\x98a7H\xdc9r\x85\ +)\x10I\x80H\xc5V\x00a\x80\xdezx(\xec\x00\ +\xe8\xfb\x02\xb8Uc\x05@\xdf+\x80I\xbcN\x8c\x14\ +@\xdf\x99\xf3\xb4\xb1\x03`'\xbd#\x8a6@\xdfa\ +\x91\xb8\xb1\x04\xa0g\x19\xb8\x14\x96\x00\xf4\xcc\x80\x0b\xc9\ +\xf7\x8a\x92\x01\xe0\xdd\xab\xa0P\xf6\xa5\xa2d\x00\x12=\ +\xf1\x93\x01\xc84\xc5O\x05\xa0\xf64\xc5O\x05\xa0\xf3\ +\xde\xa1\x85\x8a{\xd6i\x00t\xde8\x11\x9d\x1b[\x00\ +r\xa6\xfe\xfdO\x09\xa0s\x02X)\xba^\x9e\x00\x80\ +X(\xcf\x7fi\x01$\x8a\xb6@\xa8\x02\xecTl\x82\ +\x13\x06\xc8\x98\xe2\xf5/1\x80\xc2\x95z\x1a\x80<@\ +\xe5\xc9\xee\x03\xa4\x0d\xc0\x03\xb5\xf5/j\x00\xe7\xf6\x0b\ +\x90\xed\x1b{\x00D\xa4\xe8\xfal\xaa\x00\xed\x1a\x18\xcb\ +\x1a\x8b\x00bU\x17\xc8\x13\x05\xe8\x88?o,\x02\x88\ +\x95\x5c\x9dN\x17 V\xf6\x13\x124\x01\xda\xf3\x9fW\ +6\xf6\x00\x88\xf6UA~\xd5\xd8\x03\xd0\x11\x7f\xa0+\ +~\x1d\x00\xe7v\xfe\x13\xd6\x8d=\x00|\xa1\xa5\xfcM\ +\x06\x80\x87z\xca\xdfT\x00\xeav\xfc\x91h\xec\x01\xa8\ +\x03]\xe5o\x1a\x00\xd57m\xe5o\x12\x00\x95\xaf\xaf\ +\xfcM\x01\xa0\xf45\x96\xbf\x09\x00\x14\x9e\xce\xf2\xb7~\ +\x80\xe2Ak\xf9[;\xc0\xc9\xd5[\xfe\xd6\x0d\x90\xbb\ +\xca\xfa\xbfI\x02\x1c]\xdd\xe5o\xbd\x00\xafL{\xf9\ +[+@{\xffO}\xf9['\xc0\x81\x11(\x7fk\ +\x04\xd83\x0a\xe5o}\x00)\x8d\xf2\xb76\x80\x1d\x91\ +\xf2\xb7.\x80-\x95\xf2\xb7&\x80v\x03\xa0?E\xf9\ +[pC\x00~\x8c:\xfe$\xf8[Y\x9c\xf2\xec\xb0\ +O\xb7\x9b\xf5K\x12\xaf\x96Q\x18\xf8\x0f\xec\xfe\xdbJ\ +\x15\x03t]\x07S\xb7\xc3\xac\xab\xf70_\xdf\xc3\xdc\ +\xbd\x87\xf9\xfc+\xcc\xef\x81\xef\xb9j\xae\x93\x92\x09\xf0\ +\xd4y\x0bJ\xba\xdb\xbe\x87\x99\xbc\x87\xb9\xb8\x1af\xff\ +H\x0d\x00\x90\xf7\xcb\xb3S\xde\xac,\x0f \x96\x19?\ +\xfd9@\xac\xa4\xc6?\xddm\x22\x92\x00\xc4Rn\xfc\ +NI\x1b@D\x92\xe3\x9f\xee\x17\x07\xe5\xfc\xda\xdcB\ +v\xfc\x0e\xa7\x0c\xa0 ~\xe7L\x18\x80\x87\xf2\xe3w\ +\x04]\x80:P\x10\xfft\xd9\xfa\xe4\x00oJ\xe2\xa7\ +\x0bP\xf9\x8e\xd5\x00\xa5\xa2\xf8\xa9\x02\x94\x9ec5@\ +\xa1,~\xa2\x00+\x07\x00\x00\x00\x00\x00\x00\xa0b\x08\ +\xdb\x018\x00,\x07\xa8m\x07\xa8l\x07(l\x07\xa0\ +^\x167g\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`+\x80\ +m\x03\x00\x00\x00\xc0\xff\x00\xf6\x0e\x00\x00\x00\x00\x00\xb0\ +z\xfc\x0bC\xd4\xc6\xc6D\x07\xe4\xaa\x00\x00\x00\x00I\ +END\xaeB`\x82\ +\x00\x00\x09\x13\ +\x89\ +PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ +\x00\x01\x00\x00\x00\x01\x00\x08\x03\x00\x00\x00k\xacXT\ +\x00\x00\x00\xedPLTEx\x00\xf8\x80\x00\xf8\x80\x04\ +\xf8\x80\x08\xf8\x80\x0c\xf8\x88\x10\xf8\x88\x14\xf8\x88\x18\xf8\ +\x88\x1c\xf8\x88 \xf8\x90 \xf8\x90$\xf8\x90(\xf8\x90\ +,\xf8\x900\xf8\x980\xf8\x984\xf8\x988\xf8\x98<\ +\xf8\x98@\xf8\xa0@\xf8\xa0D\xf8\xa0H\xf8\xa0L\xf8\ +\xa0P\xf8\xa8P\xf8\xa8T\xf8\xa8X\xf8\xa8\x5c\xf8\xa8\ +`\xf8\xb0`\xf8\xb0d\xf8\xb0h\xf8\xb0l\xf8\xb0p\ +\xf8\xb8p\xf8\xb8t\xf8\xb8x\xf8\xb8|\xf8\xb8\x80\xf8\ +\xc0\x80\xf8\xc0\x84\xf8\xc0\x88\xf8\xc0\x8c\xf8\xc0\x90\xf8\xc8\ +\x90\xf8\xc8\x94\xf8\xc8\x98\xf8\xc8\x9c\xf8\xc8\xa0\xf8\xd0\xa0\ +\xf8\xd0\xa4\xf8\xd0\xa8\xf8\xd0\xac\xf8\xd0\xb0\xf8\xd8\xb0\xf8\ +\xd8\xb4\xf8\xd8\xb8\xf8\xd8\xbc\xf8\xd8\xc0\xf8\xe0\xc0\xf8\xe0\ +\xc4\xf8\xe0\xc8\xf8\xe0\xcc\xf8\xe0\xd0\xf8\xe8\xd0\xf8\xe8\xd4\ +\xf8\xe8\xd8\xf8\xe8\xdc\xf8\xe8\xe0\xf8\xf0\xe0\xf8\xf0\xe4\xf8\ +\xf0\xe8\xf8\xf0\xec\xf8\xf0\xf0\xf8\xf8\xf0\xf8\xf8\xf4\xf8\xf8\ +\xf8\xf8\xf8\xfc\xf8\x09\xd19\xc7\x00\x00\x00\x09pHY\ +s\x00\x00\x00H\x00\x00\x00H\x00F\xc9k>\x00\x00\ +\x07\xccIDATx\xda\xed\xddiC\xdaL\x10\x00\ +\xe0\x1cP\x90\xa3(-R\x81\xaax\x03\xe5\xa8\x82\x22\ +E\xa8\x81\x0a\x91d\xfe\xff\xcf\xe9\x87\xbe\xafr\xe4\xce\ +&f6\xb3\xdf#\xd9G\xc81;3+@\xcc\x87\ +@\x00\x04@\x00\x04@\x00 \xc4p\x10\x00\x01\x10\xc0\ +&@\xac.}\x04@\x00\x04@\x00\x04@\x00\x04@\ +\x00\x04@\x00\x04@\x00\x04@\x00\x04@\x00\x04@\x00\ +\x04@\x00\x04@\x00\x04@\x00\x04@\x00\x04@\x00\x04\ +@\x00\x04@\x00\x04@\x00\x04@\x00\x04@\x00\xac\x87\ +\xae\x0cZg\x95/\xf9tR\x96\x04)\xf1)\x9d-\ +V\xce[}E\x8f\x03\x80\xd2=\xfe,\x99\xa4,J\ +\x85Zg\xc63\xc0\xea\xae\x96\xb2M\xdcLUo_\ +\xb9\x04\xd0\xee\x0e%\x87\xc9\xab\xd2\xb7\xbe\xc6\x1b\xc0\xe4\ +Xv\x95\xc0\x9b8\x9fs\x04\xa0\xff,\xb8\xcfa\x16\ ++\x13N\x00^\x1b\x9f<\xe6qW\xe7\x1c\x00\xa8W\ +\x09\xef\x99\xec\xd2\xf9\x0a9\xc0\xab\x9f\xe9\x0b\x82 d\ +\xc6\x98\x01\xb4V\xd2w9\x83x\xa6\xa1\x05\xb8K3\ +\xa9\xe88X\xe2\x04P\xf7Y\xd5\xb4\xa4\x9fQ\x02<\ +\xb0\xab\xea\x91\xc7\x18\x01\xae\x18\xd65%\x9e\x10\x02\x1c\ +\xb2\xac\xecJL\xf1\x01$Y\x02\x08\xa9%6\x809\ +\xe3\xea\xbe\xa2\x8e\x0c\xe0\x8eu}\xe3\x052\x80s\xd6\ +\x00\xe2\x04\x17\xc0\x01\xf3\x12\xd7}T\x00\xba\xcc\x1c@\ +\xe8`\x02\xf8\x1d@\x95sJG\x04\xd0\x0d\xa2\xce\xfb\ +\x16\x11\xc0I\x10\x00yD\x00\xf9@J\xfd'h\x00\ +4\xd1\xf4n\x96\xab\x5cw\x87OSE\x99\x8e\x07\x9d\ +\xcb\xf2\x9e\x1b\x80K4\x00c\x93\x17\xdb\xb3\xe1N\x8c\ +k\xd9?q\x1c3\xcc\xa1\x01h\x19\x86yGf\xf7\ +\xcca\xd9\xa1\xc0\x1c\x0b@\xd5 \xc4k\xb9\xe8\xa5T\ +\x1c\x01\x0c\xb0\x00dv\xbe\xbc\xb61\x8dQ\xd6\x01\xc0\ +\x15\x12\x80\xe5\xf6\x89\x9f8\x88l\xaej\xf6\x00e$\ +\x00\xc3\xad_\x7f\xcf\xd9a\x0d[\x80\xcfH\x00n6\ +\x977\x86N\x8fk\xdb.\x13 \x01\xd8\x08\x87\x89\xf7\ +\xce\x0f\xbc\xb0\x01H\x22\x01\xd8\xb8\xb1\xf7\xdc\x1c\xf9\xc5\ +&(\x80\x03\xe0\xcf\xc6\xf5\xcf]$\xcdz)M\xc6\ +\x01\xd0_\x7f\x81\xd1\xfc\x5c>\x90\xfe\x04\xd6~\xc9\xa2\ +\xdbx\xf6\xca2\x9a\x9c\xc6\x01P\xf4\x13\xca\xb4\x0c&\ +\x16q\x00\xbc\x87\xc3\x92\xee\xf3\x9d\x9e-S&P\x00\ +\xacM\xe1\x07\x8b\xc7\xe8\xb5q\x83\x02\xa0\xe7/\x8ag\ +\x15L\x1a\xa2\x008};\xdf\x06\xebp\xe2\x12\x05\xc0\ +[J\x98\xacz9\xfc\x97\xf9\xfc\x0b(\x02\x22\xda[\ +>\xe4wO\xc7\xbf\x84\xf76\x1c\x0c\xc0\xd3\xdb\xf9z\ +\xcbkP\xcd\x01~\xa3\x00h\xfb|u\xd3C\x5c\x1c\ +\x0b\x04\xa0\xeas=W3\x05\xe8\xe1\x00\xc8\xfa\x8c\xe2\ +/M\x9f\x83\x91,\x8d\xfd\xec\xfd\x1b?=\x1e?\xe3\ +bq\x94]<\xed=\xae\xaa\xc7\x04\xc0,.\xf6\x0b\ +b\x02pl<\xff\x13\x88\x0b\x80\xf1\xcbPN\x8b\x0b\ +\x80qz\x99\x1cL\xbal\x14\x01\x9a\x86\xc1\xd0\x07\x88\ +\x0d@\xce\x08\xe0\x07\xc4\x06`d4\xff\x06\xc4\x07\xa0\ +\x18B\x1c(\xca\x00\x06I\xf6b\x07\xe2\x03\xf0\xba[\ +d\x22\x0f F\x00\xbb+\xe4{S\x88\x11\xc0nf\ +MI\x85\x18\x01\x0c\xb6s\xcb\xa4V\xc0\x9f\x18-\x80\ +\xbb\xed\xf9\xe7~C\x9c\x00\x1a[\xf3\x97\xae\x83o\xa8\ +\x10!\x80\xe5v\xa6\x5cQ\x09\xe1S\xa3\x03\xd0\xdbZ\ +\x14N\xdf\x85\xf2\xb1Q\x01\x18l\xa5\x16\xcbW\xdc5\ +P\xb0\xfa\xf2\xb7\xb6\xde\x7f\xa4\xfa2\xac\xcf\xfex\x00\ +\xa5U\x92vj\xe6\x07S\x95S\x00u\xf9\xdfX\xcc\ +\x95\xa7A\xab^4\xcd\x08J\xee\xd7\x1a\xc3\x05o\x00\ +\xaa\xe8\xb6l\xba\xda}\xe1\x09`\xe0\xa5H\xa2\xd0\x5c\ +p\x03\xe0\xb1\x8eF\xac<q\x02\xb0'x\x1d\xa5\x09\ +\x0f\x003?\xe5B\xd5\x17\xfc\x00\x1d\x7f\x15\xf4=\xf4\ +\x00e\xc1\xdf8Z\xe1\x06\xd0}v\xd4\x11\x84\xac\x82\ +\x1a`\xec\xbfn0\xf9\x84\x19\x80ES\x11\xf9\x111\ +\x00\x93\xb6:\xf2\x08-\x80\xeb\xe7`\x93\x9b\x81\x82\x15\ +\xa0\xcf\xa8~8\xa3\x22\x05`VO^F\x0a\x90f\ +\x05 tQ\x02\xcc\x98\xcd_H,0\x02l\xa7>\ +\x89\x92w\x81\x1aF\x80\x7f\xa9O\x99\xf2E\xe7a2\ +W5\x00\x00m9\x1b\xf5\xae\xca)\xf7\x02S\x84\x00\ +\xfae\xf1\xfa\xd1\xb8\x80fq[q\xf9\x94\x5c\xe1*\ +*\x0c\x00\xa0\xdf\x1f\xbayL\x10\xe7\xbc\x01\x00\xc0\xfc\ +\xd8\x05\xc1%\x87\x00\x00\xb3#\xe7-\x85\xb8\x04\x00\x18\ +8\xee&2\xe6\x13\x00\x96\xa5p{\xcbE/IJ\ +\xaf;\x03\xf8\xcc+\x80I\xa6\xe8\xeeP\xb9\x05\xb0\xa9\ +\x1f\xff\x7f\x8c\xf8\x05X+\xbc\xb4\x18-\x8e\x01t'\ +\xcd\x18O9\x06\x80\x85\x83'\xe3\x12\xcf\x00N\x96P\ +\xf2\x5c\x038\x08\xa0\xa6\xf8\x06\x18\xd9GE\xf8\x06\xb0\ +\xff\x0aH\x9c\x03\xd8\xe6R\x88\x9c\x03\xe8v\xad\xc9\x93\ +\x9c\x03\xd8F\xd13\xbc\x03<\xda\xa5\x0e\xf1\x0e\xb0\xb2\ +\x89\x0f}\xe5\x1d\xc0\xee>p\xc4=@=\xc6\xef\x02\ +\x00`\xdb\x5c\xb1\xc3=\xc0\xd0\x1a\xe0\x89{\x00\xc5\xfa\ +9H\xe3\x1e`\x11F\x8f\xe9(\x03\xbc\x0a!\xb4\x95\ +\x8b2\x80n\x09\xd0\xe6\x1f@\xb3\x04x\x89,\x80\xfa\ +\xc0\xa8\xd6}i5\x7fV=\xa5\x18\x03\xa8\xf7\xe7\x05\ +\x91U\xd3?\xcb\x9d\x9a\x1aQ\x04\xb8\xcc\x8b,\xff=\ +\x13+\x80Y\x14\x01\xde[\x1f0I\xe7\xec\x87\xb1\xd1\ +\x02S\x80\xb3\xb7\xf3;`qn\x8d\xe0\xef\x01\x8c\x01\ +\xd6v\x17c\xd1\xf3\xe5\xbbE4H\x8b$\x80\xc26\ +h\x9f\x0d\xa3\xb1$\xdb\xbb\xc0\xda\xeeZ\xfe\xb7D\xb2\ +\xb8\x0bJQ\xcd\x13\x5c[\xd2K\xfb\xfe\x92Z\x84\x85\ +\x19v\x15c\x0bp\xca\xf2[j\xbe\xdf\x864\x8f*\ +\xc0zC`\xf9\x8f\xcf\x07\xe1D(\xade\xd9\x02L\ +\x18\xe6t\x9b\xff\x02\xd2Zd\x016\xf7\xd7\xf2\xd7\x01\ +\xe1K8[\x0d1~\x17\xd8\xb8s%\xfd4\x01\x98\ +\x06\x9c\x17\x10\x10\xc0\xe6nQ\x87\xc0\xea/\xad/\x0a\ +\xcf\xa2\x0cp\xbdy\xb2M\xcf\x7f\xc8\xbc\xc2\xae\x0fQ\ +\x06\xd8\xbar\x89\x9e3\xb9\x0a\xc1\xae\x06\x04\x060c\ +T\xe1e\x9a*X\xd0\xa2\x0d\xa0oW\x81\xecy\x8a\ +\x5cM\xcc\x8aI\x12\xacw\xdbc\x1e\x12\xdb\xd9h3\ +\xeb\xe1\xb1]5\xdbcCb\xbd\xbf\x04{\x80\xddm\ +\x06\xb3\xae\x9f\x085\xb3,A1\x80\xdeJ\xac\x01\x0c\ +\x82\x18)\x97\xfdp\xf5r\x88\xad\xc5\x99\x03\x18=\xc0\ +&\xee\xdd\xfc\x85U)\xe88h\xa0\x00\xc6\xc5\x81u\ +\xe7\xd7\xee\xc5~\xc0\x05\x02\x01\x03\x80\xf1\xf5;\xeb\xb4\ +/\xfa\xd0\xb4b\xa4\x098\x00\xcc\xf6[\xae:\xb9\x16\ +\xae\xceLC\x00A\xf5\x96c\x0eP5\x9dB\xdd\xf6\ +\x91\xe0\xd6\xb4\x8221\x02,\x00\x16\xb1l\xa9j\x95\ +\xd3\xa0\xf7r\xe6\x11\x80g@\x03`\x9d\xe0\x99\xbd1\ +y6\x9e^X\xd4\xcf\x1e\x06\xd8\x5c\x8f9\x80m\x8d\ +x\xe6\xe4v\xeb\x85v\xd1\xaf[\xed8,\xfd\x00@\ +\x04\x00Nj\xc2\xe5B\xe5\xac\xd1\xe9v\xdb\x8d\xcb\xea\ +\x81M\xa5`6\xd0\xbe\xca\x01\x00\xe4\x05\xa6\xe3x\x05\ +\xc8\x00\xaa,\xa7\x9f\x1f\x03`\x03h\xb0\x9b~\xa2\x8d\ +\xb1\xaf\xf0\x03\xb3\xf9\xd7\xc2\xe8\xac\xca\x1e`\xceh\xfa\ +GS\x00\x94\x00\xeb+\xa4\x9e\x87X}\x06\xc0\x0a\xe0\ +\xbfa\x96X\x9b\x01\xe0\x05\xf0\xdb/)\xdb\x5c\x00`\ +\x06\x80\xc7o\xde\x9b\x86\xc9\xdf\xc7\x10\xea\x08&Qr\ +q\x93\xf34\xfbro\x05\xc0\x03\x00\x00L/2.\ +[\xa4\xd5\x1f5\x08\x7f\x04\x99*\xab\xb4J\xce\xee\x08\ +b\xae\xd6V\xe0cF\xc0\xb9\xc2\xfa\xb4]\xcd[)\ +\xc8\xd9Js\xb4\x82\x8f\x1b\xa1$K\xcf\x87\xdd\xab\xe3\ +\xc3b>\x9dL\xc8\x92 %R\x99\xfc\xfe\xd7\xf2i\ +\xb3?Y\xc0G\x8f(g\x8b\x13\x00\x01\x10\x00\x01\x10\ +\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\ +\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\x10\x00\x01\ +\x10\x00\x01\x10\x00\x01\x10@\x80\x00q\x1b\x04@\x00\x04\ +\xf0\x0e\x10\xdfA\x00\x04@\x00\x04\x10\xeb\xf1\x17\xe9\x89\ +Gh\xda\x1b|\x00\x00\x00\x00\x00IEND\xaeB\ +`\x82\ " qt_resource_name = b"\ @@ -721,17 +721,17 @@ qt_resource_name = b"\ \x00s\ \x00i\x00d\x00e\x004\x00.\x00p\x00n\x00g\ \x00\x09\ -\x0a\x86\xa4\xa7\ +\x0a\x89\xa4\xa7\ \x00s\ -\x00i\x00d\x00e\x003\x00.\x00p\x00n\x00g\ +\x00i\x00d\x00e\x006\x00.\x00p\x00n\x00g\ \x00\x09\ \x0a\x85\xa4\xa7\ \x00s\ \x00i\x00d\x00e\x002\x00.\x00p\x00n\x00g\ \x00\x09\ -\x0a\x89\xa4\xa7\ +\x0a\x86\xa4\xa7\ \x00s\ -\x00i\x00d\x00e\x006\x00.\x00p\x00n\x00g\ +\x00i\x00d\x00e\x003\x00.\x00p\x00n\x00g\ " qt_resource_struct = b"\ @@ -740,17 +740,17 @@ qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01e\xaf\x16\xd2\x95\ -\x00\x00\x00r\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x1c\ -\x00\x00\x01e\xaf\x16\xd2\x95\ -\x00\x00\x00Z\x00\x00\x00\x00\x00\x01\x00\x00\x11\x05\ -\x00\x00\x01e\xaf\x16\xd2\x95\ +\x00\x00\x01z\xe7\xee&\xf9\ +\x00\x00\x00r\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x97\ +\x00\x00\x01z\xe7\xee&\xf9\ +\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00!\x83\ +\x00\x00\x01z\xe7\xee&\xf9\ \x00\x00\x00B\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xc3\ -\x00\x00\x01e\xaf\x16\xd2\x95\ +\x00\x00\x01z\xe7\xee&\xf9\ \x00\x00\x00*\x00\x00\x00\x00\x00\x01\x00\x00\x04\x18\ -\x00\x00\x01e\xaf\x16\xd2\x95\ -\x00\x00\x00\x8a\x00\x00\x00\x00\x00\x01\x00\x00!\x08\ -\x00\x00\x01e\xaf\x16\xd2\x95\ +\x00\x00\x01z\xe7\xee&\xf9\ +\x00\x00\x00Z\x00\x00\x00\x00\x00\x01\x00\x00\x11\x05\ +\x00\x00\x01z\xe7\xee&\xf9\ " def qInitResources(): diff --git a/examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.png b/examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.png Binary files differnew file mode 100644 index 000000000..263d7a3d1 --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.png diff --git a/examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.rst b/examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.rst new file mode 100644 index 000000000..79e13cf60 --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.rst @@ -0,0 +1,9 @@ +Threaded QOpenGLWidget Example +============================== + +The threaded QOpenGLWidget example demonstrates OpenGL rendering +in separate threads. + +.. image:: threadedqopenglwidget.png + :width: 400 + :alt: Threaded QOpenGLWidget Example Screenshot diff --git a/examples/opengl/threadedqopenglwidget/glwidget.py b/examples/opengl/threadedqopenglwidget/glwidget.py new file mode 100644 index 000000000..edb88e77c --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/glwidget.py @@ -0,0 +1,79 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + + +from PySide6.QtOpenGLWidgets import QOpenGLWidget +from PySide6.QtCore import QMutexLocker, QSize, QThread, Slot, Signal + +from renderer import Renderer + + +class GLWidget(QOpenGLWidget): + + render_requested = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + + self.aboutToCompose.connect(self.on_about_to_compose) + self.frameSwapped.connect(self.on_frame_swapped) + self.aboutToResize.connect(self.on_about_to_resize) + self.resized.connect(self.on_resized) + + self._thread = QThread() + self._renderer = Renderer(self) + self._renderer.moveToThread(self._thread) + self._thread.finished.connect(self._renderer.deleteLater) + + self.render_requested.connect(self._renderer.render) + self._renderer.context_wanted.connect(self.grab_context) + + self._thread.start() + + def stop_rendering(self): + self._renderer.prepare_exit() + self._thread.quit() + self._thread.wait() + self._thread = None + self._renderer = None + + def closeEvent(self, event): + self.stop_rendering() + event.accept() + + def paintEvent(self, event): + pass + + def sizeHint(self): + return QSize(200, 200) + + @Slot() + def on_about_to_compose(self): + # We are on the gui thread here. Composition is about to + # begin. Wait until the render thread finishes. + self._renderer.lock_renderer() + + @Slot() + def on_frame_swapped(self): + self._renderer.unlock_renderer() + # Assuming a blocking swap, our animation is driven purely by the + # vsync in self example. + self.render_requested.emit() + + @Slot() + def on_about_to_resize(self): + self._renderer.lock_renderer() + + @Slot() + def on_resized(self): + self._renderer.unlock_renderer() + + @Slot() + def grab_context(self): + if not self._renderer: + return + self._renderer.lock_renderer() + with QMutexLocker(self._renderer.grab_mutex()): + self.context().moveToThread(self._thread) + self._renderer.grab_condition().wakeAll() + self._renderer.unlock_renderer() diff --git a/examples/opengl/threadedqopenglwidget/main.py b/examples/opengl/threadedqopenglwidget/main.py new file mode 100644 index 000000000..2a379da27 --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/main.py @@ -0,0 +1,93 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the Threaded QOpenGLWidget Example from Qt v6.x""" + +import sys + +from argparse import ArgumentParser, RawTextHelpFormatter + +from PySide6.QtWidgets import QApplication, QMessageBox +from PySide6.QtGui import QShortcut, QSurfaceFormat +from PySide6.QtCore import QCoreApplication, QPoint, qVersion, Qt + +try: + from OpenGL import GL +except ImportError: + app = QApplication(sys.argv) + message = "PyOpenGL must be installed to run this example." + message_box = QMessageBox(QMessageBox.Critical, + "Threaded QOpenGLWidget Example", + message, QMessageBox.Close) + detail = "Run:\npip install PyOpenGL PyOpenGL_accelerate" + message_box.setDetailedText(detail) + message_box.exec() + sys.exit(1) + +from glwidget import GLWidget +from mainwindow import MainWindow + + +if __name__ == "__main__": + app = QApplication(sys.argv) + + desc = "Qt Threaded QOpenGLWidget Example" + parser = ArgumentParser(description=desc, + formatter_class=RawTextHelpFormatter) + parser.add_argument("--single", "-s", action="store_true", + help="Single thread") + options = parser.parse_args() + + QCoreApplication.setApplicationName(desc) + QCoreApplication.setOrganizationName("QtProject") + QCoreApplication.setApplicationVersion(qVersion()) + + format = QSurfaceFormat() + format.setDepthBufferSize(16) + QSurfaceFormat.setDefaultFormat(format) + + # Two top-level windows with two QOpenGLWidget children in each. The + # rendering for the four QOpenGLWidgets happens on four separate threads. + + top_gl_widget = GLWidget() + pos = top_gl_widget.screen().availableGeometry().topLeft() + pos += QPoint(200, 200) + top_gl_widget.setWindowTitle(desc + " top level") + top_gl_widget.move(pos) + top_gl_widget.show() + + functions = top_gl_widget.context().functions() + vendor = functions.glGetString(GL.GL_VENDOR) + renderer = functions.glGetString(GL.GL_RENDERER) + gl_info = f"{vendor}/f{renderer}" + + supports_threading = ("nouveau" not in gl_info and "ANGLE" not in gl_info + and "llvmpipe" not in gl_info) + tool_tip = gl_info + if not supports_threading: + tool_tip += "\ndoes not support threaded OpenGL." + top_gl_widget.setToolTip(tool_tip) + print(tool_tip) + + close_shortcut = QShortcut(Qt.CTRL | Qt.Key_Q, top_gl_widget) + close_shortcut.activated.connect(QApplication.closeAllWindows) + close_shortcut.setContext(Qt.ApplicationShortcut) + + mw1 = None + mw2 = None + + if not options.single and supports_threading: + pos += QPoint(100, 100) + mw1 = MainWindow() + mw1.setToolTip(tool_tip) + mw1.move(pos) + mw1.setWindowTitle(f"{desc} #1") + mw1.show() + pos += QPoint(100, 100) + mw2 = MainWindow() + mw2.setToolTip(tool_tip) + mw2.move(pos) + mw2.setWindowTitle(f"{desc} #2") + mw2.show() + + sys.exit(app.exec()) diff --git a/examples/opengl/threadedqopenglwidget/mainwindow.py b/examples/opengl/threadedqopenglwidget/mainwindow.py new file mode 100644 index 000000000..4999b799d --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/mainwindow.py @@ -0,0 +1,24 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtWidgets import QWidget, QHBoxLayout + +from glwidget import GLWidget + + +class MainWindow(QWidget): + def __init__(self): + super().__init__() + + layout = QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self._glwidget1 = GLWidget(self) + layout.addWidget(self._glwidget1) + self._glwidget2 = GLWidget(self) + layout.addWidget(self._glwidget2) + + def closeEvent(self, event): + self._glwidget1.stop_rendering() + self._glwidget2.stop_rendering() + event.accept() 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) diff --git a/examples/opengl/threadedqopenglwidget/threadedqopenglwidget.pyproject b/examples/opengl/threadedqopenglwidget/threadedqopenglwidget.pyproject new file mode 100644 index 000000000..3faba3113 --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/threadedqopenglwidget.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py", "glwidget.py", "mainwindow.py", "renderer.py"] +} |