I. Moteur de rendu de Qt Quick 2▲
Pour comprendre les modes d'interaction entre du code OpenGL et Qt Quick, la première étape est d'analyser la manière dont Qt Quick 2 affiche une scène. En fait, la machinerie responsable du rendu est assez complexe ; elle utilise déjà OpenGL dans un fil d'exécution distinct pour l'affichage des éléments Qt Quick.
Premièrement, cela implique l'utilisation d'OpenGL pour l'affichage. Cette décision de conception a un impact très profond sur Qt Quick et s'appuie sur le fait qu'à peu près tout appareil a aujourd'hui un processeur graphique contrôlable par OpenGL. De plus, l'utilisation d'OpenGL est nécessaire pour effectuer des rendus à raison de soixante images par seconde de manière stable, pour ajouter des effets spéciaux (comme des ombres, des particules, des flous) ou encore pour avoir des animations fluides. Ainsi, tous les éléments visibles dans une scène Qt Quick sont affichés à l'aide de points, de lignes, de triangles et de shaders. Cette décision facilite aussi l'intégration de code OpenGL dans l'étape de rendu Qt Quick.
Deuxièmement, l'affichage est effectué dans un fil d'exécution distinct, différent du fil principal, c'est-à-dire celui qui exécute la fonction main() en C++. Cette manière de procéder a deux avantages principaux :
- Elle permet de laisser le rendu continuer et de ne pas bloquer l'interface (et notamment de garder les animations à soixante images par seconde), même si le processeur est occupé dans des calculs ou si le fil principal est arrêté dans l'appel d'une fonction bloquante ;
- De même, elle permet de laisser le fil principal se dérouler au cas où le processeur graphique serait trop lent pour l'affichage de la scène.
Ce fil d'exécution réservé à l'affichage est utilisé pour à peu près toutes les plateformes avec Qt 5.6, avec la notable exception d'ANGLE sous Windows. Même si Qt n'est pas encore compatible avec une plateforme donnée, il est probable qu'elle le soit dans une version à venir : prendre en compte ces aspects multifils pour l'intégration de code OpenGL dans le moteur de rendu de Qt Quick 2 est donc très important au risque de voir ses applications planter lamentablement sans raison apparente.
II. Graphe de scène et synchronisation▲
La vraie question n'est toujours pas posée : comment le moteur effectue-t-il le rendu d'une scène ? En réalité, il utilise une structure de données spéciale, le graphe de scène. Ce nom revient assez souvent dans le cas de l'affichage 3D : il fait référence à toute structure de données qui peut être utilisée pour analyser et optimiser certaines tâches (pas seulement l'affichage, mais aussi les simulations physiques, la recherche de chemins, etc.). Cette série ne s'attardera pas sur la manière d'utiliser ce graphe de scène pour construire de nouveaux éléments Qt Quick, puisque son objectif est de montrer l'intégration de code existant : elle détaillera seulement les parties les plus intéressantes.
Dans le monde de Qt Quick, le graphe de scène n'est utilisé que pour l'affichage. Il contient toutes les informations visuelles nécessaires pour dessiner une scène. Ce dernier point est important : toutes les informations non visuelles sont perdues lors du passage au graphe de scène. Par exemple, pour dessiner un bouton rectangulaire, le graphe de scène n'a aucune idée des réactions qu'il peut avoir lors d'un clic ; tout ce qu'il sait, c'est que, pour afficher ce bouton, il doit d'abord dessiner un quadrilatère (en réalité, deux triangles), avec certaines couleurs définies au niveau des arêtes pour simuler un dégradé dans l'arrière-plan ; ensuite, il doit dessiner un quadrilatère plus petit avec une texture (c'est-à-dire une image) qui représente le texte du bouton.
Le graphe de scène Qt Quick est implémenté comme un arbre de nœuds. Chaque type de nœud (une classe qui dérive de QSGNode, où SG signifie « graphe de scène », qui se dit scene graph en anglais) implémente une fonctionnalité particulière : modifier les transformations des enfants, leur opacité, gérer une forme géométrique (à l'aide de VBO), détenir un shader, etc. La figure suivante montre une série de classes du graphe de scène de Qt Quick.
Le graphe de scène est construit en itérant sur les éléments QML. Chaque type d'élément visuel de Qt Quick (Rectangle, Image, Text…) construit un petit arbre de nœuds dans sa version de la fonction QQuickItem::updatePaintNode et le retourne au moteur de rendu. Ce dernier peut alors prendre tous ces arbres, les analyser et décider de la meilleure manière de les afficher.
Le lecteur attentif aura remarqué une pierre d'achoppement : comment le fil d'exécution du moteur de rendu construit-il, exactement, la structure du graphe de scène à partir des éléments QML, qui appartiennent au fil d'exécution principal ? Évidemment, avec deux fils d'exécution, la question de la synchronisation s'impose à l'agenda. Le fil de rendu ne peut pas parcourir l'arbre des éléments QML si le fil principal modifie en même temps cet arbre.
À première vue, ajouter des verrous un peu partout devrait résoudre ce problème, mais ils deviennent vite des fardeaux à l'exécution. La solution choisie par Qt Quick est drastique, mais elle enlève ces problèmes de verrouillage de manière explicite : le fil de rendu et le fil principal se synchronisent, c'est-à-dire que le moteur de rendu met en pause le fil d'exécution principal, ce qui lui permet d'appeler sans crainte la fonction QQuickItem::updatePaintNode sur tous les éléments visuels de la scène. Ainsi, cette fonction est bien appelée depuis un autre fil d'exécution. Une fois que le fil de rendu a terminé ses opérations sur les éléments QML, le fil principal est débloqué et peut continuer son exécution. Le fil de rendu a alors récupéré toutes les informations nécessaires pour dessiner la scène à l'écran : il peut maintenant analyser le graphe de scène et l'afficher.
Parfois, une image vaut cent mots : la documentation Qt détaille les interactions entre les deux fils et leur synchronisation avec le diagramme suivant.
Tout ce processus se produit chaque fois qu'une nouvelle image est demandée par exemple, parce qu'un élément visuel a changé dans la scène ou parce que la fonction QQuickWindow::update a été appelée.
Cette image révèle toutefois un autre élément : pendant la synchronisation, le fil de rendu émet des signaux. La première manière d'intégrer du code OpenGL avec Qt Quick 2 utilisera justement ces signaux. En effet, il est possible d'y connecter des slots et d'appeler des fonctions OpenGL à l'intérieur de ces slots, ce qui permet de mélanger du code OpenGL arbitraire à l'intérieur du processus de rendu de Qt Quick 2, ce qui est exactement l'objectif poursuivi.
III. Et après ?▲
Avec ces quelques éléments sur le fonctionnement du moteur de rendu de Qt Quick 2, les prochaines parties de cette série étudieront trois mécanismes différents pour intégrer du code OpenGL dans une application Qt Quick :
- Des incrustations dans une scène OpenGL ;
- Des éléments personnalisés exploitant directement OpenGL ;
- Un contrôle manuel du rendu de Qt Quick.
IV. Article original▲
Le blog KDAB est rédigé par les ingénieurs de KDAB s'occupant des formations, de la consultance ainsi que du développement (de Qt et de produits additionnels). Vous pouvez trouver les versions originales.
Cet article est une traduction de l'article original écrit par Giuseppe D'Angelo paru le 22 octobre 2015.
Cet article est une traduction de l'un des articles en anglais écrits par KDAB. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à KDAB.
V. Remerciements▲
Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à KDAB pour nous avoir autorisés à traduire cet article !
Je tiens à remercier Alexandre Laurent et Sarah Wassermann pour leurs conseils et relectures. Merci à Claude Leloup pour sa correction orthographique.