Sur cette page

Exemple de fenêtre OpenGL

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

Capture d'écran de l'exemple OpenGLWindow

Remarque : il s'agit d'un exemple de bas niveau de l'utilisation de QWindow avec OpenGL. En pratique, vous devriez envisager d'utiliser la classe de niveau supérieur QOpenGLWindow. Voir l'exemple Hello GLES3 pour une démonstration de la classe de commodité QOpenGLWindow.

Super classe OpenGLWindow

Notre classe OpenGLWindow agit comme une API qui est ensuite sous-classée pour effectuer le rendu réel. Elle possède des 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 OpenGLWindow peut soit réimplémenter render() pour un rendu basé sur OpenGL, soit render(QPainter *) pour un rendu avec QPainter. Utilisez OpenGLWindow::setAnimating(true) pour que render() soit appelé au taux de rafraîchissement vertical, en supposant que la synchronisation verticale soit activée dans les pilotes OpenGL sous-jacents.

Dans la classe qui effectue le rendu OpenGL, vous voudrez typiquement hériter de QOpenGLFunctions, comme le fait notre 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'il contient seront prioritaires, et vous n'aurez pas à vous soucier de la résolution de ces fonctions si vous voulez que votre application fonctionne avec OpenGL ainsi 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 doit être utilisée pour le rendu OpenGL et non pour le rendu de contenu matriciel avec QPainter en utilisant QBackingStore.

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

Toute initialisation OpenGL nécessaire peut être effectuée en surchargeant la fonction initialize(), qui est appelée une fois avant le premier appel à render(), avec un courant valide QOpenGLContext. Comme on peut le voir dans l'extrait de code suivant, les implémentations render(QPainter *) et initialize() par défaut sont vides, alors que l'implémentation render() par défaut initialise un QOpenGLPaintDevice et appelle ensuite 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 programmer une mise à jour lorsque le système est prêt à être repeint.

Nous appelons également la fonction renderNow() lorsque nous recevons un événement expose. L'événement exposeEvent() est la notification à la fenêtre que son exposition, c'est-à-dire sa visibilité, sur 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. N'effectuez pas de rendu ou n'appelez pas 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, en outre, ce qui est rendu pourrait ne 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 retournons si nous ne sommes pas actuellement exposés, auquel cas le rendu est retardé jusqu'à ce que nous recevions un événement d'exposition. Si nous ne l'avons pas encore fait, nous créons le site QOpenGLContext avec le même QSurfaceFormat que celui de l'OpenGLWindow, et appelons initialize() pour le bien de la sous classe, et initializeOpenGLFunctions() pour que la super classe QOpenGLFunctions soit associée à la bonne QOpenGLContext. Dans tous les cas, nous rendons le contexte actuel en appelant QOpenGLContext::makeCurrent(), nous appelons render() pour effectuer le rendu proprement dit, et enfin nous planifions la visibilité du contenu rendu en appelant QOpenGLContext::swapBuffers() avec l'OpenGLWindow comme paramètre.

Une fois que le rendu d'une image utilisant un contexte OpenGL est initié en appelant QOpenGLContext::makeCurrent(), en donnant la surface sur laquelle le rendu doit être effectué comme paramètre, les commandes OpenGL peuvent être émises. Les commandes peuvent être émises soit directement en incluant <qopengl.h>, qui inclut également les en-têtes OpenGL du système, soit en utilisant QOpenGLFunctions, dont on peut hériter par commodité, ou auquel on peut accéder en utilisant QOpenGLContext::functions(). QOpenGLFunctions donne accès à tous les appels OpenGL de niveau ES 2.0 qui ne sont pas déjà standard dans OpenGL ES 2.0 et OpenGL de bureau. Pour plus d'informations sur les API OpenGL et OpenGL ES, consultez le registre officiel OpenGL et le registre Khronos OpenGL ES API.

Si l'animation a été activée avec OpenGLWindow::setAnimating(true), nous appelons renderLater() pour programmer 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 demande 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 sous-classons OpenGLWindow pour montrer comment effectuer le rendu OpenGL d'un triangle en rotation. En sous-classant indirectement QOpenGLFunctions, nous accédons à toutes les fonctionnalités d'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 principale, nous initialisons QGuiApplication et instancions notre TriangleOpenGLWindow. Nous lui donnons une adresse QSurfaceFormat en spécifiant que nous voulons quatre échantillons d'anti-crénelage multisample, ainsi qu'une géométrie par défaut. Puisque nous voulons avoir de l'animation, nous appelons la fonction setAnimating() mentionnée ci-dessus avec un argument de 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 montre le programme de shaders OpenGL utilisé dans cet exemple. Les vertex et fragment shaders sont relativement simples, ils effectuent la transformation des vertex et la coloration interpolée des vertex.

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 shader En utilisant QOpenGLShaderProgram au lieu d'OpenGL brut, nous obtenons la commodité qui supprime les qualificatifs highp, mediump, et lowp sur OpenGL de bureau, où ils ne font pas partie de la norme. Nous stockons les emplacements des attributs et des uniformes dans des variables membres pour éviter d'avoir à rechercher l'emplacement à 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 de visualisation, effacer l'arrière-plan et rendre un triangle rotatif.

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