aboutsummaryrefslogtreecommitdiffstats
path: root/examples/opengl
diff options
context:
space:
mode:
Diffstat (limited to 'examples/opengl')
-rw-r--r--examples/opengl/2dpainting.py173
-rw-r--r--examples/opengl/contextinfo.py283
-rw-r--r--examples/opengl/contextinfo/contextinfo.py266
-rw-r--r--examples/opengl/contextinfo/contextinfo.pyproject3
-rw-r--r--examples/opengl/contextinfo/doc/contextinfo.pngbin0 -> 35022 bytes
-rw-r--r--examples/opengl/contextinfo/doc/contextinfo.rst10
-rw-r--r--examples/opengl/grabber.py435
-rw-r--r--examples/opengl/hellogl.py287
-rw-r--r--examples/opengl/hellogl2.py472
-rw-r--r--examples/opengl/hellogl2/doc/hellogl2.pngbin0 -> 4143 bytes
-rw-r--r--examples/opengl/hellogl2/doc/hellogl2.rst23
-rw-r--r--examples/opengl/hellogl2/glwidget.py272
-rw-r--r--examples/opengl/hellogl2/hellogl2.pyproject3
-rw-r--r--examples/opengl/hellogl2/logo.py101
-rw-r--r--examples/opengl/hellogl2/main.py58
-rw-r--r--examples/opengl/hellogl2/mainwindow.py29
-rw-r--r--examples/opengl/hellogl2/window.py110
-rw-r--r--examples/opengl/overpainting.py385
-rw-r--r--examples/opengl/samplebuffers.py192
-rw-r--r--examples/opengl/textures/doc/textures.pngbin0 -> 38108 bytes
-rw-r--r--examples/opengl/textures/doc/textures.rst9
-rw-r--r--examples/opengl/textures/textures.py218
-rw-r--r--examples/opengl/textures/textures.pyproject3
-rw-r--r--examples/opengl/textures/textures_rc.py552
-rw-r--r--examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.pngbin0 -> 10616 bytes
-rw-r--r--examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.rst9
-rw-r--r--examples/opengl/threadedqopenglwidget/glwidget.py79
-rw-r--r--examples/opengl/threadedqopenglwidget/main.py93
-rw-r--r--examples/opengl/threadedqopenglwidget/mainwindow.py24
-rw-r--r--examples/opengl/threadedqopenglwidget/renderer.py326
-rw-r--r--examples/opengl/threadedqopenglwidget/threadedqopenglwidget.pyproject3
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
new file mode 100644
index 000000000..c7911f162
--- /dev/null
+++ b/examples/opengl/contextinfo/doc/contextinfo.png
Binary files differ
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
new file mode 100644
index 000000000..674ea9fbe
--- /dev/null
+++ b/examples/opengl/hellogl2/doc/hellogl2.png
Binary files differ
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
new file mode 100644
index 000000000..ff80a7d8f
--- /dev/null
+++ b/examples/opengl/textures/doc/textures.png
Binary files differ
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
new file mode 100644
index 000000000..263d7a3d1
--- /dev/null
+++ b/examples/opengl/threadedqopenglwidget/doc/threadedqopenglwidget.png
Binary files differ
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"]
+}