QML下的OpenGL Squircle#
QML下的OpenGL示例显示了如何应用使用QQuickWindow::beforeRendering()信号在Qt Quick场景下绘制自定义OpenGL内容。此信号在每个框架开始时发出,在场景图开始渲染之前,因此对响应此信号而做出的任何OpenGL绘图调用都将堆叠在Qt Quick项目之下。
作为替代方案,希望将OpenGL内容渲染在Qt Quick场景之上应用程序可以通过连接到QQuickWindow::afterRendering()信号来做到这一点。
在这个示例中,我们还将看到如何让值暴露给QML,这些值会影响OpenGL渲染。我们使用QML文件中的NumberAnimation动画阈值值,该值由绘制圆圈的OpenGL着色器程序使用。
# 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())
// 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
}
}
# 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)
# 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()