OpenGl et Gtk+ de Hood mis en ligne le 09 février 2003
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://www.gtk-fr.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:anonymous@anoncvs.gnome.org:/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.
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 :
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
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 :
* 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
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 :
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);
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 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 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(
GtkWidget * 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 :
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 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 [(robin.bourianes)@(wanadoo.fr)] @
GlinFrench? & Gtk-fr