Sur cette page

Exemple de fenêtre OpenGL

Cet exemple montre comment créer une application minimale basée sur QWindow afin d'utiliser OpenGL.

Capture d'écran de l'exemple OpenGLWindow

Remarque : il s'agit d'un exemple de bas niveau illustrant l'utilisation d'QWindow avec OpenGL. En pratique, il est recommandé d'utiliser la classe de plus haut niveau QOpenGLWindow. Consultez l'exemple « Hello GLES3 » pour une démonstration de la classe de commodité QOpenGLWindow.

Superclasse OpenGLWindow

Notre classe OpenGLWindow fait office d’API qui est ensuite sous-classée pour effectuer le rendu proprement dit. Elle dispose de fonctions permettant de demander l’appel de render(), soit immédiatement avec renderNow(), soit dès que la boucle d’événements a fini de traiter le lot d’événements en cours avec renderLater(). La sous-classe d’OpenGLWindow peut soit réimplémenter render() pour un rendu basé sur OpenGL, soit render(QPainter *) pour un rendu avec un moteur de rendu QPainter. Utilisez OpenGLWindow::setAnimating(true) pour que render() soit appelé à la fréquence de rafraîchissement verticale, en supposant que la synchronisation verticale soit activée dans les pilotes OpenGL sous-jacents.

Dans la classe qui effectue le rendu OpenGL, vous souhaiterez généralement hériter d’ QOpenGLFunctions, comme le fait notre classe OpenGLWindow, afin d’obtenir un accès indépendant de la plateforme aux fonctions OpenGL ES 2.0. En héritant de ` QOpenGLFunctions `, les fonctions OpenGL qu’elle contient auront la priorité, et vous n’aurez pas à vous soucier de la résolution de ces fonctions si vous souhaitez que votre application fonctionne aussi bien avec OpenGL qu’avec OpenGL ES 2.0.

class OpenGLWindow : public QWindow, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit OpenGLWindow(QWindow *parent = nullptr);
    ~OpenGLWindow();

    virtual void render(QPainter *painter);
    virtual void render();

    virtual void initialize();

    void setAnimating(bool animating);

public slots:
    void renderLater();
    void renderNow();

protected:
    bool event(QEvent *event) override;

    void exposeEvent(QExposeEvent *event) override;

private:
    bool m_animating = false;

    QOpenGLContext *m_context = nullptr;
    QOpenGLPaintDevice *m_device = nullptr;
};

Le type de surface de la fenêtre doit être défini sur « QSurface::OpenGLSurface » pour indiquer que la fenêtre est destinée au rendu OpenGL et non au rendu de contenu raster avec QPainter à l’aide d’un « QBackingStore ».

OpenGLWindow::OpenGLWindow(QWindow *parent)
    : QWindow(parent)
{
    setSurfaceType(QWindow::OpenGLSurface);
}

Toute initialisation OpenGL nécessaire peut être effectuée en redéfinissant la fonction initialize(), qui est appelée une fois avant le premier appel à render(), avec une QOpenGLContext valide. Comme le montre l’extrait de code suivant, les implémentations par défaut de render(QPainter *) et initialize() sont vides, tandis que l’implémentation par défaut de render() initialise un QOpenGLPaintDevice puis appelle render(QPainter *).

void OpenGLWindow::render(QPainter *painter)
{
    Q_UNUSED(painter);
}

void OpenGLWindow::initialize()
{
}

void OpenGLWindow::render()
{
    if (!m_device)
        m_device = new QOpenGLPaintDevice;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    m_device->setSize(size() * devicePixelRatio());
    m_device->setDevicePixelRatio(devicePixelRatio());

    QPainter painter(m_device);
    render(&painter);
}

La fonction `renderLater()` appelle simplement ` QWindow::requestUpdate()` pour planifier une mise à jour lorsque le système est prêt à se rafraîchir.

Nous appelons également `renderNow()` lorsque nous recevons un événement `expose`. L’événement `exposeEvent()` est la notification indiquant à la fenêtre que son exposition, c’est-à-dire sa visibilité, à l’écran a changé. Lorsque l’événement `expose` est reçu, vous pouvez interroger ` QWindow::isExposed()` pour savoir si la fenêtre est actuellement exposée ou non. Ne procédez pas au rendu et n’appelez pas la méthode ` QOpenGLContext::swapBuffers()` sur une fenêtre avant qu’elle n’ait reçu son premier événement `expose`, car avant cela, sa taille finale pourrait être inconnue et, de plus, ce qui est rendu pourrait ne même pas s’afficher à l’écran.

void OpenGLWindow::renderLater()
{
    requestUpdate();
}

bool OpenGLWindow::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::UpdateRequest:
        renderNow();
        return true;
    default:
        return QWindow::event(event);
    }
}

void OpenGLWindow::exposeEvent(QExposeEvent *event)
{
    Q_UNUSED(event);

    if (isExposed())
        renderNow();
}

Dans `renderNow()`, nous sortons de la fonction si nous ne sommes pas actuellement exposés ; dans ce cas, le rendu est reporté jusqu’à ce que nous recevions effectivement un événement « expose ». Si ce n’est pas encore fait, nous créons l’ QOpenGLContext avec le même QSurfaceFormat que celui défini sur l’OpenGLWindow, puis nous appelons initialize() pour la sous-classe, ainsi que initializeOpenGLFunctions() afin que la superclasse QOpenGLFunctions soit associée au bon QOpenGLContext. Dans tous les cas, nous rendons le contexte actif en appelant ` QOpenGLContext::makeCurrent()`, nous appelons `render()` pour effectuer le rendu proprement dit, et enfin nous programmons l’affichage du contenu rendu en appelant ` QOpenGLContext::swapBuffers()` avec l’`OpenGLWindow` comme paramètre.

Une fois que le rendu d’une image à l’aide d’un contexte OpenGL est lancé en appelant ` QOpenGLContext::makeCurrent()` et en passant en paramètre la surface sur laquelle effectuer le rendu, les commandes OpenGL peuvent être émises. Ces commandes peuvent être émises soit directement en incluant <qopengl.h> (qui inclut également les en-têtes OpenGL du système), soit via QOpenGLFunctions, qui peut être héritée pour plus de commodité ou accessible à l’aide de QOpenGLContext::functions(). QOpenGLFunctions donne accès à tous les appels OpenGL de niveau OpenGL ES 2.0 qui ne sont pas encore standardisés à la fois dans OpenGL ES 2.0 et dans OpenGL pour ordinateurs de bureau. Pour plus d’informations sur les API OpenGL et OpenGL ES, consultez le registre officiel OpenGL et le registre des API OpenGL ES de Khronos.

Si l’animation a été activée avec `OpenGLWindow::setAnimating(true)`, nous appelons `renderLater()` pour planifier une autre requête de mise à jour.

void OpenGLWindow::renderNow()
{
    if (!isExposed())
        return;

    bool needsInitialize = false;

    if (!m_context) {
        m_context = new QOpenGLContext(this);
        m_context->setFormat(requestedFormat());
        m_context->create();

        needsInitialize = true;
    }

    m_context->makeCurrent(this);

    if (needsInitialize) {
        initializeOpenGLFunctions();
        initialize();
    }

    render();

    m_context->swapBuffers(this);

    if (m_animating)
        renderLater();
}

L'activation de l'animation planifie également une requête de mise à jour, comme le montre l'extrait de code suivant.

void OpenGLWindow::setAnimating(bool animating)
{
    m_animating = animating;

    if (animating)
        renderLater();
}

Exemple de sous-classe de rendu OpenGL

Ici, nous créons une sous-classe de `OpenGLWindow` pour montrer comment utiliser OpenGL afin de rendre un triangle en rotation. En créant indirectement une sous-classe de ` QOpenGLFunctions `, nous avons accès à toutes les fonctionnalités du niveau OpenGL ES 2.0.

class TriangleWindow : public OpenGLWindow
{
public:
    using OpenGLWindow::OpenGLWindow;

    void initialize() override;
    void render() override;

private:
    GLint m_matrixUniform = 0;
    QOpenGLBuffer m_vbo;
    QOpenGLShaderProgram *m_program = nullptr;
    int m_frame = 0;
};

Dans notre fonction main, nous initialisons QGuiApplication et instancions notre TriangleOpenGLWindow. Nous lui fournissons une fonction d'QSurfaceFormat e spécifiant que nous souhaitons quatre échantillons d'anti-aliasing multisample, ainsi qu'une géométrie par défaut. Comme nous souhaitons une animation, nous appelons la fonction setAnimating() mentionnée ci-dessus avec l'argument true.

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);

    QSurfaceFormat format;
    format.setSamples(16);

    TriangleWindow window;
    window.setFormat(format);
    window.resize(640, 480);
    window.show();

    window.setAnimating(true);

    return app.exec();
}

L'extrait de code suivant présente le programme de shaders OpenGL utilisé dans cet exemple. Les shaders de sommets et de fragments sont relativement simples : ils effectuent une transformation des sommets et une coloration interpolée de ceux-ci.

static const char *vertexShaderSource = "attribute highp vec4 posAttr;\n"
                                        "attribute lowp vec4 colAttr;\n"
                                        "varying lowp vec4 col;\n"
                                        "uniform highp mat4 matrix;\n"
                                        "void main() {\n"
                                        "   col = colAttr;\n"
                                        "   gl_Position = matrix * posAttr;\n"
                                        "}\n";

static const char *fragmentShaderSource = "varying lowp vec4 col;\n"
                                          "void main() {\n"
                                          "   gl_FragColor = col;\n"
                                          "}\n";

Voici le code qui charge les shaders et initialise le programme de shaders. En utilisant QOpenGLShaderProgram au lieu d’OpenGL brut, nous bénéficions de la commodité de ne pas avoir à gérer les qualificatifs highp, mediump et lowp sur OpenGL pour ordinateurs de bureau, où ils ne font pas partie de la norme. Nous stockons les emplacements des attributs et des uniformes dans des variables membres afin d’éviter d’avoir à rechercher ces emplacements à chaque image.

void TriangleWindow::initialize()
{
    static const GLfloat vertices_colors[] = { +0.0f, +0.707f, 1.0f, 0.0f, 0.0f,
                                               -0.5f, -0.500f, 0.0f, 1.0f, 0.0f,
                                               +0.5f, -0.500f, 0.0f, 0.0f, 1.0f };

    m_vbo.create();
    m_vbo.bind();
    m_vbo.allocate(vertices_colors, sizeof(vertices_colors));

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), nullptr);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat),
                          reinterpret_cast<void *>(2 * sizeof(GLfloat)));

    m_program = new QOpenGLShaderProgram(this);
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    m_program->bindAttributeLocation("posAttr", 0);
    m_program->bindAttributeLocation("colAttr", 1);
    m_program->link();
    m_program->bind();

    m_matrixUniform = m_program->uniformLocation("matrix");
    Q_ASSERT(m_matrixUniform != -1);
}

Enfin, voici notre fonction `render()`, dans laquelle nous utilisons OpenGL pour configurer la fenêtre d’affichage, effacer l’arrière-plan et afficher un triangle en rotation.

void TriangleWindow::render()
{
    const qreal retinaScale = devicePixelRatio();
    glViewport(0, 0, width() * retinaScale, height() * retinaScale);

    glClear(GL_COLOR_BUFFER_BIT);

    m_program->bind();

    QMatrix4x4 matrix;
    matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
    matrix.translate(0, 0, -2);
    matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0);

    m_program->setUniformValue(m_matrixUniform, matrix);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    m_program->release();

    ++m_frame;
}

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.