Szenengraph – OpenGL unter QML
Zeigt, wie OpenGL in einer „ Qt Quick “-Szene gerendert wird.
Qt Quick-Szene mit Textüberlagerung" src="images/openglunderqml-example.jpg" title="Animiertes „Squircle“, gerendert mit OpenGL in einer Qt Quick-Szene mit Textüberlagerung"/>
Das Beispiel „OpenGL unter QML“ veranschaulicht, wie eine Anwendung das Signal „ QQuickWindow::beforeRendering()“ nutzen kann, um benutzerdefinierte OpenGL-Inhalte unter einer „ Qt Quick “-Szene zu zeichnen. Dieses Signal wird zu Beginn jedes Frames ausgesendet, bevor der Szenengraph mit dem Rendern beginnt. Daher werden alle OpenGL-Zeichenaufrufe, die als Reaktion auf dieses Signal erfolgen, unter den „ Qt Quick “-Elementen gestapelt.
Alternativ können Anwendungen, die OpenGL-Inhalte über der „ Qt Quick “-Szene rendern möchten, dies tun, indem sie sich mit dem Signal „ QQuickWindow::afterRendering()“ verbinden.
In diesem Beispiel werden wir außerdem sehen, wie es möglich ist, Werte in QML bereitzustellen, die das OpenGL-Rendering beeinflussen. Wir animieren den Schwellenwert mithilfe eines „ NumberAnimation “ in der QML-Datei, und dieser Wert wird vom OpenGL-Shader-Programm verwendet, das die „Squircles“ zeichnet.
Das Beispiel entspricht in vielerlei Hinsicht den Beispielen „Direct3D 11 unter QML“, „Metal unter QML“ und „Vulkan unter QML“ – sie alle rendern denselben benutzerdefinierten Inhalt, 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;
};Zunächst benötigen wir ein Objekt, das wir für QML verfügbar machen können. Es handelt sich um eine Unterklasse von ` QQuickItem `, sodass wir problemlos auf ` QQuickItem::window()` zugreifen können. Wir machen es für QML mithilfe des Makros ` QML_ELEMENT ` verfügbar.
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;
QOpenGLBuffer m_vbo;
};Dann benötigen wir ein Objekt, das sich um das Rendern kümmert. Diese Instanz muss vom ` QQuickItem ` getrennt sein, da das Element im GUI-Thread läuft und das Rendern möglicherweise im Render-Thread erfolgt. Da wir eine Verbindung zu ` QQuickWindow::beforeRendering()` herstellen wollen, machen wir den Renderer zu einem ` QObject`. Der Renderer enthält eine Kopie des gesamten benötigten Zustands, unabhängig vom GUI-Thread.
Hinweis: Versuchen Sienicht , die beiden Objekte zu einem einzigen zusammenzufassen. QQuickItems können im GUI-Thread gelöscht werden, während der Render-Thread gerade 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 lediglich die Werte und verbindet sich mit dem Signal „changed“ des Fensters, 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 Signal „ QQuickWindow::beforeSynchronizing()“, das wir nutzen werden, um den Renderer zu erstellen und den Zustand sicher in ihn zu kopieren. Außerdem verbinden wir uns mit dem Signal „ QQuickWindow::sceneGraphInvalidated()“, um die Bereinigung des Renderers zu übernehmen.
Hinweis: Da das Squircle-Objekt an den GUI-Thread gebunden ist und die Signale vom Rendering-Thread ausgesendet werden, ist es entscheidend, dass die Verbindungen über „ Qt::DirectConnection “ hergestellt werden. Andernfalls 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 besteht darin, den Framebuffer vor dem Rendern zu löschen. Das ist in Ordnung, da wir unseren eigenen Rendercode einfügen, nachdem dieser Löschvorgang in die Warteschlange gestellt wurde. Stellen Sie jedoch sicher, dass wir den Framebuffer auf die gewünschte Farbe (Schwarz) zurücksetzen.
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 Zustand unseres Elements in den Renderer zu kopieren. Beim Erstellen des Renderers verbinden wir außerdem die Signale ` QQuickWindow::beforeRendering()` und ` QQuickWindow::beforeRenderPassRecording()` mit den Slots ` init() ` und ` paint() ` des Renderers.
Hinweis: Das Signal „ QQuickWindow::beforeSynchronizing()“ wird im Rendering-Thread ausgesendet, während der GUI-Thread gesperrt ist; daher ist es sicher, den Wert einfach ohne zusätzliche Schutzmaßnahmen 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 daraufhin seine eigenen Ressourcen bereinigt. Ergänzend dazu implementieren wir „ QQuickWindow::releaseResources(...)“ neu, da das bloße Anbinden an das Signal „sceneGraphInvalidated()“ allein nicht ausreicht, um alle Fälle abzudecken.
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()` anstelle von ` QQuickItem::update()` auf, da Ersteres die Neuzeichnung des gesamten Fensters erzwingt, selbst wenn sich der Szenenbaum 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();
const float values[] = { -1, -1, 1, -1, -1, 1, 1, 1 };
m_vbo.create();
m_vbo.bind();
m_vbo.allocate(values, sizeof(values));
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
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 `SquircleRenderer` initialisieren wir zunächst das Shader-Programm, falls dies noch nicht geschehen ist. Der OpenGL-Kontext ist im 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_vbo.bind();
m_program->bind();
m_program->setUniformValue("t", (float)m_t);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr);
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);
glDisableVertexAttribArray(0);
m_program->release();
m_window->endExternalCommands();
}Wir verwenden das Shader-Programm, um das Squircle 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 ein ` 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“ unter dem Namen, den wir in der Funktion „ main() “ registriert haben. Anschließend instanziieren wir ihn und erstellen ein laufendes „ NumberAnimation “ über seine Eigenschaft „ t “.
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
}
}Anschließend fügen wir einen kurzen beschreibenden Text ein, damit deutlich erkennbar ist, dass wir tatsächlich OpenGL unter unserer „ Qt Quick “-Szene rendern.
© 2026 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.