Le support d’OpenGL dans Qt 5 a été modifié pour mieux l’intégrer avec les nouveaux modules de Qt : QtQuick2 et Qt3D. Cet article présente les modifications apportées dans Qt 5.
OpenGL fonctionne via un système de versions et d'extensions. Quand on veut utiliser une fonction GL, il faut regarder dans quelles versions d'OpenGL elle est disponible. Ou l'inverse : quand on utilise une version d'OpenGL, il faut vérifier les fonctions disponibles.
Les extensions sont un complément aux versions. Elles permettent d'appeler des fonctions non disponibles dans une version donnée (par exemple, appeler une fonction de GL 4 dans GL 3).
Et bien sûr, tout cela va dépendre du matériel et des capacités de la carte graphique. (Ce qui signifie qu'avant d'utiliser une fonction, il faut vérifier que le matériel la supporte).
Bref, pas simple.
Qt propose plusieurs outils pour gérer cela.
QOpenGLFunctions fournit les fonctions de base que tout système DOIT supporter pour pouvoir faire tourner une application Qt 5. C'est donc la base minimale que l'on peut utiliser en ayant la garantie que cela fonctionne partout. (C'est en gros basé sur un sous ensemble d'OpenGL ES 2.) les classes QOpenGLFunctions_x_y, qui permettent d'utiliser une version spécifique de GL (par exemple http://doc.qt.io/qt-5/qopenglfunctions-2-0.html ou http://doc.qt.io/qt-5/qopenglfunctions-4-5-core.html). Mais il faut vérifier que le matériel supporte cela avant d'utiliser une version particulière de GL QOpenGLContext::hasExtension et QOpenGLContext::getProcAddress permettent de gérer manuellement les extensions. Idem, il faut tester le matériel. Toutes les fonctions plus haut niveau QOpenGLXxx (par exemple QOpenGLTexture, QOpenGLShaderProgram, QOpenGLFramebufferObject) permettent d'utiliser des fonctionnalités de GL sans se préoccuper des versions de GL. Ces classes vérifient en interne les fonctions GL utilisables, pour optimiser au mieux les tâches. Bref, pas simple tout cela.
Je vous conseille de prendre un cours/tutos/livre sur une version d'OpenGL pas trop ancienne, de créer un contexte Qt avec une version de GL correspondante et de bosser avec cela.
Dans un programme pro, on ne pourra bien sûr pas faire comme cela. Il faudra faire un code minimal qui fonctionne sur un maximum de matériel, puis écrire du code spécifique pour chaque fonctionnalité/matériel qui permettent d'optimiser le rendu. C'est beaucoup de boulot.
Dans Qt 4, les fonctionnalités d’OpenGL sont implémentées dans un module spécifique, QtOpenGL. Ce module était utilisé par différentes classes pour bénéficier de l’accélération matérielle. Il existe plusieurs méthodes pour activer l’accélération matérielle :
QApplication::setGraphicsSystem(“opengl”)
, dans la fonction main
par exemple ;QGraphicsView
(QtGraphics) ou QDeclarativeView
(QtQuick), utilisez la fonction setViewport(new QGLWidget)
;QPainter
directement sur un QGLWidget
, qui est une classe dérivée de QWidget
avec un contexte OpenGL ;Dans Qt 4, le support d'OpenGL était donc optionnel, dans un module dédié. Il fallait créer spécifiquement un contexte OpenGL et le passer en paramétré pour bénéficier de l’accélération matérielle.
Dans Qt 5, l'objectif a été de fournir un support minimal d'OpenGL dans QtGui, ce qui permet de l'utiliser dans tous les modules graphiques (widgets, Qt Quick) qui dépendent de QtGui.
QOpenGLXxx
appartiennent au module QtGui et fournissent les fonctionnalités de base permettant l’accélération matérielle pour toute les classes de rendu de Qt ;QWidget
n’est plus le parent de toutes les classes de rendu. Cette classe et ses dérivées (QGraphicsView
par exemple) sont transférées dans le module “widgets” ;QWidget
, la classe QDeclarativeView
devient QQuickView
et OpenGL est utilisé par défaut ;QGLWidget
et ses dérivés (les classes qui commencent par QGLXxx
). Ce module permet de conserver le code Qt 4 compatible avec Qt 5, mais ces classes sont dépréciées au profit des classes QOpenGLXxx
.
Remarque : il faut faire attention de ne pas confondre le module QtOpenGL, contenant les classes commençant par QGLXxx
, et le module QtGui, contenant les classes commençant par QOpenGLXxx
(sans t).
Ce tutoriel concerne que les classes de Qt Gui, non QtOpenGL.
Un contexte est une “zone” de travail d'OpenGL, contient les informations permettant à OpenGL de fonctionner. Peut correspondre à une fenêtre visible à l'écran, et dans ce cas, tous les rendus réalisés avec ce contexte seront visible dans cette fenêtre, ou hors écran.
initializeGL
, paintGL
et resizeGL
.QOpenGLContext* context = new QOpenGLContext(parent); context->setFormat(...); context->create(); context->makeCurrent(this); context->swapBuffers(this);
// gestion des erreurs GLenum error = GL_NO_ERROR; do { error = glGetError(); if (error != GL_NO_ERROR) // handle the error } while (error != GL_NO_ERROR); // création d'un contexte debug QSurfaceFormat format; // asks for a OpenGL 3.2 debug context using the Core profile format.setMajorVersion(3); format.setMinorVersion(2); format.setProfile(QSurfaceFormat::CoreProfile); format.setOption(QSurfaceFormat::DebugContext); QOpenGLContext *context = new QOpenGLContext; context->setFormat(format); context->create(); QOpenGLContext *ctx = QOpenGLContext::currentContext(); ctx->hasExtension(QByteArrayLiteral("GL_KHR_debug")) QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this); logger->initialize(); // initializes in the current context, i.e. ctx // read logs QList<QOpenGLDebugMessage> messages = logger->loggedMessages(); foreach (const QOpenGLDebugMessage &message, messages) qDebug() << message; // connexion directe connect(logger, &QOpenGLDebugLogger::messageLogged, receiver, &LogHandler::handleLoggedMessage); logger->startLogging(); // send message QOpenGLDebugMessage message = QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("Custom message")); logger->logMessage(message);
En fonction des extensions ARB_timer_query et EXT_timer_query extensions. Voir Déboguer avec OpenGL 4 pour les détails.
// init QOpenGLTimeMonitor::setSampleCount(n); QOpenGLTimeMonitor::create(); // définir des points de mesure QOpenGLTimeMonitor::recordSample(); // lire les résultats QOpenGLTimeMonitor::isResultAvailable(); QOpenGLTimeMonitor::waitForIntervals(); // intervals QOpenGLTimeMonitor::waitForSamples(); // raw timestamp // reset QOpenGLTimeMonitor::reset();
* OpenGL >=3.3 offers full support for all timer query functionality. * OpenGL 3.2 with the ARB_timer_query extension offers full support for all timer query functionality. * OpenGL ⇐3.2 with the EXT_timer_query extension offers limited support in that the timestamp of the GPU cannot be queried. Places where this impacts functions provided by Qt classes will be highlighted in the function documentation. * OpenGL ES 2 (and OpenGL ES 3) do not provide any support for OpenGL timer queries.
Granularité de 1 nanosecondes.
// simple QOpenGLTimerQuery::begin(); QOpenGLTimerQuery::end(); QOpenGLTimerQuery::isResultAvailable(); QOpenGLTimerQuery::waitForResult();
OpenGL cas particulier, puisque les fonctions et fonctions dispo dépend du GPU sur lequel s'exécute le programme, peut changer pour chaque utilisateur. Besoin de gestion plus dynamique.
Versions mineurs et majeur d'OpenGL, ajout de nouvelles fonctions, enums, etc. et retrait d'autres. Donc a chaque version, correspond une liste de fonctions dispo. Possibilité d'utiliser des fonctions ne correspondant pas à la version de contexte utilisée, si supporté par le GPU, en chargeant dynamiquement la fonction par un système d'extension.
Par exemple, GPU qui supporte OpenGL 4.0, création d'un contexte GL 2.0 et chargement d'une fonction de GL 3.0.
Remarque : a partir de GL 3.2, suppression des anciennes fonctions dans Core, conservée dans Compatibility .
Qt permet de gérer tout cela (création d'un contexte avec une version spécifique, chargement d'une extension). Par défaut, OpenGL ES 2 (supporté partout, même sur mobile).
QOpenGLFunctions: initializeOpenGLFunctions(); hasExtension getProcAddress QOpenGLFunctions_3_3_Core* funcs = 0; funcs = context->versionFunctions<QOpenGLFunctions_3_3_Core>(); if (!funcs) { qWarning() << "Could not obtain required OpenGL context version"; exit(1); } funcs->initializeOpenGLFunctions();
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"; // init prog QOpenGLShaderProgram *m_program; m_program = new QOpenGLShaderProgram(this); m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource); m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource); m_program->link(); m_posAttr = m_program->attributeLocation("posAttr"); m_colAttr = m_program->attributeLocation("colAttr"); m_matrixUniform = m_program->uniformLocation("matrix"); // load shader GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, 0); glCompileShader(shader); // render const qreal retinaScale = devicePixelRatio(); // iOS 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); GLfloat vertices[] = { 0.0f, 0.707f, -0.5f, -0.5f, 0.5f, -0.5f }; GLfloat colors[] = { 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f }; glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices); glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLES, 0, 3); glDisableVertexAttribArray(1); glDisableVertexAttribArray(0); m_program->release();
Vous trouverez la liste de toutes les classes QOpenGLXxx
dans la documentation de Qt.