Scene Graph - OpenGL sous QML
Montre comment effectuer un rendu OpenGL sous une scène Qt Quick.

L'exemple OpenGL sous QML montre comment une application peut utiliser le signal QQuickWindow::beforeRendering() pour dessiner un contenu OpenGL personnalisé sous une scène Qt Quick. Ce signal est émis au début de chaque image, avant que le graphique de la scène ne commence son rendu, ainsi tous les appels de dessin OpenGL qui sont faits en réponse à ce signal, s'empileront sous les éléments Qt Quick.
Comme alternative, les applications qui souhaitent rendre un contenu OpenGL au-dessus de la scène Qt Quick peuvent le faire en se connectant au signal QQuickWindow::afterRendering().
Dans cet exemple, nous verrons également comment il est possible d'avoir des valeurs exposées à QML qui affectent le rendu OpenGL. Nous animons la valeur du seuil en utilisant un NumberAnimation dans le fichier QML et cette valeur est utilisée par le programme shader OpenGL qui dessine les écureuils.
L'exemple est équivalent dans la plupart des cas aux exemples Direct3D 11 Under QML, Metal Under QML, et Vulkan Under QML, ils rendent tous le même contenu personnalisé, juste via des API natives différentes.
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; };
Tout d'abord, nous avons besoin d'un objet que nous pouvons exposer à QML. Il s'agit d'une sous-classe de QQuickItem, ce qui nous permet d'accéder facilement à QQuickItem::window(). Nous l'exposons à QML en utilisant la macro QML_ELEMENT.
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; };
Ensuite, nous avons besoin d'un objet qui s'occupe du rendu. Cette instance doit être séparée de QQuickItem parce que l'élément vit dans le fil d'exécution de l'interface graphique et que le rendu se produit potentiellement dans le fil d'exécution du rendu. Puisque nous voulons nous connecter à QQuickWindow::beforeRendering(), nous faisons du moteur de rendu un QObject. Le moteur de rendu contient une copie de tout l'état dont il a besoin, indépendamment du fil d'exécution de l'interface graphique.
Remarque : ne soyez pas tenté de fusionner les deux objets en un seul. Les QQuickItems peuvent être supprimés sur le fil d'exécution de l'interface graphique pendant que le fil d'exécution du rendu effectue le rendu.
Passons maintenant à l'implémentation.
Squircle::Squircle() : m_t(0) , m_renderer(nullptr) { connect(this, &QQuickItem::windowChanged, this, &Squircle::handleWindowChanged); }
Le constructeur de la classe Squircle initialise simplement les valeurs et se connecte au signal de changement de fenêtre que nous utiliserons pour préparer notre moteur de rendu.
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);
Une fois que nous avons une fenêtre, nous nous attachons au signal QQuickWindow::beforeSynchronizing() que nous utiliserons pour créer le moteur de rendu et pour y copier l'état en toute sécurité. Nous nous connectons également au signal QQuickWindow::sceneGraphInvalidated() pour gérer le nettoyage du moteur de rendu.
Remarque : Étant donné que l'objet Squircle a une affinité avec le thread GUI et que les signaux sont émis par le thread de rendu, il est crucial que les connexions soient établies avec Qt::DirectConnection. Dans le cas contraire, les slots seront invoqués sur le mauvais thread sans qu'aucun contexte OpenGL ne soit présent.
// Ensure we start with cleared to black. The squircle's blend mode relies on this. win->setColor(Qt::black); } }
Le comportement par défaut du graphe de scène est de vider le framebuffer avant le rendu. C'est une bonne chose puisque nous insérerons notre propre code de rendu après que ce clear ait été mis en attente. Veillez toutefois à ce que le tampon soit de la couleur souhaitée (noir).
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()); }
Nous utilisons la fonction sync() pour initialiser le moteur de rendu et pour copier l'état de notre élément dans le moteur de rendu. Lorsque le moteur de rendu est créé, nous connectons également les signaux QQuickWindow::beforeRendering() et QQuickWindow::beforeRenderPassRecording() aux emplacements init() et paint() du moteur de rendu.
Remarque : le signal QQuickWindow::beforeSynchronizing() est émis sur le thread de rendu alors que le thread de l'interface graphique est bloqué, il est donc possible de copier simplement la valeur sans protection supplémentaire.
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; }
Dans la fonction cleanup(), nous supprimons le moteur de rendu qui, à son tour, nettoie ses propres ressources. Ceci est complété par la réimplémentation de QQuickWindow::releaseResources() puisque la simple connexion au signal sceneGraphInvalidated() n'est pas suffisante pour gérer tous les cas.
void Squircle::setT(qreal t) { if (t == m_t) return; m_t = t; emit tChanged(); if (window()) window()->update(); }
Lorsque la valeur de t change, nous appelons QQuickWindow::update() plutôt que QQuickItem::update() parce que le premier forcerait la fenêtre entière à être redessinée, même si le graphe de scène n'a pas changé depuis la dernière image.
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(); } }
Dans la fonction init() du SquircleRenderer, nous commençons par initialiser le programme shader si ce n'est pas encore fait. Le contexte OpenGL est courant sur le thread lorsque le slot est invoqué.
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(); }
Nous utilisons le programme shader pour dessiner l'écureuil dans paint().
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(); }
La fonction main() de l'application instancie un QQuickView et lance le fichier 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 } }
Nous importons le type QML Squircle avec le nom que nous avons enregistré dans la fonction main(). Nous l'instançons ensuite et créons un fichier en cours d'exécution NumberAnimation sur sa propriété 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 } }
Nous superposons ensuite un court texte descriptif, afin qu'il soit clairement visible que nous effectuons en fait un rendu OpenGL sous notre scène Qt Quick.
© 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.