Graphique de scène - OpenGL sous QML

Montre comment effectuer un rendu OpenGL dans une scène Qt Quick.

« Squircle » animé, rendu avec OpenGL dans une scène <span translate=Qt Quick avec superposition de texte" src="images/openglunderqml-example.jpg" title="« Squircle » animé, rendu avec OpenGL dans une scène Qt Quick avec superposition de texte"/>

L'exemple « OpenGL sous QML » montre comment une application peut utiliser le signal QQuickWindow::beforeRendering() pour dessiner du contenu OpenGL personnalisé au sein d'une scène Qt Quick. Ce signal est émis au début de chaque image, avant que le graphe de scène ne commence son rendu ; ainsi, tous les appels de dessin OpenGL effectués en réponse à ce signal s'empileront sous les éléments Qt Quick.

Sinon, les applications qui souhaitent afficher du contenu OpenGL par-dessus 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’exposer à QML des valeurs qui influencent le rendu OpenGL. Nous animons la valeur seuil à l’aide d’un NumberAnimation dans le fichier QML, et cette valeur est utilisée par le programme de shader OpenGL qui dessine les « squircles ».

Cet exemple est équivalent à bien des égards aux exemples « Direct3D 11 sous QML », « Metal sous QML » et « Vulkan sous QML » : ils affichent tous le même contenu personnalisé, mais 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 à l’aide de 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 chargé du rendu. Cette instance doit être séparée de l’ QQuickItem, car l’élément réside dans le thread de l’interface graphique et le rendu peut potentiellement s’effectuer sur le thread de rendu. Puisque nous souhaitons nous connecter à QQuickWindow::beforeRendering(), nous faisons en sorte que le rendu soit un QObject. Le rendu contient une copie de tout l’état dont il a besoin, indépendamment du thread de l’interface graphique.

Remarque : ne soyezpas tenté de fusionner les deux objets en un seul. Les `QQuickItem` peuvent être supprimés sur le thread de l’interface graphique pendant que le thread de 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 ` se contente d’initialiser les valeurs et de se connecter au signal `changed` de la 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 disposons d’une fenêtre, nous nous connectons au signal ` QQuickWindow::beforeSynchronizing()` que nous utiliserons pour créer le moteur de rendu et 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 : comme l’objet `Squircle` est associé au thread de l’interface graphique et que les signaux sont émis depuis le thread de rendu, il est essentiel que les connexions soient établies avec ` Qt::DirectConnection`. Si ce n’est pas le cas, les slots seront appelés sur le mauvais thread, sans contexte OpenGL disponible.

        // 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 consiste à effacer le tampon de trame avant le rendu. Cela ne pose pas de problème puisque nous insérerons notre propre code de rendu après la mise en file d’attente de cet effacement. Assurez-vous toutefois que l’effacement s’effectue vers 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 ce dernier. Lors de la création du moteur de rendu, nous connectons également les signaux ` QQuickWindow::beforeRendering()` et ` QQuickWindow::beforeRenderPassRecording()` aux slots ` init() ` et ` paint() ` du moteur de rendu.

Remarque : le signal QQuickWindow::beforeSynchronizing() est émis sur le thread de rendu tandis que le thread de l’interface graphique est bloqué ; il est donc sans risque de simplement copier la valeur sans aucune 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 rendu, qui se charge alors de libérer ses propres ressources. Cette opération est complétée par la réimplémentation de QQuickWindow::releaseResources(), car le simple fait de se connecter au signal sceneGraphInvalidated() ne suffit pas à lui seul pour gérer tous les cas de figure.

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()`, car la première fonction force le rafraîchissement complet de la fenêtre, 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() ` de `SquircleRenderer`, nous commençons par initialiser le programme de shader s’il ne l’est pas encore. Le contexte OpenGL est actif sur le thread lorsque le slot est appelé.

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 de shader pour dessiner le « squircle » 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 » sous le nom que nous avons enregistré dans la fonction ` main() `. Nous l’instancions ensuite et créons un ` NumberAnimation ` en cours d’exécution 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 bien un rendu OpenGL au sein de notre scène « Qt Quick ».

Exemple de projet sur code.qt.io

© 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.