I. L'article original

Le site Qt Labs permet aux développeurs de Qt de présenter les projets, plus ou moins avancés, sur lesquels ils travaillent.

Nokia, Qt, Qt Labs et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article QML Scene Graph in Master de Gunnar Sletta paru dans Qt Labs.

Cet article est une traduction d'un des tutoriels écrits par Nokia Corporation and/or its subsidiary(-ies) inclus dans la documentation de Qt, en anglais. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II. Introduction

En début de semaine, nous avons intégré les QML Scene Graph dans la branche qtdeclarative-staging.git#master. Récupérer le dépôt de Qt 5 pour commencer à bidouiller !

La méthode principale pour tester celui-ci est d'exécuter qmlscene ou en créant une instance de QSGView et de lui fournir un fichier .qml. Le nom d'importation a été mis à niveau vers QtQuick 2.0, il faut donc mettre à jour le .qml. Sinon, le binaire qmlscene va essayer de charger tous les fichiers QtQuick 1.0 et de Qt Quick 2.0, mais les plug-ins basés sur QDeclarativeItem ne seront pas chargés.

Pour une visite rapide des possibilités de QML Scene Graph, nous avons réalisé une vidéo.

Le code source de cette vidéo est disponible ici. Il utilise le système de présentation QML.

Je vais répondre à la question de savoir pourquoi nous n'avons pas utilisé un système existant, pour anticiper les commentaires : nous voulions quelque chose de petit et léger pour répondre aux cas d'utilisation du QML. Le noyau de Scene Graph est inférieur à 10 000 lignes de code (y compris la documentation des classes) et est adapté à ce cas d'utilisation. Tout le reste est l'intégration avec Qt et le QML. Code que nous aurions eu à écrire pour n'importe quel système. Nous n'aurions pas pu obtenir quelque chose de si léger en QML en utilisant des technologies existantes.

Avertissement : ne prenez pas ce billet de blog comme une documentation finale. Je vous explique l'état actuel des choses. Elles peuvent changer à l'avenir.

III. Fonctionnalités de base

Le but de Scene Graph n'est pas tant d'offrir de nouvelles fonctionnalités mais de changer l'infrastructure de base de nos outils graphiques afin de s'assurer que Qt et le QML soient plus efficaces. Certains de mes collègues et moi avons rédigé une série d'articles décrivant nos outils graphiques existants et quelques-uns des problèmes rencontrés avec eux. Dans l'article initial du blog de Scene Graph, j'ai expliqué comment nous avions l'intention d'aborder ces questions.

L'équipe QML travaille sur de nouvelles fonctionnalités pour le QML, comme le nouveau système de particules, mais je vais les laisser rédiger un post dessus quand ils se sentiront prêts.

  • Kim a déjà parlé de ShaderEffectItem dans son article La puissance pratique de QML Scene Graph. L'idée principale de l'élément "effet de shader" est d'ouvrir les barrières et laisser la créativité en liberté.
  • Nous utilisons une nouvelle méthode de rendu du texte. La méthode par défaut est maintenant basée sur les distance fields, ce qui donne des caractères redimensionnables avec juste une seule texture. Cette technique prend en charge le positionnement en virgule flottante et, si la puissance du GPU le permet, on peut aussi l'utiliser pour réaliser l'antialiasing des pixels intermédiaires ("sub-pixel"). Cela rend les éléments de texte dans le QML plus rapides, plus agréables et plus souples qu'auparavant. Nous avons également différents mécanismes de rendu des polices en place qui sont semblables au rendu des polices natives (de la même façon que le rendu des caractères avec QPainter est effectué actuellement), mais ce n'est pas activé pour le moment.
  • Nous avons changé quelques-unes des fonctionnalités internes du framework d'animation de Qt pour être en mesure de piloter des animations basées sur le "vertical blank signal". J'ai parlé de ce concept dans Velours et QML Scene Graph.

IV. API publique et API back-end

Nous avons divisé l'API en deux types différents. L'API publique, qui est destinée aux développeurs d'applications, et le back-end API, qui est destinée aux intégrateurs de systèmes à utiliser. L'API publique contient toutes les classes nécessaires pour réaliser la partie graphique dans le QML, d'introduire de nouvelles primitives et des effets d'ombrage ainsi que l'accès à quelques fonctionnalités bas niveau. Tous les fichiers qui sont visibles dans la documentation générée doivent être considérés comme l'API publique. J'aurais voulu donner un lien vers la documentation publique, mais nous n'avons pas encore la génération automatique de documentation pour les dépôts modulaires, ça devrait donc venir plus tard.

L'API back-end inclut des choses comme les implémentations du moteur de rendu et la texture. L'idée est que nous pouvons optimiser une partie du système en fonction du matériel en cas de besoin. L'API back-end est constituée de fichiers d'en-têtes privés. Certains d'entre eux pourraient passer dans l'API publique dans le futur, mais nous ne sommes pas à l'aise avec l'idée de déverrouiller cette API pour l'instant.

V. Le modèle de rendu

Fondamentalement, le graphe de scène est un arbre de nœuds prédéfinis. Le rendu est réalisé en remplissant l'arbre avec des nœuds de géométrie, qui correspondent à des objets géométriques définis par un vertex ou un mesh à dessiner et à un matériau qui définit ce qu'il faut faire avec cette géométrie. Le matériau est essentiellement un QGLShaderProgram avec une certaine logique ajoutée.

Quand le moment est venu de dessiner les nœuds, le moteur de rendu récupère l'arbre et réalise le rendu. Le moteur de rendu est libre de réorganiser l'arbre comme il l'entend pour améliorer les performances, tant que le rendu visuel ne change pas. Le moteur de rendu par défaut travaille en séparant la géométrie opaque de la géométrie transparente. La géométrie opaque est rendue en premier, regroupée en fonction de ses matériaux afin de minimiser les changements d'état. La géométrie opaque est rendue à la fois dans le tampon de couleur (color buffer) et dans le tampon de profondeur (depth buffer). La profondeur des éléments est calculée en fonction de l'ordre d'origine des éléments dans le graphe. Lors du rendu de la géométrie translucide, le test de profondeur est activé ; ainsi, si la géométrie opaque recouvrant la géométrie transparente, le GPU n'aura à faire aucun travail pour les pixels transparents. Le moteur de rendu par défaut possède également un paramètre pour que le rendu de la géométrie opaque se fasse d'avant en arrière (à activer avec l'option --opaque-front-to-back à qmlscene). Les moteurs de rendu personnalisés qui utilisent des algorithmes différents pour rendre l'arbre peuvent être implémentés par l'API back-end.

Le graphe de scène fonctionne sur un seul contexte OpenGL, mais sans le créer ou le posséder. Le contexte peut venir de n'importe où et, dans le cas de QSGView, le contexte vient de la classe de base QGLWidget. Avec l'inversion de la pile graphique qui doit être ajoutée au dépôt au plus tard cet été, le contexte OpenGL est fourni par la fenêtre elle-même.

VI. Le modèle de threads

Le QML Scene Graph est thread indépendant, il peut fonctionner sur n'importe quel thread qui possède un contexte OpenGL. Cependant, une fois que le graphe de scène est mis en place dans ce contexte / thread, il ne peut pas être déplacé. Au départ, nous voulions exécuter les animations QML et tous les appels OpenGL dans un thread de rendu dédié. En raison de la façon dont fonctionne QML, cela s'est avéré impossible. Les animations QML sont liées à des propriétés QML, qui sont des QObject et vont lancer le code C++. Si nous devions réaliser les animations dans le thread de rendu et qu'elles soient réalisés avec des appels vers le code C++, nous aurions un problème de synchronisation, de sorte que nous sommes partis avec une approche différente.

Le contexte OpenGL et tout le code lié au graphe de scène s'exécutent sur le thread de rendu, sauf indication contraire dans la documentation. Les animations sont exécutées dans le thread GUI, mais sont pilotées par un événement envoyé par le thread de rendu. Avant le début du rendu d'une trame, il y a une courte période pendant laquelle nous bloquons le thread GUI pour copier l'arbre QML et ses changements dans le graphe de scène. Il représente donc un instantané de l'arbre QML à ce moment-là. Du point de vue de l'API utilisateur, cela se passe pendant l'appel à QSGItem::updatePaintNode(). J'ai essayé de représenter ça dans un diagramme.

Image non disponible

L'avantage de ce modèle est que, lors des animations, nous pouvons calculer les animations pour la trame suivante et évaluer le JavaScript des bindings sur le thread GUI, tandis que nous réalisons le rendu de la trame actuelle sur le thread de rendu. Le calcul des étapes suivantes des animations et l'évaluation du JavaScript prend généralement un peu plus de temps que de faire le rendu, de sorte qu'ils continuent alors que le thread de rendu se bloque en attendant le prochain signal vsync. Donc, même pour un processeur monocore, il y a un avantage à ce que les animations continuent pendant que le thread de rendu reste sans rien faire en attendant le signal vsync, généralement via l'appel à swapBuffers().

VII. Intégration avec QPainter

Le graphe de scène QML n'utilise pas QPainter directement, mais il y a plusieurs façons de l'intégrer. La façon la plus évidente est d'utiliser la classe QSGPaintedItem. Cette classe va ouvrir un QPainter sur une QImage ou un FBO en fonction des besoins de l'utilisateur et possède une fonction virtuelle paint(), qui sera appelée lorsque l'utilisateur lancera un update() sur l'élément. Il s'agit d'une classe similaire à QDeclarativeItem, qui a été portée pour fonctionner dans le graphe de scène. Les deux classes ont une API équivalente, mais leurs fonctionnements internes sont quelque peu différents. La fonction paint() est appelée par défaut lors de la phase de "synchronisation", lorsque le thread de l'interface graphique est bloqué pour éviter les problèmes de threading, mais on peut basculer la fonction pour tourner sur le thread de rendu et la découpler du thread d'interface graphique.

Une autre option est de réaliser le rendu manuellement dans un FBO ou une QImage et d'ajouter le résultat dans un QSGSimpleTextureNode ou similaire.

VIII. Intégration avec OpenGL

Nous avons trois approches principales pour intégrer OpenGL.

Le signal QSGEngine::beforeRendering() est émis sur le thread de rendu lorsque le contexte de rendu est attaché. Ce signal peut être utilisé pour exécuter du code GL en arrière-plan du graphe de scène avec le rendu de l'interface utilisateur en QML en premier plan. Naturellement, nous ne pouvons pas effacer l'arrière-plan lors du rendu du graphe lors de l'utilisation de ce mode et il y a des propriétés dans le QSGEngine prévues à cet effet. Un cas d'utilisation typique ici serait d'avoir le rendu d'un moteur de jeu 3D en arrière-plan et une interface utilisateur QML par-dessus.

Le signal QSGEngine::afterRendering() est émis sur le thread de rendu après que le graphe ait terminé son rendu, mais avant de réaliser l'échange. Cela peut être utilisé pour mettre, par exemple, du contenu 3D par-dessus une interface utilisateur QML.

Réaliser le rendu dans un FBO et ajouter la texture dans le graphe. C'est la meilleure façon d'intégrer du contenu à l'intérieur du QML, qui doit être conforme aux propriétés du QML telles que l'opacité, le découpage et la transformation. On peut intégrer une vue Ogre3D dans un graphe de scène QML en utilisant une texture hors écran. Un moyen facile de réaliser cela est d'hériter de QSGPaintedItem, de réaliser un rendu basé sur les FBO et d'appeler QPainter::beginNativePainting() dans la fonction paint().

IX. Les modes de débogage

Actuellement, nous proposons quelques variables d'environnement pour aider à traquer les fuites lors des animations.

  • QML_TRANSLUCENT_MODE=1 : l'activation de cette variable d'environnement permet de réaliser le rendu de tous les nœuds de géométrie avec une transparence de 0,5. Certains matériaux peuvent choisir d'ignorer complètement l'opacité ; dans ce cas, cette variable n'a aucun effet pour eux, mais ils sont peu nombreux. Ceci est utile s'il y a beaucoup d'éléments QML complètement masqués par autre chose.
  • QML_FLASH_MODE=1 : l'activation de cette variable d'environnement est similaire à QT_FLUSH_UPDATE que nous avons dans Qt. Tous les éléments QML qui reçoivent un événement de mise à jour graphique afficheront un rectangle temporaire au dessus de l'élément. Cette fonction est utile dans la traque des fuites lors des animations

Ensemble, ces deux variables peuvent, par exemple, être utilisées pour traquer les fuites lors des animations en dehors de la vue actuelle.

X. Où nous trouver ?

Lorsque vous commencez à utiliser ces nouvelles fonctionnalités, vous pourriez trouver des problèmes à signaler, des caractéristiques qui manquent, des suggestions d'amélioration. Les endroits appropriés pour communiquer avec nous sont :

  • bogues et caractéristiques : le bugreports. Pour les sujets concernant le Scene Graph, c'est-à-dire l'API de rendu, il y a un composant "SceneGraph" ;
  • IRC : #qt-graphics sur freenode.net est l'endroit où sont la plupart des personnes qui travaillent sur la partie graphique ;
  • mail : il y a une liste de diffusion qt5-feedback qui a été créée il y a quelques semaines ;
  • nous aurons également un débat sur graphe de scène QML lors du sommet des collaborateurs de Qt.

XI. Quelques chiffres

Nous avons pensé qu'il serait bien que nous partagions quelques chiffres sur ce que nous avons à l'heure actuelle. Voici les chiffres obtenus lors de l'exécution de la démo photoviewer qui se trouve dans demos/declarative, en utilisant le QML 1, avec les moteurs Raster et OpenGL et le moteur de rendu de Mesa qui utilise LLVMpipe, et le QML 2, avec OpenGL et Mesa/LLVMpipe. Le test a été réalisé sur un processeur Intel i7-2600K Sandy Bridge avec un GPU intégré Intel HD Graphics 3000, sous Linux, avec la version Qt 5 HEAD utilisant le back-end XCB (l'équivalent de X11 dans Qt 4.8 pour les moteurs Raster et OpenGL).

Image non disponible

Comme vous pouvez le voir, QML Scene Graph apporte un gain de 2,5 fois sur un exemple arbitraire de code QML par rapport à la pile graphique que nous avons en QML 1. L'autre partie intéressante est que LLVMpipe est dans la même gamme que notre moteur logiciel Raster, en fait il est un peu plus rapide. Ce n'est pas trop surprenant, étant donné qu'ils font essentiellement la même chose. Avec QML 2, la version multithread de LLVMpipe est plus rapide que la version OpenGL de QML 1. J'espère que cela aide à réduire certaines des préoccupations qui ont été levées sur la dépendance à OpenGL dans Qt 5.

X. Conclusion

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisés à traduire cet article !

Un grand merci à Thibaut Cuvelier et Claude Leloup pour leur relecture très attentive et leurs conseils.