OpenGL et GTK de Hood
Transcription
OpenGL et GTK de Hood
OpenGL et GTK de Hood OpenGL et GTK de Hood Table des matières OpenGL et GTK ?..............................................................................................................................................1 Introduction..............................................................................................................................................1 GtkGLArea..............................................................................................................................................1 1. Présentation...................................................................................................................................1 2. Création.........................................................................................................................................2 3. Utilisation de fonctions OpenGL..................................................................................................5 GtkGLExt.................................................................................................................................................6 1. Presentation...................................................................................................................................6 2. Création.........................................................................................................................................7 3. Utilisation des fonctions OpenGL.................................................................................................8 Callbacks..................................................................................................................................................8 Le rafraîchissement / Redraw................................................................................................................10 La démo.................................................................................................................................................10 Remarques..............................................................................................................................................10 Annexe 1 : Les sources......................................................................................................................................12 1. gtkglarea.c..........................................................................................................................................12 2. gtkglext..............................................................................................................................................13 3. gl.c......................................................................................................................................................15 4. main.c.................................................................................................................................................16 i OpenGL et GTK ? Si, si, c'est possible. Je vous assure. Introduction A l'heure où est écrite cette petite doc, on trouve deux projets permettant d'exploiter OpenGL et GTK. Je dis bien projets car OpenGL et GTK n'est pas quelque chose d'officiel, ce sont des développeurs indépendants et bénévoles qui ont utilisés leur temps et leur talent pour nous offrir ces fonctionnalités. Ces deux projets ont des noms : GtkGLArea, qui fera l'objet d'un premier paragraphe et GtkGLExt, objet d'un second. Pour GtkGLArea comme pour GtkGLExt, leur étude sera structurée de la même façon. Tout d'abord une présentation qui vous donnera des infos qu'il est bon de connaître, comme qui en est l'auteur, où trouver les sources, les binaires, les headers ... ainsi que des liens utiles. J'attaquerai ensuite une partie "Comment créer une GtkGLArea / GtkGLExt ?" qui, vous l'aurez deviné, expliquera comment créer le widget en question et enfin une dernière partie sur comment utiliser OpenGL grâce à ces widgets. A la suite de ces deux paragraphes, vous trouverez une section "Callbacks", une autre "Rafraîchissement / Redraw" et une dernière "Remarques" pour lesquelles il n'y a pas besoin de spécificités particulières à l'un ou l'autre widget. Avant d'entrer plus en avant dans les détails, il faut savoir que pour aborder ce tutoriel, vous devez maîtriser les quelques bases indispensables de GTK (communes à toutes les API utilisant la programmation événementielle) notamment tout ce qui est boucle d'attente d'événements (ou boucle principale), fonctionnement / mise en place de callbacks (fonctions de rappel), signaux émis (va de pair avec les callbacks), utilisation des widgets, etc ... Vous devez savoir comment fonctionne GTK (en gros, ou avoir des notions de programmation événementielle) et encore mieux, avoir déjà utilisé GTK dans vos propres réalisations. Si GTK c'est encore du chinois, allez voir ici −> http://www.gtk.org (vous avez une section "Tutorials" et il y a de la doc en ligne) et là : http://gtk−fr.apinc.org (où des cours et des articles en français, s'il vous plaît, vous attendent). Bien entendu, vous aurez aussi besoin de quelques bases OpenGL (si ce n'est pas le cas −> http://www.glinfrench.fr.st, voire http://OpenGL.org). GtkGLArea 1. Présentation GtkGLArea est le projet le plus ancien. Il a été développé à l'origine par Janne Löf et est désormais aidé principalement par Tor Lillqvist (auteur du port Win32 de GTK) et James HensTridge (j'espére que j'en oublie pas). GtkGLArea a vu sa première version apparaître avec GTK 1.2 et a maintenant évolué vers GTK 2.X. Mais alors qu'est ce qu'une GtkGLArea ? Et bien c'est un widget spécialement conçu pour utiliser OpenGL ; sa surface sera utilisée comme surface de rendu par OpenGL. Vous en créez une (GtkGLArea), et vous pouvez utiliser OpenGL pour dessiner dedans, génial non ? Les couleurs sont pourries, c'est normal, c'est du GIF ;) Les sources ainsi que les headers sont disponibles via CVS (pour se logger: ":pserver:[email protected]:/cvs/gnome " et nom du module "gtkglarea", merci Inerti@ ;)). Il y a aussi la page sur CVS à cette adresse : http://cvs.gnome.org/bonsai/rview.cgi?cvsroot=/cvs/gnome&dir=gtkglarea. Malheuresement, il n'y a que trés peu de doc sur ce widget. 1 OpenGL et GTK de Hood 2. Création Avant tout, il faut savoir deux trois petites choses ... Lorsqu'on souhaite dessiner avec OpenGL, avec quelque API que ce soit, le système a besoin d'une structure appelée "contexte". Cette structure contient tout un tas d'informations que je ne saurait pas vous décrire mais il faut garder à l'esprit qu'un contexte est associé à une et une seule "fenêtre" (fenêtre au sens région sur l'écran, dans ce tutoriel, "fenêtre" signifie surface du widget, cf "La GtkGLArea" dans la capture ci dessus). Mais à quoi sert ce truc ? En fait, le contexte sert essentiellement à deux choses. Premièrement il contient les textures ainsi que les listes d'affichages (display lists) qui ont pu être créées dans ce même contexte (donc dans la fenêtre associée). Un inconvénient de ce système est que comme chaque contexte contient ses propres display lists et ses propres textures, il va falloir créer vos textures et display lists dans chacun d'eux même s'il s'agit de la même scène avec les mêmes textures, mêmes display lists ... Pas trés commode me direz vous, devoir créer en double, triple, voire quadruple des textures et display lists ... Heureusement, un contexte est "partageable", c'est à dire que les contextes peuvent partager ces informations de textures et display lists ... ouf, plus besoin de créer tout cela en plusieurs exemplaires ... Deuxième utilité du contexte : OpenGL ne peut rendre dans une "fenêtre", que si le contexte qui y est associé est activé. Inconvénient qui va avec : vu que le dessin de chaque fenêtre est indépendant (du fait de leur propre contexte à activer), il va falloir appeler quatre fois la routine de dessin, d'initialisation et de redimensionnement , en activant à chaque fois le contexte de la fenêtre dans laquelle on veux dessiner. Bon ca y est, c'est la merde dans votre esprit mais dans le mien aussi de toute façon ... Prenons un exemple, un modeleur où on a quatre vues d'une même scène texturée, display listée ... On a quatre widget OpenGL, donc en interne (laissons le système gérer son bordel) le système se garde quatre contextes, un par fenêtre. Vu que j'ai dit qu'un contexte est partageable, et qu'ici on a l'utilité de créer des contextes partagés , on va s'en servir (on va pas s'emmerder à créer en quadruples nos textures et nos display lists non plus ?). Nos quatres widget OpenGL sont "liés" par leur contexte, tout le monde connaît les textures et les display lists de tout le monde, plus besoin d'avoir quatre fois la même texture. Arrive le moment d'initialiser OpenGL. Comme je l'ai dit, le rendu dans une fenêtre n'est possible que si son contexte est activé, il va donc falloir activer l'un aprés l'autre les contextes associés aux fenêtres et appeler la fonction d'initialisation pour chacun d'eux. Idem pour la fonction de dessin de la scène ainsi que son redimmensionnement. Des petits schémas pour mettre tout ça en ordre ? −> Le monde merveilleux du partage de contexte : 2 OpenGL et GTK de Hood La fenêtre 1 et la fenêtre 2 ne partagent pas leurs contextes. Si on essaie d'appeler une texture créée dans la fenêtre 1 depuis la fenêtre 2, ca ne marchera pas. Idem dans l'autre sens. Par contre, les fenêtres 3 et 4 partagent leurs contextes, une texture (ou une display lists) créée dans le fenêtre 3 est connue de la fenêtre 4 et c'est aussi valable dans l'autre sens. −> Le rendu 3 OpenGL et GTK de Hood Le rendu se fait donc en autant d'étapes qu'il y a de fenêtres à gérer. Ici, on a quatre fenêtres à dessiner, donc quatre étapes. Première étape : le fenêtre 1. Il faut activer son contexte, appeler la fonction OpenGL qu'il faut (initialisation, redimensionnement ou dessin) et enfin permuter les tampons d'images. Deuxième étape : la fenêtre 2. Comme pour la fenêtre 1, activation de son contexte, appels OpenGL et permutation des tampons. Troisième et quatrième étapes c'est idem ... Bon maintenant que vous maîtrisez comme des fous, on peut revenir à la création de notre GtkGLArea. Nous avons deux fonctions à notre disposition : GtkWidget * gtk_gl_area_new(int * attrList) sert à créer une nouvelle GtkGLArea avec son propre contexte OpenGL, sans utiliser de partage de contexte. C'est la fonction qu'il convient d'appeler pour créer la première GtkGLArea de votre application. GtkWidget * gtk_gl_area_share_new(int * attrList, GtkGLArea * share) sert aussi à créer une nouvelle GtkGLArea mais cette fois ci, on utilise le partage de contexte. Une texture créée dans cette GtkGLArea sera connue de la GtkGLArea share et des autres GtkGLArea partageant ce contexte, et inversement (Je vous conseille d'utiliser une seule GtkGLArea comme "maître contexte", vous en créez une via gtk_gl_area_new() que vous appelez "gl_maitre" et vous l'utilisez pour créer les autres GtkGLArea à contexte partagé). Dans ces deux fonctions, vous remarquez int * attrList. Il s'agit en fait d'un tableau servant à décrire les attributs qu'OpenGL utilisera pour le rendu. Typiquement il aura cette forme : int attrList[] = { ATTRIBUT_1, valeur, ATTRIBUT_2, valeur, ...... GDK_GL_NONE }; où ATTRIBUT_1, ATTRIBUT_2, ... est un enum parmi : 4 OpenGL et GTK de Hood • GDK_GL_NONE • GDK_GL_USE_GL • GDK_GL_BUFFER_SIZE • GDK_GL_LEVEL • GDK_GL_RGBA • GDK_GL_DOUBLEBUFFER • GDK_GL_STEREO • GDK_GL_AUX_BUFFERS • GDK_GL_RED_SIZE • GDK_GL_GREEN_SIZE • GDK_GL_BLUE_SIZE • GDK_GL_ALPHA_SIZE • GDK_GL_DEPTH_SIZE • GDK_GL_STENCIL_SIZE • GDK_GL_ACCUM_RED_SIZE • GDK_GL_ACCUM_GREEN_SIZE • GDK_GL_ACCUM_BLUE_SIZE • GDK_GL_ACCUM_ALPHA_SIZE. Tout les attributs n'ont pas besoin d'une valeur comme par exemple GDK_GL_RGBA ou encore GDK_GL_DOUBLEBUFFER. Voici un exemple simple de création de GtkGLArea : GtkWidget * gl_maitre; GtkWidget gl2; int attrList[]= { GDK_GL_RGBA, /* RGBA */ GDK_GL_RED_SIZE,1, /* 8 bits de rouge (1 octect) */ GDK_GL_GREEN_SIZE,1, /* 8 bits de vert */ GDK_GL_BLUE_SIZE,1, /* 8 bits de bleu */ GDK_GL_DOUBLEBUFFER, /* double buffer */ GDK_GL_NONE /* marqueur de fin */ }; gl_maitre = gtk_gl_area_new(attrList); /* gl_maitre est une GtkGLArea */ gl2 = gtk_gl_area_share_new(attrList, GTK_GL_AREA(gl_maitre)); /* gl2 est aussi une GtkGLArea partageant le contexte de gl_maitre */ Dans cet exemple, gl_maitre et gl2 partagent leurs contextes, les textures et display lists de gl_maitre sont connues par gl2 et inversement. Faire gtk_gl_area_new(attrList) revient à faire gtk_gl_area_share_new(attrList, NULL). 3. Utilisation de fonctions OpenGL Les primitives OpenGL resteront sans effet si elles ne sont pas appelées au bon moment, c'est à dire lorsqu'un contexte OpenGL est "actif". Un contexte étant lié à une fenêtre, le fait d'activer un contexte dessinera dans la fenêtre associée. Pour activer le contexte d'une GtkGLArea (et donc, dessiner dans la fenêtre qui lui est associée), c'est simple, il suffit d'utiliser gboolean gtk_gl_area_make_current(GtkGLArea * widget). Cette fonction s'occupe de tout. Elle renvoie TRUE si l'activation a réussi et FALSE sinon. Les appels OpenGL aprés un gtk_gl_make_current() raté resteront sans effet. Avec ce bout de code, vous serez prêts à tout : if(gtk_gl_area_make_current(gl_maitre)) { /* appels OpenGL */ } else 5 OpenGL et GTK de Hood printf("Activation du contexte XX ratée !\n"); Il ne faut pas oublier non plus la fonction permettant de permuter les tampons d'images, qui pour les GtkGLArea se résume à un appel à void gtk_gl_area_swap_buffers(GtkGLArea * gl). Cette fonction est bien entendu à appeler à la fin du dessin OpenGL lorsqu'un contexte est actif, comme dans la condition du code ci dessus par exemple qui devient du coup : if(gtk_gl_area_make_current(gl_maitre)) { /* Dessin OpenGL */ gtk_gl_area_swap_buffers(gl_maitre); } else printf("Activation du contexte XX ratée !\n"); GtkGLExt 1. Presentation GtkGLExt est un projet plus récent, en effet, sa première version est apparue avec GTK 2.0. Il est développé par Naofumi Yasufuku (à l'origine du projet) et Igor Fernandez qui l'a rejoint ensuite et sa version actuelle est la 0.5. GtkGLExt, contrairement à GtkGLArea, n'est pas un widget tout prêt. Il s'agit d'une extension à GTK 2.X permettant théoriquement d'apporter les fonctionnalités OpenGL à n'importe quel widget (d'où le "ext" de GtkGLExt). En clair, il est théoriquement possible de faire de l'OpenGL sur n'importe quel widget, un bouton, un menu etc ... (je vous conseille quand même de limiter son utilisation à des GtkDrawingArea, plus appropriées pour cette utilisation). Il permet de transformer n'importe quel widget en surface de rendu pour OpenGL. Allez, un petit schéma gratuit : 6 OpenGL et GTK de Hood Les couleurs sont toujours pourries, c'est toujours normal, c'est toujours du GIF ;) GtkGLExt est à peine un peu plus complexe à manier que GtkGLArea. Les sources, headers, binaires et documentations sont disponibles sur SourceForge.net ici −> http://gtkglext.sourceforge.net/.La documentation est complète et bien faite, elle est du même style que celle de GTK, elle reprend chaque fonction en expliquat son rôle, ses différents paramètres, etc ... 2. Création On ne peut pas réellement parler de création puisque comme je l'ai dit plus haut, on va en fait ajouter les fonctionnalités OpenGL à un widget préexistant. Pour ajouter ce support OpenGL, on utilise gboolean gtk_widget_set_gl_capability(GtkWidget * widget, GdkGLConfig * config, GdkGLContext * context, gboolean direct, gint render_type). Bon il y a pas mal de paramètres, ne paniquons pas, on se les fait un par un : • GtkWidget * widget : ca c'est le widget auquel on veut donner la possibilité d'utiliser OpenGL. • GdkGLConfig * config : c'est une structure pour la description des attributs qu'OpenGL aura le droit d'utiliser (comme le "attrList" de GtkGLArea). • GdkGLContext * context : c'est un contexte pour le cas où on voudrait faire du partage de contexte. • gboolean direct : un booleen qui sert à quelque chose mais je ne saurais vous dire exactement quoi (Mettez le à TRUE) ( −"Wether rendering is to be done with a direct connection to the graphics system"− extrait de la doc, je vois pas trop comment rendre autrement ...). • gint render_type : un enum parmi GDK_GL_RGBA_TYPE ou GDK_GL_COLOR_INDEX_TYPE qui indique quel mode de couleur on va utiliser (COLOR_INDEX n'est pas encore supporté). • valeur de retour : TRUE si la fonction a réussi à convertir notre widget à OpenGL, FALSE sinon. Et une structure GdkGLConfig, ça se construit comment ? C'est tout simple, elle se créé en utilisant GdkGLConfig * gdk_gl_config_new_by_mode(GdkGLConfigMode mode) où mode est un ou binaire entre les différentes possibilités : • GDK_GL_MODE_RGB, • GDK_GL_MODE_RGBA, • GDK_GL_MODE_INDEX, • GDK_GL_MODE_SINGLE, • GDK_GL_MODE_DOUBLE, • GDK_GL_MODE_ACCUM, • GDK_GL_MODE_ALPHA, • GDK_GL_MODE_DEPTH, • GDK_GL_MODE_STENCIL, • GDK_GL_MODE_STEREO, • GDK_GL_MODE_MULTISAMPLE (pas encore supporté), • GDK_GL_MODE_LUMINANCE (pas encore supporté). Une configuration "standard" serait : GdkGLConfig * config; config = gdk_gl_config_new_by_mode(GDK_GL_MODE_RGBA | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE); Il est aussi possible de créer un structure GdkGLConfig grâce à un tableau du type attrList avec la fonction GdkGLConfig* gdk_gl_config_new (const int *attrib_list) que je vous laisse découvrir. Et le contexte d'où on le sort ? Et bien en fait on va le récupérer sur un widget ayant déja des capacités OpenGL avec GdkGLContext * gtk_widget_get_gl_context(GtkWidget * widget). Sur notre widget "drawing_area" vu précedemment, ça donne : GdkGLContext * context; context = gtk_widget_get_gl_context(GTK_WIDGET(drawing_area); Avec tout ça, vous pouvez créer vos GtkGlExt. gtk_widget_set_gl_capability(GTK_WIDGET(drawing_area), config, NULL, TRUE, GDK_GL_RGBA_TYPE); 7 OpenGL et GTK de Hood Ceci apporte à drawing_area le support OpenGL sans partager de contexte (on peut supposer qu'elle jouera le rôle de "gl_maitre"). GdkGLContext * context; context = gtk_widget_get_gl_context(GTK_WIDGET(drawing_area); gtk_widget_set_gl_capability(GTK_WIDGET(drawing_area2), config, context, TRUE, GDK_GL_RGBA_TYPE); Et ceci apporte à drawing_area2 le support OpenGL tout en partageant le contexte de drawing_area ( avec la connaissance de texture etc ...) Un widget ne peut partager son contexte que s'il est "realisé", c'est à dire que ce widget doit être un enfant (direct ou indirect) d'une fenêtre du type GTK_WINDOW_TOPLEVEL et qu'on ait déjà fait sur ce widget un void gtk_widget_realize(GtkWidget * widget). Il n'y a qu'à cette condition qu'un widget ayant reçu le support OpenGL pourra partager un contexte ! Cela est du au fait que le contexte est créé au moment où la gdkwindow associée à tout widget est créée, c'est à dire, suite à l'émission du signal "realize". 3. Utilisation des fonctions OpenGL Comme pour les GtkGLArea, il faut activer le bon contexte pour dessiner dans la bonne fenêtre, mais il faut ici quelques étapes supplémentaires (rien d'insurmontable, n'ayez crainte). On récupère tout d'abord le contexte du widget avec la fonction vu précedemment, GdkGLContext * gtk_widget_get_gl_context(GtkWidget * widget) puis on récupère la surface virtuelle du widget (sa fenêtre en somme) grâce à GdkGLDrawable * gtk_widget_get_gl_drawable(GtkWidget * widget). Avec ces deux structures, on peut activer le contexte sur la surface et permettre le rendu OpenGL avec gboolean gdk_drawable_gl_begin(GdkGLDrawable * drawable, GdkGLContext * context). Cette fonction retourne TRUE si l'appel à réussi et FALSE sinon (comme gtk_gl_area_make_current()). Maintenant on peut appeler les fonctions OpenGL. Lorsqu'on a terminé les appels OpenGL, il faut désactiver le contexte avec void gdk_gl_drawable_gl_end(GdkGLDrawable * drawable). Ce qui donne en condensé : /* récupération du contexte et de la surface de notre widget */ GdkGLContext * contexte = gtk_widget_get_gl_context(GTK_WIDGET(drawing_area)); GdkGLDrawable * surface = gtk_widget_get_gl_drawable(GTK_WIDGET(drawing_area)); /* activation du contexte */ if(gdk_gl_drawable_gl_begin(surface,contexte)) { /* appels OpenGL */ gdk_gl_drawable_gl_end(surface); /* désactivation du contexte */ } Bien entendu, lorsqu'on travaille en double tampon (double buffer), il faut les permuter les tampons aprés le dessin OpenGL, ceci est fait grâce à void gdk_gl_drawable_swap_buffers (GdkGLDrawable *gldrawable). Notre routine de dessin ressemblera donc à : /* récupération du contexte et de la surface de notre widget */ GdkGLContext * contexte = gtk_widget_get_gl_context(GTK_WIDGET(drawing_area)); GdkGLDrawable * surface = gtk_widget_get_gl_drawable(GTK_WIDGET(drawing_area)); /* activation du contexte */ if(gdk_gl_drawable_gl_begin(surface,contexte)) { /* dessin OpenGL */ gdk_gl_drawable_swap_buffers(surface); /* permutation des tampons */ gdk_gl_drawable_gl_end(surface); /* désactivation du contexte */ } Callbacks Maintenant que la création de widgets OpenGL n'a plus de secrets pour vous et que vous savez à quelles conditions vous pouvez appeler OpenGL pour dessiner, vous vous posez sans doute des questions. "Mais quand est ce que je vais faire l'initialisation d'OpenGL ?", "Comment on fait pour le redimensionnement ?" et encore mieux : "Quand est ce 8 OpenGL et GTK de Hood qu'on dessine ?". Et bien il n'y a qu'une seule réponse : on va utiliser les callbacks. Comme vous devez le savoir, chaque widget émet des signaux lorsqu'il a besoin d'être redessiné, déplacé, etc ... Suite à ces signaux, les fonctions de rappel (callbacks) que vous avez installées au moyen (entre autre) de g_signal_connect() sont automatiquement appelées par GTK avec les paramètres adéquats (notamment le widget émetteur du signal en premier paramètre). Il va donc suffir de récupérer les bons signaux de notre widget OpenGL et d'agir en conséquence (ni GtkGLArea ni GtkGLExt n'introduisent de nouveaux signaux). • Lorsqu'il apparaît pour la première fois −> signal "realize". On effectue à ce moment là l'initialisation OpenGL puisque ce sigal n'est envoyé qu'une seule fois, à la première apparition du widget. • Lorsqu'il demande à être redimensionné −> signal "configure_event". On appelle alors la fonction de redimensionnement de la scène OpenGL. • Lorsqu'il demande à être (re)dessiné −> signal "expose_event". On appelle cette fois ci la fonction de dessin OpenGL. /* connection du widget aux callabcks */ g_signal_connect(G_OBJECT(widget_gl), "realize", G_CALLBACK(init), NULL); g_signal_connect(G_OBJECT(widget_gl), "configure_event", G_CALLBACK(reshape), NULL); g_signal_connect(G_OBJECT(widget_gl), "expose_event", G_CALLBACK(draw), NULL); /* prtotype de la callback "init" */ gboolean init(GtkWidget widget, /* émetteur du signal */ gpointer data) /* données supplémentaires (dernier paramètre de g_signal_connect) */ { /* activation du contexte en fonction du widget utilisé GtkGLArea ou GtkGLExt */ init_GL(); /* fonction d'initialisation OpenGL */ /* désactivation du contexte dans le cas des GtkGLExt */ return TRUE; } /* prtotype de la callback "reshape" */ gboolean reshape(GtkWidget widget, /* émetteur du signal */ GdkEventConfigure ev, /* structure contenant les infos relatives au redimensionnement (notamment les nouvelles dimensions) */ gpointer data) /* données supplémentaires (dernier paramètre de g_signal_connect) */ { /* activation du contexte en fonction du widget utilisé GtkGLArea ou GtkGLExt */ reshape_GL(ev−>width, ev−>height); /* fonction de redimensionnement OpenGL */ /* désactivation du contexte dans le cas des GtkGLExt */ return TRUE; } /* prtotype de la callback "draw" */ gboolean draw(GtkWidget widget, /* émetteur du signal */ gpointer data) /* données supplémentaires (dernier paramètre de g_signal_connect) */ { /* activation du contexte en fonction du widget utilisé GtkGLArea ou GtkGLExt */ draw_GL(); /* fonction de dessin OpenGL */ /* permutation des tampons */ /* désactivation du contexte dans le cas des GtkGLExt */ return TRUE; } Une fois ces trois fonctions installées, notre widget OpenGL sera "autonome", à sa première apparition, il fera l'initialisation OpenGL qu'il faut, dés qu'on le redimensionnera, la scène OpenGL sera redimensionné aussi et le dessin 9 OpenGL et GTK de Hood se fera dés qu'il y en aura besoin. De plus, si plusieurs widgets sont connectés à ces callbacks, on est sûrs de rendre dans la bonne fenêtre puisque le premier paramètre des callbacks est l'émetteur du signal à traiter, on active donc bien le contexte de la fenêtre concernée. Le rafraîchissement / Redraw Mais et si on a scène dynamique, que l'on veut redessiner cette scène alors que le widget ne le demande pas ? Et oui, parce que le dessin OpenGL ne se fera que si le signal "expose_event" est émis or ce signal n'est émis que lorsqu'il y en a besoin (lorsq'une partie du widget réapparait à l'écran par exemple). On pourrait directement appeler la fonction de rappel de dessin en y passant les bons paramètres ou bien encore forcer l'émission du signal "expose_event". Mais en fait ces solutions sont compliquées et on va dire qu'elles ne sont pas "propres", plutôt dégueux même ;) Bon mais comment on fait alors ? Il existe en GTK une fonction, LA fonction. Cette fonction force un widget à être redessiné immédiatement, à faire un redraw en fait. Cette fonction c'est void gtk_widget_queue_draw(GtkWidfget * widget), elle n'est pas spécifique aux widgets OpenGL, c'est une fonction utilisable pour n'importe quel widget qui aurait besoin d'être rafraîchi. Notre fonction de rafrachissement se résume donc à : void redraw(GtkWidget * widget) /* le widget à rafraîchir */ { gtk_widget_queue_draw(GTK_WIDGET(widget)); /* demande son rafraichissement immédiat */ } A partir de là, vous savez tout ce qu'il y a à savoir sur les possibilités OpenGL avec GTK ; et pour bien être sûr d'avoir tout compris, quoi de mieux qu'une petite démo signée Hood ;) Un bon exercice serait de bidouiller cette démo pour tester vous même le partage de contexte (niark ! niark !), c'est faisable, j'ai réussi ;) La démo La démo se compose de quatre fihiers : • gtkglarea.c −> code de création pour widget GtkGLArea • gtkglext.c −> code de création widget OpenGL avec GtkGLExt • gl.c −> routines OpenGL (init, dessin et redimensionnement + petite fonction pour le rafraîchissement) • main.c −> code du programme Sa compilation ne devrait pas poser de problèmes particuliers. Il faut bien entendu avoir les librairies ainsi que les headers GTK 2.X (Glib, Gobject , GDK et tout le tralala ...) pour compiler, ainsi que ceux pour GtkGLArea et GtkGLExt. Cette démo a été compilée : • avec la version windows de GTK 2.0.3 (disponible sur : http://www.gimp.org/~tml/gimp/win32/downloads.html), sous windows XP et visual C++ 6 • avec la version Linux de GTK 2.0 (disponible sur : http://www.gtk.org/download) sous la Mandrake 8.2 et gcc (dont je ne sait pas le numéro de version et j'ai la flemme de regarder) Les versions des projets utilisés sont : • la dernière pour GtkGLArea • 0.5 pour GtkGLExt Vous aurez evidemment besoin des headers OpenGL standards pour compiler la démo (gl.h et GLU.h). Sous Linux, veillez à avoir l'extension GLX installée pour pouvoir éxécuter la démo (extension OpenGL pour X) (j'espère que je dit pas de conneries). Remarques J'ai fait connaissance avec ces projets dans le cadre du développement de Sabrina, un modeleur 3D imaginé par Kitone que j'ai aidé dans le développement de l'interface. Pour tout vous dire, c'est même grâce à lui si je vous conseille d'utiliser gtk_widget_queue_draw() plutôt qu'une solution de merde car c'est lui qui a constaté que cette fonction (gtk_widget_queue_draw()) donnait de meilleurs résultats que les autres méthodes (de merde). Sabrina utilisait à l'origine des GtkGLArea, mais j'ai été amené à chercher autre chose du fait de problèmes que nous avons rencontrés dans son utilisation, notamment le rafraîchissement du widget aprés des opérations de masquage / réaffichage du widget (gtk_widget_hide() et gtk_widget_show()) ou encore aprés l'utilisation de la fonction 10 OpenGL et GTK de Hood gtk_widget_reparent() qui permet de "déplacer" un widget dans la hiérarchie de l'interface. Les GtkGLArea ne parvenaient plus à dessiner la secène OpenGL aprés ces opérations. C'est peut−être du à une mauvaise utilisation de notre part mais toujours est−il que nous n'avons pas rencontré ces problèmes avec les GtkGLExt. De tout façon, dans le cadre d'une utilisation "normale" de ces widgets, cela ne pose pas de problèmes, le mieux c'est que vous essayiez les deux projets et utilisiez celui qui fonctionne le mieux par rapport à l'utilisation que vous en faites. Par contre, si vous commencez à essayer de faire des choses un peu tordues avec ces widgets (style gtk_widget_reparent()), préparez vous à bidouiller sévére, croyez en mon expérience ;) Bonne prog ! Hood @ GlinFrench & Gtk−fr 11 Annexe 1 : Les sources 1. gtkglarea.c /***************************************************** * gtkglarea.c * Construction d'un widget opengl en utilisant * les gtkglarea * Construit et met en place les callbacks *****************************************************/ #include <gtk/gtk.h> // include GTK #include <gtkgl/gtkglarea.h> // include gtkglarea /* fonctions Opengl */ extern void init(); extern void reshape(int height, int width); extern void draw(); /**************************************** * init_area() * Callback connectée au signal "realize" * c'est à dire, première apparition du * widget à l'écran * Appelle l'init Opengl aprés avoir "activé" * le contexte *****************************************/ gint init_area(GtkWidget * widget, gpointer data) { /* activation du contexte */ if(gtk_gl_area_make_current(GTK_GL_AREA(widget))) { init(); // procédure d'init Opengl } return TRUE; } /**************************************** * reshape_area() * Callback connectée au signal "configure_event" * c'est à dire, dés qu'il y a redimensionement * Appelle le redimensionnement Opengl aprés * avoir "activé" le contexte *****************************************/ gint reshape_area(GtkWidget * widget, GdkEventConfigure * ev, gpointer data) { /* GdkEventConfigure * ev est une structure qui contient toutes les infos relatives à l'opération de redimensionnment. Pour + d'infos −> doc sur les structures GDK */ /* activation du contexte */ if(gtk_gl_area_make_current(GTK_GL_AREA(widget))) { reshape(ev−>height,ev−>width); // procédure de redimensionnment Opengl } return TRUE; } /**************************************** * draw_area() * Callback connectée au signal "expose_event" * c'est à dire, dés qu'il y a besoin de (re)dessiner * Appelle le dessin Opengl aprés avoir "activé" * le contexte 12 OpenGL et GTK de Hood *****************************************/ gint draw_area(GtkWidget * widget, gpointer data) { /* activation du contexte */ if(gtk_gl_area_make_current(GTK_GL_AREA(widget))) { draw(); // dessin opengl gtk_gl_area_swap_buffers(GTK_GL_AREA(widget)); // permutation tampons } return TRUE; } /**************************************** * CreateGl_GtkGlArea() * Fonction qui va s'occuper de la création * de notre widget et de le rattacher à * ses callbacks *****************************************/ GtkWidget * CreateGl_GtkGlArea() { GtkWidget * widget_gl; // notre widget gint attrList[]={ // la config Opengl à utiliser GDK_GL_RGBA, GDK_GL_RED_SIZE,1, GDK_GL_GREEN_SIZE,1, GDK_GL_BLUE_SIZE,1, GDK_GL_DOUBLEBUFFER, GDK_GL_NONE }; /* création */ widget_gl = gtk_gl_area_new(attrList); /* connections aux callbacks */ g_signal_connect(G_OBJECT(widget_gl),"realize",G_CALLBACK(init_area),NULL); g_signal_connect(G_OBJECT(widget_gl),"configure_event",G_CALLBACK(reshape_area),NULL); g_signal_connect(G_OBJECT(widget_gl),"expose_event",G_CALLBACK(draw_area),NULL); return widget_gl; } 2. gtkglext /***************************************************** * gtkglext.c * Construction d'un widget opengl en utilisant * les gtkglext * Construit et met en place les callbacks *****************************************************/ #include <gtk/gtk.h> // include GTK #include <gtk/gtkgl.h> // include gtkglext /* fonctions Opengl */ extern void init(); extern void reshape(int height, int width); extern void draw(); /**************************************** * init_ext() * Callback connectée au signal "realize" * c'est à dire, première apparition du * widget à l'écran * Appelle l'init Opengl aprés avoir "activé" * le contexte 13 OpenGL et GTK de Hood *****************************************/ gint init_ext(GtkWidget * widget, gpointer data) { /* recuperation du contexte et de la surface de notre widget */ GdkGLContext * contexte = gtk_widget_get_gl_context(GTK_WIDGET(widget)); GdkGLDrawable * surface = gtk_widget_get_gl_drawable(GTK_WIDGET(widget)); /* activation du contexte */ if(gdk_gl_drawable_gl_begin(surface,contexte)) { init(); // init Opengl gdk_gl_drawable_gl_end(surface); // désactivation du contexte } return TRUE; } /**************************************** * reshape_ext() * Callback connectée au signal "configure_event" * c'est à dire, dés qu'il y a redimensionement * Appelle le redimensionnement Opengl aprés * avoir "activé" le contexte *****************************************/ gint reshape_ext(GtkWidget * widget, GdkEventConfigure * ev, gpointer data) { /* recuperation du contexte et de la surface de notre widget */ GdkGLContext * contexte = gtk_widget_get_gl_context(GTK_WIDGET(widget)); GdkGLDrawable * surface = gtk_widget_get_gl_drawable(GTK_WIDGET(widget)); /* activation du contexte */ if(gdk_gl_drawable_gl_begin(surface,contexte)) { reshape(ev−>height,ev−>width); // redimensionnement Opengl gdk_gl_drawable_gl_end(surface); // désactivation du contexte } return TRUE; } /**************************************** * draw_ext() * Callback connectée au signal "expose_event" * c'est à dire, dés qu'il y a besoin de (re)dessiner * Appelle le dessin Opengl aprés avoir "activé" * le contexte *****************************************/ gint draw_ext(GtkWidget * widget, gpointer data) { /* recuperation du contexte et de la surface de notre widget */ GdkGLContext * contexte = gtk_widget_get_gl_context(GTK_WIDGET(widget)); GdkGLDrawable * surface = gtk_widget_get_gl_drawable(GTK_WIDGET(widget)); /* activation du contexte */ if(gdk_gl_drawable_gl_begin(surface,contexte)) { draw(); // dessin Opengl gdk_gl_drawable_swap_buffers(surface); // permutation tampons gdk_gl_drawable_gl_end(surface); // désactivation du contexte } return TRUE; } 14 OpenGL et GTK de Hood /**************************************** * CreateGl_GtkGlExt() * Fonction qui va s'occuper de la création * de notre widget et de le rattacher à * ses callbacks *****************************************/ GtkWidget * CreateGl_GtkGlExt() { GtkWidget * widget_gl; // notre widget GdkGLConfig * glconfig; // la config qui va aller avec static const gint attrList[] = { // les paramètres de la config GDK_GL_DOUBLEBUFFER, GDK_GL_RGBA, GDK_GL_RED_SIZE, 1, GDK_GL_GREEN_SIZE, 1, GDK_GL_BLUE_SIZE, 1, GDK_GL_ATTRIB_LIST_NONE }; /* création config */ glconfig = gdk_gl_config_new(attrList); /* création d'une drawing area qui va recevoir le support Opengl */ widget_gl = gtk_drawing_area_new(); /* ajout du support Opengl */ gtk_widget_set_gl_capability(GTK_WIDGET(widget_gl),glconfig,NULL,TRUE,GDK_GL_RGBA_TYPE); /* connection aux callbacks */ g_signal_connect(G_OBJECT(widget_gl),"realize",G_CALLBACK(init_ext),NULL); g_signal_connect(G_OBJECT(widget_gl),"configure_event",G_CALLBACK(reshape_ext),NULL); g_signal_connect(G_OBJECT(widget_gl),"expose_event",G_CALLBACK(draw_ext),NULL); return widget_gl; } 3. gl.c /***************************************************** * gl.c * Fonctions pures Opengl *****************************************************/ /* assure compatibilité avec Win 32 */ #ifdef WIN32 #include <windows.h> #endif /* includes standards opengl */ #include <GL/gl.h> #include <GL/glu.h> /* angle de rotation de notre carré (global) */ float angle = 0.0; int step =0; /************************* * init() * Initialisation Opengl **************************/ void init() { glClearColor(0.0,0.0,0.0,0.0); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); } 15 OpenGL et GTK de Hood /************************** * reshape() * Redimensionnement scéne ***************************/ void reshape(int height, int width) { glViewport(0,0,width,height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0,(GLfloat)width/(GLfloat)height,1.0,1000000.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } /************************* * draw() * Fonction de dessin Opengl **************************/ void draw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); gluLookAt(0.0,0.0,30.0,0.0,0.0,−1.0,0.0,1.0,0.0); glRotatef(angle,0.0,0.0,1.0); glBegin(GL_QUADS); glColor3f(1.0,0.0,0.0); glVertex3f(−10.0,−10.0,0.0); glColor3f(0.0,1.0,0.0); glVertex3f(−10.0,10.0,0.0); glColor3f(0.0,0.0,1.0); glVertex3f(10.0,10.0,0.0); glColor3f(1.0,1.0,0.0); glVertex3f(10.0,−10.0,0.0); glEnd(); } /*********************** * idle() * Fonction "idle" * Incrémente l'angle de * rotation d'une unité ************************/ void idle() { angle += (float)0.3; } 4. main.c /***************************************************** * main.c * Crée notre petite interface : une fenetre + un widget * opengl et met en place la fonction "idle" qui va * etre appelée quand GTK ne fait rien (trés souvent) * et qui fait tourner notre carré *****************************************************/ #include <gtk/gtk.h> // include pour GTK #include <stdio.h> #include <stdlib.h>/* fonctions pour créer un widget opengl */ 16 OpenGL et GTK de Hood //extern GtkWidget * CreateGl_GtkGlArea(); // avec les gtkglarea extern GtkWidget * CreateGl_GtkGlExt(); // avec les gtkglext extern void idle(); // code "opengl" de la fonction "idle" /**************************************** * idling() * Fonction appelée quand GTK ne fait rien. * Appelle une routine du fichier de * fonctions opengl et demande un * rafraichissement du widget (redraw) *****************************************/ gboolean idling(gpointer data) { idle(); // code dans gl.c gtk_widget_queue_draw(GTK_WIDGET(data)); // rafraichissement du widget passé en paramètre return TRUE; } /*************************************** * Ce bon vieux main(int argc, char ** argv) ... ****************************************/ void main(int argc, char ** argv) { GtkWidget * fenetre; // le widget qui va servir de fenetre GtkWidget * gl; // le widget opengl gboolean gtkglarea = FALSE; // un ptit booleen g_print("Utilisation de ?\n\n1− GtkGLArea\n2− GtkGLExt\n"); if(getchar() == '1') gtkglarea = TRUE; /* init GTK */ gtk_init(&argc,&argv); /* creation fenetre */ fenetre = gtk_window_new(GTK_WINDOW_TOPLEVEL); /* connection fermeture à sortie de la boucle GTK */ g_signal_connect(G_OBJECT(fenetre), // connecter qui ? "delete_event", // à quel evenement ? G_CALLBACK(gtk_main_quit), // et faire quoi quand il arrive ? NULL); // avec quels paramètres ? if(gtkglarea) { /* creation widget opengl avec GtkGLArea */ /*gl = CreateGl_GtkGlArea(); // code dans gtkglarea.c g_print("Utilisation de GtkGLArea !!\n"); gtk_window_set_title(GTK_WINDOW(fenetre),"> GtkGLArea <");*/ } else { /* creation widget opengl avec GtkGLExt */ gl = CreateGl_GtkGlExt(); // code dans gtkglext.c g_print("Utilisation de GtkGLExt !!\n"); gtk_window_set_title(GTK_WINDOW(fenetre),"> GtkGLExt <"); } /* ajout du widget opengl à notre fenetre */ gtk_container_add(GTK_CONTAINER(fenetre),GTK_WIDGET(gl)); /* affichage fenetre */ gtk_widget_show_all(GTK_WIDGET(fenetre)); /* mise en place fonction "idle" avec en paramètre le widget opengl*/ gtk_idle_add(idling, // la fonction qui sera appelée 17 OpenGL et GTK de Hood (gpointer)gl); // le paramètre à y passer /* c'est parti ! */ gtk_main(); } 18