OpenGL threadé dans Qt 4.8
Date de publication : 07/06/2011. Date de mise à jour : .
Par
Jason Barron
Traducteur : Guillaume Belz
Qt Labs
résumé
N'hésitez pas à commenter cet article !
Commentez
I. L'article original
II. Introduction
III. Le thread d'échange de tampons
IV. Introduction
V. Introduction
VI. Conclusion
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 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
Si vous avez déjà utilisé le module OpenGL dans Qt à un moment ou un autre,
vous vous étes peut être retrouvé à vouloir exécuter une partie d'OpenGL dans
un thread séparé. L'implémentation sous-jacente d'OpenGL est en elle-même (principalement)
rérentrant, il n'y avait rien en pratique qui vous en empêche. En fait, pour
rappel, dans le Qt Quarterly numéro 6, nous avions même écrit un
article décrivant la façon dont vous pourriez faire cela. C'est bien pour
les gens qui passent la plupart de leur temps à écrire en OpenGL pure, mais qu'en
est-il si vous souhaitez utiliser une partie des classes de Qt dans un thread séparé ?
Malheureusement, cela n'a pas été possible puisque ces classes Qt ne sont pas thread-safe.
Avec Qt 4.8, cela a changé et nous avons ajouté le support pour quelques-uns des
scénarios les plus courants. Pour utiliser cette nouvelle fonctionnalité sur X11,
vous devez activer l'option de l'application
Qt::AA_X11InitThreads pour garentir que les appels en arrière-plan à GLX sont
thread-safe, mais pour Windows et Mac OS X, ça devrait fonctionner directement.
III. Le thread d'échange de tampons
En fonction de votre pilote et du GPU, l'appel à
swapBuffers()
peut parfois être un appel de fonction coûteuse (en particulier sur les processeurs
embarqués). Dans de nombreux cas, c'est la fonction qui indique au GPU qu'il faut
prendre toutes vos commandes de rendu et les exécuter pour réaliser le rendu de l'image
en cours. Si cette opération est synchrone, alors votre thread principal est bloqué
pendant que le GPU fait son travail. C'est malheureux parce que votre thread en cours a des choses beaucoup mieux à faire que d'attendre pour le GPU. Par exemple, il pourrait revenir à la boucle d'événements et de processus d'entrée des utilisateurs du réseau de la circulation, ou l'avance la scène suivante d'une animation. La solution à ce en 4,8 est d'avoir un thread séparé dont le seul but dans la vie est d'attendre pour le GPU en appelant swapBuffers. En pratique, vous rendrait tout comme d'habitude dans votre thread principal, mais au lieu de swapBuffers appelant (), vous appelez doneCurrent () sur le contexte actuel GL. Vous devez alors aviser le fil échange que vous avez fini de rendu et elle remettrait makeCurrent () pour rendre le contexte actif et ensuite appeler swapBuffers (). Le fil swapping devez ensuite appeler doneCurrent () et en informe le thread principal qu'il a terminé. Remarque, il ya un changement de contexte ici et cette surcharge peut en fait être plus lent que si le thread principal venait attendu pour le GPU pour finir il est donc important que vous tester pour voir si vous avez réellement gagner quelque chose.
If this operation is synchronous then your main
thread is blocked while the GPU is doing its thing. That's unfortunate because your
current thread has much better things to do than wait for the GPU. For example, it
could return to the event loop and process some user input, network traffic or advance
the next scene of an animation. The solution to this in 4.8 is to have a separate
thread whose sole purpose in life is to wait for the GPU by calling swapBuffers.
In practice you would render everything as normal in your main thread, but instead
of calling swapBuffers(), you would call doneCurrent() on the current GL context.
You would then notify the swapping thread that you have finished rendering and it
would call makeCurrent() to make the context active and then call swapBuffers().
The swapping thread should then call doneCurrent() and notify the main thread that
it has finished. Note, there is a context switch here and this overhead may actually
be slower than if the main thread had just waited for the GPU to finish so it's important
that you test this to see if you actually gain anything.
IV. Introduction
thread téléchargement Texture
Transfert de nombreux (ou grand) textures est généralement une opération coûteuse en raison de la quantité de données étant poussé vers le GPU. Encore une fois, c'est l'un de ces opérations qui peuvent inutilement bloquer votre thread principal. Dans la version 4.8 vous pouvez résoudre ce problème en créant une paire de QGLWidgets partagé. Un des widgets est en cours dans un thread séparé, mais n'est jamais visible à l'écran. Le thread principal informe le fil le téléchargement des images à télécharger et le fil de téléchargement appelle simplement bindTexture () sur chacune de ces images et en avise alors le thread principal où chacun a fini de sorte qu'il peut être établi à l'écran.
Texture uploading thread
Uploading many (or large) textures is typically an expensive operation because of the amount of data being pushed to the GPU. Again, this is one of those operations that can unnecessarily block your main thread. In 4.8 you can solve this problem by creating a pair of shared QGLWidgets. One of the widgets is made current in a separate thread, but is never made visible on screen. The main thread informs the uploading thread which images to upload and the uploading thread simply calls bindTexture() on each of these images and then notifies the main thread when each one has finished so it can be drawn to screen.
V. Introduction
thread QPainter
Dans la version 4.8 il est maintenant possible d'utiliser QPainter dans un thread séparé pour rendre à un QGLWidget, QGLPixelBuffer et QGLFrameBufferObject en supposant que vous utilisez l'OpenGL [ES] 2.0 moteur de peinture. Il est important de noter que QGLWidget ne puissent pas être déplacées à un thread secondaire (après tout c'est un QWidget). Toutefois, étant donné son contexte seront utilisés ailleurs, il est nécessaire de faire appel doneCurrent () sur le QGLWidget pour libérer le contexte du thread principal. Ensuite, vous devez créer une sous-classe QObject distinct qui peut être déplacé vers le fil peintre secondaire. Cette QObject fera contexte, le QGLWidget de courant dans le fil de peintre et peut alors ouvrir un QPainter sur la cible et commencez à peindre. Si vous utilisez QGLWidget que la cible, alors vous devez aussi définir le "Qt:: WA_PaintOutsidePaintEvent" attribut sur l'QGLWidget. En outre, vous aurez besoin de sous-classe et de réimplémenter le resizeEvent () et paintEvent () fonctions de QGLWidget. L'implémentation par défaut de ces vais essayer de faire le contexte QGLWidget actuelles et étant donné que ces fonctions sont appelées depuis le thread principal (où vit le widget) nous ne voulons pas que cela se produise puisque nous sommes l'aide du contexte dans le fil de peintre. Dans votre nouvelle implémentation de ces fonctions, vous devez en informer la sous-classe QObject qui ne le rendu pour qu'il puisse aller et redimensionner la fenêtre ou de repeindre la scène si nécessaire. Le résultat de tout ceci peut être vu ci-dessous dans une nouvelle démo glhypnotizer appelé qui gère plusieurs sous-fenêtres MDI contenant un rendu QGLWidget à partir d'un thread séparé.
QPainter thread
In 4.8 it is now possible to use QPainter in a separate thread to render to a QGLWidget, QGLPixelBuffer and QGLFrameBufferObject assuming you are using the OpenGL [ES] 2.0 paint engine. It's important to note that QGLWidget does not get moved to a secondary thread (it's a QWidget afterall). However, since its context will be used elsewhere, it is necessary to call doneCurrent() on the QGLWidget to release the context from the main thread. Next you should create a separate QObject subclass that can be moved to the secondary painter thread. This QObject will make the QGLWidget's context current in the painter thread and can then open a QPainter on the target and start painting. If you are using QGLWidget as the target, then you additionally need to set the “?Qt::WA_PaintOutsidePaintEvent” attribute on the QGLWidget. Additionally, you will need to subclass and reimplement the resizeEvent() and paintEvent() functions of QGLWidget. The default implementation of these will try to make the QGLWidget's context current and since these functions are called from the main thread (where the widget lives) we don't want that to happen since we're using the context in the painter thread. In your reimplementation of these functions, you should notify the QObject subclass that does the rendering so it can go and resize the viewport or repaint the scene if needed. The result of all this can be seen below in a new demo called glhypnotizer which runs several MDI subwindows containing a QGLWidget rendering from a separate thread.
VI. 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 à
...,
à
...
et à
...
pour leur relecture très attentive et leurs conseils.
Copyright © 2011 Jason Barron.
Aucune reproduction, même partielle, ne peut être faite
de ce site ni de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à
trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.