diff options
Diffstat (limited to 'examples/quick/scenegraph')
11 files changed, 463 insertions, 0 deletions
diff --git a/examples/quick/scenegraph/openglunderqml/doc/openglunderqml.rst b/examples/quick/scenegraph/openglunderqml/doc/openglunderqml.rst new file mode 100644 index 000000000..6a89a99d9 --- /dev/null +++ b/examples/quick/scenegraph/openglunderqml/doc/openglunderqml.rst @@ -0,0 +1,21 @@ +OpenGL under QML Squircle +========================= + +The OpenGL under QML example shows how an application can make use of the +QQuickWindow::beforeRendering() signal to draw custom OpenGL content under a Qt +Quick scene. This signal is emitted at the start of every frame, before the +scene graph starts its rendering, thus any OpenGL draw calls that are made as +a response to this signal, will stack under the Qt Quick items. + +As an alternative, applications that wish to render OpenGL content on top of +the Qt Quick scene, can do so by connecting to the +QQuickWindow::afterRendering() signal. + +In this example, we will also see how it is possible to have values that are +exposed to QML which affect the OpenGL rendering. We animate the threshold +value using a NumberAnimation in the QML file and this value is used by the +OpenGL shader program that draws the squircles. + +.. image:: squircle.png + :width: 400 + :alt: Squircle Screenshot diff --git a/examples/quick/scenegraph/openglunderqml/doc/squircle.png b/examples/quick/scenegraph/openglunderqml/doc/squircle.png Binary files differnew file mode 100644 index 000000000..c099a6b7e --- /dev/null +++ b/examples/quick/scenegraph/openglunderqml/doc/squircle.png diff --git a/examples/quick/scenegraph/openglunderqml/main.py b/examples/quick/scenegraph/openglunderqml/main.py new file mode 100644 index 000000000..0e24877bd --- /dev/null +++ b/examples/quick/scenegraph/openglunderqml/main.py @@ -0,0 +1,27 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +from pathlib import Path + +from PySide6.QtCore import QUrl +from PySide6.QtGui import QGuiApplication +from PySide6.QtQuick import QQuickView, QQuickWindow, QSGRendererInterface + +from squircle import Squircle # noqa: F401 + +if __name__ == "__main__": + app = QGuiApplication(sys.argv) + + QQuickWindow.setGraphicsApi(QSGRendererInterface.OpenGL) + + view = QQuickView() + view.setResizeMode(QQuickView.SizeRootObjectToView) + qml_file = Path(__file__).parent / "main.qml" + view.setSource(QUrl.fromLocalFile(qml_file)) + + if view.status() == QQuickView.Error: + sys.exit(-1) + view.show() + + sys.exit(app.exec()) diff --git a/examples/quick/scenegraph/openglunderqml/main.qml b/examples/quick/scenegraph/openglunderqml/main.qml new file mode 100644 index 000000000..73bfa3262 --- /dev/null +++ b/examples/quick/scenegraph/openglunderqml/main.qml @@ -0,0 +1,39 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import OpenGLUnderQML + +Item { + + width: 320 + height: 480 + + Squircle { + SequentialAnimation on t { + NumberAnimation { to: 1; duration: 2500; easing.type: Easing.InQuad } + NumberAnimation { to: 0; duration: 2500; easing.type: Easing.OutQuad } + loops: Animation.Infinite + running: true + } + } + Rectangle { + color: Qt.rgba(1, 1, 1, 0.7) + radius: 10 + border.width: 1 + border.color: "white" + anchors.fill: label + anchors.margins: -10 + } + + Text { + id: label + color: "black" + wrapMode: Text.WordWrap + text: "The background here is a squircle rendered with raw OpenGL using the 'beforeRender()' signal in QQuickWindow. This text label and its border is rendered using QML" + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.margins: 20 + } +} diff --git a/examples/quick/scenegraph/openglunderqml/openglunderqml.pyproject b/examples/quick/scenegraph/openglunderqml/openglunderqml.pyproject new file mode 100644 index 000000000..e7cfbc570 --- /dev/null +++ b/examples/quick/scenegraph/openglunderqml/openglunderqml.pyproject @@ -0,0 +1,3 @@ +{ + "files": [ "main.py", "main.qml", "squircle.py", "squirclerenderer.py"] +} diff --git a/examples/quick/scenegraph/openglunderqml/squircle.py b/examples/quick/scenegraph/openglunderqml/squircle.py new file mode 100644 index 000000000..d2900198b --- /dev/null +++ b/examples/quick/scenegraph/openglunderqml/squircle.py @@ -0,0 +1,79 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Property, QRunnable, Qt, Signal, Slot +from PySide6.QtQml import QmlElement +from PySide6.QtQuick import QQuickItem, QQuickWindow + +from squirclerenderer import SquircleRenderer + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "OpenGLUnderQML" +QML_IMPORT_MAJOR_VERSION = 1 + + +class CleanupJob(QRunnable): + def __init__(self, renderer): + super().__init__() + self._renderer = renderer + + def run(self): + del self._renderer + + +@QmlElement +class Squircle(QQuickItem): + + tChanged = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self._t = 0.0 + self._renderer = None + self.windowChanged.connect(self.handleWindowChanged) + + def t(self): + return self._t + + def setT(self, value): + if self._t == value: + return + self._t = value + self.tChanged.emit() + if self.window(): + self.window().update() + + @Slot(QQuickWindow) + def handleWindowChanged(self, win): + if win: + win.beforeSynchronizing.connect(self.sync, type=Qt.DirectConnection) + win.sceneGraphInvalidated.connect(self.cleanup, type=Qt.DirectConnection) + win.setColor(Qt.black) + self.sync() + + @Slot() + def cleanup(self): + del self._renderer + self._renderer = None + + @Slot() + def sync(self): + window = self.window() + if not self._renderer: + self._renderer = SquircleRenderer() + window.beforeRendering.connect(self._renderer.init, Qt.DirectConnection) + window.beforeRenderPassRecording.connect( + self._renderer.paint, Qt.DirectConnection + ) + self._renderer.setViewportSize(window.size() * window.devicePixelRatio()) + self._renderer.setT(self._t) + self._renderer.setWindow(window) + + def releaseResources(self): + self.window().scheduleRenderJob( + CleanupJob(self._renderer), QQuickWindow.BeforeSynchronizingStage + ) + self._renderer = None + + t = Property(float, t, setT, notify=tChanged) diff --git a/examples/quick/scenegraph/openglunderqml/squirclerenderer.py b/examples/quick/scenegraph/openglunderqml/squirclerenderer.py new file mode 100644 index 000000000..d824f96ab --- /dev/null +++ b/examples/quick/scenegraph/openglunderqml/squirclerenderer.py @@ -0,0 +1,98 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from textwrap import dedent + +import numpy as np +from OpenGL.GL import (GL_ARRAY_BUFFER, GL_BLEND, GL_DEPTH_TEST, GL_FLOAT, + GL_ONE, GL_SRC_ALPHA, GL_TRIANGLE_STRIP) +from PySide6.QtCore import QSize, Slot +from PySide6.QtGui import QOpenGLFunctions +from PySide6.QtOpenGL import QOpenGLShader, QOpenGLShaderProgram +from PySide6.QtQuick import QQuickWindow, QSGRendererInterface + +VERTEX_SHADER = dedent( + """\ + attribute highp vec4 vertices; + varying highp vec2 coords; + void main() { + gl_Position = vertices; + coords = vertices.xy; + } + """ +) +FRAGMENT_SHADER = dedent( + """\ + uniform lowp float t; + varying highp vec2 coords; + void main() { + lowp float i = 1. - (pow(abs(coords.x), 4.) + pow(abs(coords.y), 4.)); + i = smoothstep(t - 0.8, t + 0.8, i); + i = floor(i * 20.) / 20.; + gl_FragColor = vec4(coords * .5 + .5, i, i); + } + """ +) + + +class SquircleRenderer(QOpenGLFunctions): + def __init__(self): + QOpenGLFunctions.__init__(self) + self._viewport_size = QSize() + self._t = 0.0 + self._program = None + self._window = QQuickWindow() + + def setT(self, t): + self._t = t + + def setViewportSize(self, size): + self._viewport_size = size + + def setWindow(self, window): + self._window = window + + @Slot() + def init(self): + if not self._program: + rif = self._window.rendererInterface() + assert (rif.graphicsApi() == QSGRendererInterface.OpenGL) + self.initializeOpenGLFunctions() + self._program = QOpenGLShaderProgram() + self._program.addCacheableShaderFromSourceCode(QOpenGLShader.Vertex, VERTEX_SHADER) + self._program.addCacheableShaderFromSourceCode(QOpenGLShader.Fragment, FRAGMENT_SHADER) + self._program.bindAttributeLocation("vertices", 0) + self._program.link() + + @Slot() + def paint(self): + # Play nice with the RHI. Not strictly needed when the scenegraph uses + # OpenGL directly. + self._window.beginExternalCommands() + + self._program.bind() + + self._program.enableAttributeArray(0) + + values = np.array([-1, -1, 1, -1, -1, 1, 1, 1], dtype="single") + + # This example relies on (deprecated) client-side pointers for the vertex + # input. Therefore, we have to make sure no vertex buffer is bound. + self.glBindBuffer(GL_ARRAY_BUFFER, 0) + + self._program.setAttributeArray(0, GL_FLOAT, values, 2) + self._program.setUniformValue1f("t", self._t) + + self.glViewport(0, 0, self._viewport_size.width(), self._viewport_size.height()) + + self.glDisable(GL_DEPTH_TEST) + + self.glEnable(GL_BLEND) + self.glBlendFunc(GL_SRC_ALPHA, GL_ONE) + + self.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4) + + self._program.disableAttributeArray(0) + self._program.release() + + self._window.endExternalCommands() diff --git a/examples/quick/scenegraph/scenegraph_customgeometry/doc/scenegraph_customgeometry.rst b/examples/quick/scenegraph/scenegraph_customgeometry/doc/scenegraph_customgeometry.rst new file mode 100644 index 000000000..190ab80c2 --- /dev/null +++ b/examples/quick/scenegraph/scenegraph_customgeometry/doc/scenegraph_customgeometry.rst @@ -0,0 +1,7 @@ +Scene Graph - Custom Geometry +============================= + +The custom geometry example shows how to create a QQuickItem which uses the +scene graph API to build a custom geometry for the scene graph. It does this +by creating a BezierCurve item which is made part of the CustomGeometry module +and makes use of this in a QML file. diff --git a/examples/quick/scenegraph/scenegraph_customgeometry/main.py b/examples/quick/scenegraph/scenegraph_customgeometry/main.py new file mode 100644 index 000000000..60a904065 --- /dev/null +++ b/examples/quick/scenegraph/scenegraph_customgeometry/main.py @@ -0,0 +1,152 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +"""PySide6 port of the Qt Quick customgeometry example from Qt v6.x""" + +import sys +from pathlib import Path + +from PySide6.QtQuick import (QQuickView, QQuickItem, QSGNode, QSGGeometryNode, + QSGGeometry, QSGFlatColorMaterial) +from PySide6.QtQml import QmlElement +from PySide6.QtGui import QGuiApplication, QColor +from PySide6.QtCore import (QPointF, QUrl, Property, Signal, Slot) + +# To be used on the @QmlElement decorator +# (QML_IMPORT_MINOR_VERSION is optional) +QML_IMPORT_NAME = "CustomGeometry" +QML_IMPORT_MAJOR_VERSION = 1 + + +@QmlElement +class BezierCurve(QQuickItem): + p1Changed = Signal() + p2Changed = Signal() + p3Changed = Signal() + p4Changed = Signal() + segmentCountChanged = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + + self._p1 = QPointF(0, 0) + self._p2 = QPointF(1, 0) + self._p3 = QPointF(0, 1) + self._p4 = QPointF(1, 1) + self._segmentCount = 32 + + self._node = None + self._geometry = None + self.setFlag(QQuickItem.Flags.ItemHasContents, True) + + def p1(self): + return self._p1 + + def p2(self): + return self._p2 + + def p3(self): + return self._p3 + + def p4(self): + return self._p4 + + def segmentCount(self): + return self._segmentCount + + @Slot(QPointF) + def setP1(self, p): + if p != self._p1: + self._p1 = p + self.p1Changed.emit() + self.update() + + @Slot(QPointF) + def setP2(self, p): + if p != self._p2: + self._p2 = p + self.p2Changed.emit() + self.update() + + @Slot(QPointF) + def setP3(self, p): + if p != self._p3: + self._p3 = p + self.p3Changed.emit() + self.update() + + @Slot(QPointF) + def setP4(self, p): + if p != self._p4: + self._p4 = p + self.p4Changed.emit() + self.update() + + @Slot(int) + def setSegmentCount(self, p): + if p != self._segmentCount: + self._segmentCount = p + self.segmentCountChanged.emit() + self.update() + + def updatePaintNode(self, oldNode, updatePaintNodeData): + self._node = oldNode + if not self._node: + self._default_attributes = QSGGeometry.defaultAttributes_Point2D() + self._geometry = QSGGeometry(self._default_attributes, self._segmentCount) + self._geometry.setLineWidth(2) + self._geometry.setDrawingMode(QSGGeometry.DrawingMode.DrawLineStrip) + + self._node = QSGGeometryNode() + self._node.setGeometry(self._geometry) + self._node.setFlag(QSGNode.Flags.OwnsGeometry) + self._material = QSGFlatColorMaterial() + self._material.setColor(QColor(255, 0, 0)) + self._node.setMaterial(self._material) + self._node.setFlag(QSGNode.Flags.OwnsMaterial) + else: + self._geometry = self._node.geometry() + self._geometry.allocate(self._segmentCount) + + item_size = self.size() + item_width = float(item_size.width()) + item_height = float(item_size.height()) + vertices = self._geometry.vertexDataAsPoint2D() + for i in range(self._segmentCount): + t = float(i) / float(self._segmentCount - 1) + inv_t = 1 - t + pos = ((inv_t * inv_t * inv_t * self._p1) + + (3 * inv_t * inv_t * t * self._p2) + + (3 * inv_t * t * t * self._p3) + + (t * t * t * self._p4)) + vertices[i].set(pos.x() * item_width, pos.y() * item_height) + + self._geometry.setVertexDataAsPoint2D(vertices) + + self._node.markDirty(QSGNode.DirtyGeometry) + return self._node + + p1 = Property(QPointF, p1, setP1, notify=p1Changed) + p2 = Property(QPointF, p2, setP2, notify=p2Changed) + p3 = Property(QPointF, p3, setP3, notify=p3Changed) + p4 = Property(QPointF, p4, setP4, notify=p4Changed) + + segmentCount = Property(int, segmentCount, setSegmentCount, + notify=segmentCountChanged) + + +if __name__ == "__main__": + app = QGuiApplication([]) + view = QQuickView() + format = view.format() + format.setSamples(16) + view.setFormat(format) + + qml_file = Path(__file__).parent / "main.qml" + view.setSource(QUrl.fromLocalFile(qml_file)) + if not view.rootObject(): + sys.exit(-1) + view.show() + ex = app.exec() + del view + sys.exit(ex) diff --git a/examples/quick/scenegraph/scenegraph_customgeometry/main.qml b/examples/quick/scenegraph/scenegraph_customgeometry/main.qml new file mode 100644 index 000000000..88431a176 --- /dev/null +++ b/examples/quick/scenegraph/scenegraph_customgeometry/main.qml @@ -0,0 +1,34 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import QtQuick +import CustomGeometry + +Item { + width: 300 + height: 200 + + BezierCurve { + id: line + anchors.fill: parent + anchors.margins: 20 + property real t + SequentialAnimation on t { + NumberAnimation { to: 1; duration: 2000; easing.type: Easing.InOutQuad } + NumberAnimation { to: 0; duration: 2000; easing.type: Easing.InOutQuad } + loops: Animation.Infinite + } + + p2: Qt.point(t, 1 - t) + p3: Qt.point(1 - t, t) + } + + Text { + anchors.bottom: line.bottom + x: 20 + width: parent.width - 40 + wrapMode: Text.WordWrap + + text: "This curve is a custom scene graph item, implemented using GL_LINE_STRIP" + } +} diff --git a/examples/quick/scenegraph/scenegraph_customgeometry/scenegraph_customgeometry.pyproject b/examples/quick/scenegraph/scenegraph_customgeometry/scenegraph_customgeometry.pyproject new file mode 100644 index 000000000..a5247ef6c --- /dev/null +++ b/examples/quick/scenegraph/scenegraph_customgeometry/scenegraph_customgeometry.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py","main.qml"] +} |