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

Documents pareils

Cours Gtk+ 2 - www.gtk-fr.org

Cours Gtk+ 2 - www.gtk-fr.org Cours Gtk+ 2 − www.gtk−fr.org

Plus en détail