Szenengraph - OpenGL unter QML
Zeigt wie man OpenGL unter einer Qt Quick Szene rendert.
Das OpenGL unter QML Beispiel zeigt, wie eine Anwendung das QQuickWindow::beforeRendering() Signal nutzen kann, um benutzerdefinierte OpenGL Inhalte unter einer Qt Quick Szene zu zeichnen. Dieses Signal wird zu Beginn eines jeden Frames ausgegeben, bevor der Szenegraph mit dem Rendering beginnt. Daher werden alle OpenGL-Zeichenaufrufe, die als Reaktion auf dieses Signal erfolgen, unter den Qt Quick Elementen gestapelt.
Als Alternative können Anwendungen, die OpenGL-Inhalte über der Szene Qt Quick rendern möchten, dies tun, indem sie sich mit dem Signal QQuickWindow::afterRendering() verbinden.
In diesem Beispiel werden wir auch sehen, wie es möglich ist, Werte zu haben, die QML ausgesetzt sind und das OpenGL-Rendering beeinflussen. Wir animieren den Schwellenwert mit NumberAnimation in der QML-Datei, und dieser Wert wird vom OpenGL-Shader-Programm verwendet, das die Eichhörnchen zeichnet.
Das Beispiel ist in den meisten Punkten äquivalent zu den Beispielen Direct3D 11 Under QML, Metal Under QML und Vulkan Under QML, die alle denselben benutzerdefinierten Inhalt rendern, nur über unterschiedliche native APIs.
class Squircle : public QQuickItem { Q_OBJECT Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged) QML_ELEMENT public: Squircle(); qreal t() const { return m_t; } void setT(qreal t); signals: void tChanged(); public slots: void sync(); void cleanup(); private slots: void handleWindowChanged(QQuickWindow *win); private: void releaseResources() override; qreal m_t; SquircleRenderer *m_renderer; };
Zuallererst benötigen wir ein Objekt, das wir QML zur Verfügung stellen können. Dies ist eine Unterklasse von QQuickItem, so dass wir problemlos auf QQuickItem::window() zugreifen können. Wir geben es mit dem Makro QML_ELEMENT an QML weiter.
class SquircleRenderer : public QObject, protected QOpenGLFunctions { Q_OBJECT public: ~SquircleRenderer(); void setT(qreal t) { m_t = t; } void setViewportSize(const QSize &size) { m_viewportSize = size; } void setWindow(QQuickWindow *window) { m_window = window; } public slots: void init(); void paint(); private: QSize m_viewportSize; qreal m_t = 0.0; QOpenGLShaderProgram *m_program = nullptr; QQuickWindow *m_window = nullptr; };
Dann brauchen wir ein Objekt, das sich um das Rendering kümmert. Diese Instanz muss von QQuickItem getrennt werden, da das Element im GUI-Thread lebt und das Rendering möglicherweise im Render-Thread stattfindet. Da wir eine Verbindung zu QQuickWindow::beforeRendering() herstellen wollen, machen wir den Renderer zu QObject. Der Renderer enthält eine Kopie des gesamten Zustands, den er benötigt, unabhängig vom GUI-Thread.
Hinweis: Lassen Sie sich nicht dazu verleiten, die beiden Objekte zu einem zu verschmelzen. QQuickItems können im GUI-Thread gelöscht werden, während der Renderer-Thread rendert.
Kommen wir nun zur Implementierung.
Squircle::Squircle() : m_t(0) , m_renderer(nullptr) { connect(this, &QQuickItem::windowChanged, this, &Squircle::handleWindowChanged); }
Der Konstruktor der Klasse Squircle
initialisiert einfach die Werte und verbindet sich mit dem Signal window changed, das wir zur Vorbereitung unseres Renderers verwenden werden.
void Squircle::handleWindowChanged(QQuickWindow *win) { if (win) { connect(win, &QQuickWindow::beforeSynchronizing, this, &Squircle::sync, Qt::DirectConnection); connect(win, &QQuickWindow::sceneGraphInvalidated, this, &Squircle::cleanup, Qt::DirectConnection);
Sobald wir ein Fenster haben, verbinden wir uns mit dem QQuickWindow::beforeSynchronizing()-Signal, das wir verwenden werden, um den Renderer zu erstellen und den Status sicher in ihn zu kopieren. Wir verbinden uns auch mit dem Signal QQuickWindow::sceneGraphInvalidated(), um den Renderer zu bereinigen.
Hinweis: Da das Squircle-Objekt eine Affinität zum GUI-Thread hat und die Signale vom Rendering-Thread ausgesendet werden, ist es wichtig, dass die Verbindungen mit Qt::DirectConnection hergestellt werden. Geschieht dies nicht, werden die Slots im falschen Thread aufgerufen, ohne dass ein OpenGL-Kontext vorhanden ist.
// Ensure we start with cleared to black. The squircle's blend mode relies on this. win->setColor(Qt::black); } }
Das Standardverhalten des Szenengraphen ist es, den Framebuffer vor dem Rendern zu löschen. Das ist in Ordnung, da wir unseren eigenen Rendering-Code einfügen werden, nachdem dieser Clear in die Warteschlange gestellt wurde. Vergewissern Sie sich jedoch, dass der Framebuffer auf die gewünschte Farbe (Schwarz) zurückgesetzt wird.
void Squircle::sync() { if (!m_renderer) { m_renderer = new SquircleRenderer(); connect(window(), &QQuickWindow::beforeRendering, m_renderer, &SquircleRenderer::init, Qt::DirectConnection); connect(window(), &QQuickWindow::beforeRenderPassRecording, m_renderer, &SquircleRenderer::paint, Qt::DirectConnection); } m_renderer->setViewportSize(window()->size() * window()->devicePixelRatio()); m_renderer->setT(m_t); m_renderer->setWindow(window()); }
Wir verwenden die Funktion sync()
, um den Renderer zu initialisieren und den Status in unserem Element in den Renderer zu kopieren. Wenn der Renderer erstellt wird, verbinden wir auch die QQuickWindow::beforeRendering() und QQuickWindow::beforeRenderPassRecording() mit den Slots init()
und paint()
des Renderers.
Hinweis: Das Signal QQuickWindow::beforeSynchronizing() wird im Rendering-Thread ausgegeben, während der GUI-Thread blockiert ist, so dass es sicher ist, den Wert einfach ohne zusätzlichen Schutz zu kopieren.
void Squircle::cleanup() { delete m_renderer; m_renderer = nullptr; } class CleanupJob : public QRunnable { public: CleanupJob(SquircleRenderer *renderer) : m_renderer(renderer) { } void run() override { delete m_renderer; } private: SquircleRenderer *m_renderer; }; void Squircle::releaseResources() { window()->scheduleRenderJob(new CleanupJob(m_renderer), QQuickWindow::BeforeSynchronizingStage); m_renderer = nullptr; } SquircleRenderer::~SquircleRenderer() { delete m_program; }
In der Funktion cleanup()
löschen wir den Renderer, der im Gegenzug seine eigenen Ressourcen aufräumt. Dies wird durch die Neuimplementierung von QQuickWindow::releaseResources() ergänzt, da die Verbindung mit dem sceneGraphInvalidated()-Signal allein nicht ausreicht, um alle Fälle zu behandeln.
void Squircle::setT(qreal t) { if (t == m_t) return; m_t = t; emit tChanged(); if (window()) window()->update(); }
Wenn sich der Wert von t
ändert, rufen wir QQuickWindow::update() und nicht QQuickItem::update() auf, da Ersteres das gesamte Fenster zum Neuzeichnen zwingt, selbst wenn sich der Szenegraph seit dem letzten Frame nicht geändert hat.
void SquircleRenderer::init() { if (!m_program) { QSGRendererInterface *rif = m_window->rendererInterface(); Q_ASSERT(rif->graphicsApi() == QSGRendererInterface::OpenGL); initializeOpenGLFunctions(); m_program = new QOpenGLShaderProgram(); m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, "attribute highp vec4 vertices;" "varying highp vec2 coords;" "void main() {" " gl_Position = vertices;" " coords = vertices.xy;" "}"); m_program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, "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);" "}"); m_program->bindAttributeLocation("vertices", 0); m_program->link(); } }
In der Funktion init()
des SquircleRenderers beginnen wir mit der Initialisierung des Shader-Programms, falls dies noch nicht geschehen ist. Der OpenGL-Kontext ist auf dem Thread aktuell, wenn der Slot aufgerufen wird.
void SquircleRenderer::paint() { // Play nice with the RHI. Not strictly needed when the scenegraph uses // OpenGL directly. m_window->beginExternalCommands(); m_program->bind(); m_program->enableAttributeArray(0); float values[] = { -1, -1, 1, -1, -1, 1, 1, 1 }; // This example relies on (deprecated) client-side pointers for the vertex // input. Therefore, we have to make sure no vertex buffer is bound. glBindBuffer(GL_ARRAY_BUFFER, 0); m_program->setAttributeArray(0, GL_FLOAT, values, 2); m_program->setUniformValue("t", (float) m_t); glViewport(0, 0, m_viewportSize.width(), m_viewportSize.height()); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); m_program->disableAttributeArray(0); m_program->release(); m_window->endExternalCommands(); }
Wir verwenden das Shader-Programm, um den Eichhörnchenkreis in paint()
zu zeichnen.
int main(int argc, char **argv) { QGuiApplication app(argc, argv); QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); QQuickView view; view.setResizeMode(QQuickView::SizeRootObjectToView); view.setSource(QUrl("qrc:///scenegraph/openglunderqml/main.qml")); view.show(); return QGuiApplication::exec(); }
Die Funktion main()
der Anwendung instanziiert eine QQuickView und startet die Datei main.qml
.
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 } }
Wir importieren den QML-Typ "Squircle" mit dem Namen, den wir in der Funktion main()
registriert haben. Dann instanziieren wir ihn und erstellen eine laufende NumberAnimation auf seiner t
Eigenschaft.
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: qsTr("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 } }
Dann überlagern wir einen kurzen beschreibenden Text, so dass es deutlich sichtbar ist, dass wir tatsächlich OpenGL unter unserer Qt Quick Szene rendern.
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.