I. Introduction

I-A. Pourquoi des extensions ?

Les extensions dans OpenGL permettent de rajouter des fonctionnalités sans modifier le cœur d'OpenGL (norme donnée par Khronos). Les extensions sont produites par les entreprises enregistrées dans le groupe Khronos telles que Nvidia, ATI, Apple et bien d'autres. Bien souvent, une fois que l'extension est reconnue comme essentielle, elle est intégrée dans le cœur d'OpenGL et la version d'OpenGL change. La liste des extensions pour OpenGL peut être trouvée en bas de cette page : http://www.opengl.org/registry/ . Il faut savoir qu'aucun constructeur n'est tenu d'inclure des extensions dans ses cartes graphiques. En effet pour être conforme à la norme OpenGL, il suffit juste de correctement implémenter le cœur d'OpenGL. Cela signifie que rien ne vous certifie que vous aurez une extension dans la machine exécutant votre programme.

I-B. Pourquoi un tutoriel ?

Principalement pour expliquer précisément ce que sont les extensions et pour savoir les utiliser dans tous les cas, avec ou sans bibliothèques externes.

I-C. Prérequis

Il vous sera utile de savoir comment créer un projet OpenGL ainsi que d'ouvrir une fenêtre OpenGL. Vous pouvez trouver des tutoriels sur ce point en suivant les liens suivants :

I-D. Noms des extensions

Comme je l'ai dit un peu avant, différentes sociétés développent leurs extensions. Il existe une norme de nommage pour les extensions qui permet de connaître la bibliothèque intégrant l'extension, le nom de la compagnie à l'origine de l'extension et, finalement, le nom de l'extension.

Le nom de l'extension est constitué comme suit :

  • GL_ID-DEVELOPPEUR_nom-de-l'extension ;
  • GLX_ID-DEVELOPPEUR_nom-de-l'extension ;
  • WGL_ID-DEVELOPPEUR_nom-de-l'extension.

GL / GLX et WGL désignent la bibliothèque sur laquelle l'extension agit. Nous ne nous intéressons qu'aux extensions OpenGL (donc GL) mais le principe est exactement le même pour les autres.

Rappel : GLX, WGL mais aussi AGL et PGL sont des bibliothèques qui ont pour rôle de faire le lien entre OpenGL et le système de fenêtre du système que vous utilisez : 'X' pour le serveur X présent entre autres sous Linux, 'W' pour Windows, 'A' pour Apple et 'P' pour IBM OS/2 Warp.

ID-DEVELOPPEUR est le nom de la compagnie qui a créé la norme de l'extension. On peut entre autres voir les noms suivants :

  • ARB pour Architecture Review Board (qui correspond au Comité d'évaluation de l'architecture d'OpenGL. C'est le groupe qui valide les spécifications d'OpenGL) ;
  • EXT pour les extensions mises au point par un groupe de compagnies, sans particularité quelconque ;
  • NV pour Nvidia ;
  • ATI pour ATI, maintenant appelé AMD ;
  • SGI pour Silicon Graphic International (la société à l'origine d'OpenGL).

Et j'en passe.

Et à la fin, le nom de l'extension, qui se doit d'être clair.

Voici quelques exemples tirés de cette page :

I-E. La vie des extensions

Comme il a été dit, les constructeurs présentent leurs extensions. Celles-ci ne sont pas réservées à la compagnie à l'origine de l'extension et peuvent être implémentées par toutes autres compagnies. Une fois qu'une extension est présentée par une compagnie (ou un ensemble de compagnies) elle va être revue par l'ARB. Lorsque l'extension sera validée par l'ARB elle changera de nom pour prendre les lettres ARB, à la place des lettres désignant le constructeur. Il y a de très fortes chances que les extensions ayant obtenu cette ratification soient implémentées sur toutes les cartes sorties après le jour de cette ratification. Une fois qu'une extension a atteint ce stade, il est probable de voir cette extension dans la prochaine version d'OpenGL en intégrant le cœur d'OpenGL (et qu'elle soit donc incluse d'office sur toutes les cartes compatibles avec cette version d'OpenGL).

II. Utiliser une extension et ses fonctions associées

II-A. Récupérer la liste des extensions

Comme il est dit en introduction, chaque machine embarque son lot d'extensions, et une extension présente dans une machine, peut ne pas l'être dans une autre. C'est pourquoi il est important de savoir récupérer la liste des extensions présentes dans la machine, sinon votre programme va vouloir exécuter des morceaux de code qu'il n'y a pas (ça va crasher !).

Pour ce faire, nous utilisons la commande glGetString() avec comme paramètre 'GL_EXTENSIONS'.

Bien que cette fonction puisse être très simple, il faut tout de même que votre fenêtre OpenGL soit initialisée pour que cela fonctionne. Sinon la fonction provoquera une erreur (récupérable avec glGetError()).

Afficher la liste des extensions supportées
Sélectionnez
void displayExtensions(void)
{
    printf("%s\n",glGetString(GL_EXTENSIONS));
}

Bien sûr, on pourrait améliorer l'affichage de la liste des extensions (en remplaçant les espaces par des sauts de ligne par exemple, mais cela ne nous est pas utile pour la suite du tutoriel).

II-B. Savoir si l'extension est présente

Maintenant, il faut faire en sorte que notre programme sache si les extensions dont nous avons besoin sont présentes. Si elles ne le sont pas, le programmeur pourra soit arrêter le programme avec un message d'erreur, soit passer outre et faire en sorte de ne pas utiliser toutes les fonctionnalités du programme.

Pour cela nous allons analyser la chaîne de caractères retournée par glGetString(GL_EXTENSIONS).

Un simple test n'utilisant que strstr() ne suffit pas ! Prenons le cas des extensions 'GL_ARB_draw_buffers' et 'GL_ARB_draw_buffers_blend'. Si vous cherchez 'GL_ARB_draw_buffers' et que vous n'avez QUE 'GL_ARB_draw_buffers_blend', strstr() vous retournera un résultat valide bien que vous n'ayez pas l'extension.

Savoir si une extension donnée est présente sur la machine
Sélectionnez
char hasExtension(const char* extensionName)
{
/*
    Le cast est nécessaire à cause du type de pointeur retourné
    ( GLubyte* correspondant à un const unsigned char* )
*/
    const char* extensionsList = (const char*)glGetString(GL_EXTENSIONS);
    char* extensionMatch = NULL;

    while ( (extensionMatch = strstr(extensionsList, extensionName)) != NULL )
    {
        // Vérification: avons-nous la même chaine que l'extension voulue ?
        if ( strncmp(extensionMatch,extensionName,strlen(extensionName)) == 0 )
        {
            // Vérification: le nom trouvé se termine-t-il bien ici ?
            if ( extensionMatch[strlen(extensionName)] == ' ')
            {
                return 1;
            }
        }

        // On déplace le pointeur de recherche après la chaine trouvée pour continuer la recherche
        extensionsList = extensionMatch + strlen(extensionMatch);
    }

    return 0;
}

L'utilisation est la suivante :

 
Sélectionnez
printf("Est ce que j'ai les Geometry Shader 4 : ? -> ");
if ( hasExtension("GL_ARB_geometry_shader4") )
{
    printf("OUI\n");
}
else
{
    printf("NON\n");
}

II-C. Récupérer l'adresse d'une fonction

Lorsque nous voulons utiliser les fonctions d'une extension, la seule chose que nous connaissons c'est son prototype, écrit dans le fichier d'en-têtes des extensions d'OpenGL.

En fait, le programme ne connaît pas encore les adresses des fonctions des extensions, car comme je vous l'ai dit, ces extensions peuvent être présentes, ou pas. De ce fait, nous allons devoir récupérer l'adresse de la fonction nous-même.

Pour ce faire, les bibliothèques qui se chargent de l'ouverture de la fenêtre OpenGL nous propose une fonction ayant ce but. Son nom est *GetProcAddress (je mets une étoile avant le nom de la fonction car il y a généralement des lettres avant GetProcAddress() selon la bibliothèque que vous utilisez).

La fonction va retourner un pointeur sur une fonction, qu'il faudra caster pour que ce soit un pointeur sur le prototype de la fonction réelle (comme indiqué dans la documentation ou dans le fichier d'en-têtes). Si vous ne faites pas ce cast, le compilateur n'acceptera pas que vous passiez des paramètres à une fonction qui pour lui, ne prend pas de paramètres. Dans le cas contraire (utilisation de la fonction sans avoir fait le cast, sans paramètres alors qu'elle est censée en avoir) un bug se produira.

Finalement, si la fonction voulue n'est pas trouvée, *GetProcAddress() retournera un pointeur NULL.

Prenons la fonction ayant le prototype suivant (pris du glext.h) :

 
Sélectionnez
GLAPI void APIENTRY glFramebufferTextureFaceARB (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face);

avec un peu plus bas dans le fichier :

 
Sélectionnez
typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTUREFACEARBPROC) (GLenum target, GLenum attachment, GLuint texture, GLint level, GLenum face);

Le premier prototype n'est inclus que si GL_GLEXT_PROTOTYPES est défini. Mais cela ne peut marcher que si la fonction est obligatoirement dans le code du pilote de la carte graphique. Sinon, cela ne compile pas (erreur de liaison, le code de la fonction n'est pas trouvé).

Pour que la fonction soit opérationnelle, nous définissons le pointeur comme suit :

 
Sélectionnez
PFNGLFRAMEBUFFERTEXTUREFACEARBPROC glFramebufferTextureFaceARB_Fct;

Et afin de récupérer le pointeur :

 
Sélectionnez
glFramebufferTextureFaceARB_Fct = (PFNGLFRAMEBUFFERTEXTUREFACEARBPROC)glutGetProcAddress("glFramebufferTextureFaceARB");

Il est tout de même préférable et même recommandé de vérifier si votre pointeur retourné n'est pas NULL !

Une fois que cela est fait la fonction peut-être utilisée comme toute autre :

 
Sélectionnez
glFramebufferTextureFaceARB_Fct(param1,param2,param3,param4,param5);

Si les types PFNGL* ne sont pas reconnus, c'est que vous avez un fichier d'en-têtes trop vieux. Vous pouvez le changer, ou alors, définir le type (en copiant le morceau du fichier d'en-têtes en question) dans votre propre fichier, mais n'oubliez pas de définir correctement le APIENTRY aussi (on peut le recopier à partir du fichier d'en-têtes d'OpenGL).

II-D. Information sur les fonctions dans le cœur d'OpenGL > 1.1

Malheureusement, Microsoft Windows n'a jamais été distribué avec une version d'OpenGL supérieure à la version 1.1. Cela veut dire que pour avoir, par exemple, les shaders (inclus dans le cœur de la version 2.0) nous allons devoir récupérer les adresses des fonctions comme si elles étaient encore des extensions.

Du coup, il est aussi important de vérifier la version d'OpenGL que vous utilisez (avec glGetString(GL_VERSION))

III. Bibliothèques externes

III-A. GLEW

GLEW est une bibliothèque qui permet une gestion simplifiée des extensions. La bibliothèque fait elle-même tout le processus que j'ai expliqué auparavant.

Après la création de la fenêtre, GLEW doit être initialisé afin que la bibliothèque puisse assigner et définir les pointeurs sur les extensions ainsi que d'autres variables permettant de connaître facilement la version d'OpenGL présente.

Initialisation GLEW
Sélectionnez
GLenum err = glewInit();
if (GLEW_OK != err)
{
  /* Problème : glewInit a échoué, quelquechose va sérieusement mal */
  fprintf(stderr, "Erreur : %s\n", glewGetErrorString(err));
  ...
} 

Il reste toujours à vérifier si les extensions voulues sont présentes :

depuis la version 1.1.0 de GLEW vous pouvez savoir si une extension est présente grâce aux variables ayant la forme GLEW_{nom_de_l_extension}.

Est-ce que l'extension GL_ARB_vertex_program est présente ?
Sélectionnez
if (GLEW_ARB_vertex_program)
{
  /* C'est bon, vous pouvez utilisé l'extension ARB_vertex_program. */
  glGenProgramsARB(...);
} 

Pour plus d'informations sur les possibilités de la bibliothèque, je vous invite à voir ce lien : http://glew.sourceforge.net/basic.html (en anglais).

L'inclusion du fichier d'en-têtes de GLEW doit se faire avant les autres inclusions relatives à OpenGL ou à la bibliothèque qui crée la fenêtre. Si vous ne le faites pas, vous aurez des erreurs de compilation.

III-B. GLEE

GLEE est une bibliothèque qui a le même rôle que GLEW. À vrai dire, GLEE existait avant l'apparition de GLEW mais n'intègre pas les dernières extensions et fonctionnalités d'OpenGL.

L'utilisation de GLEE est simple. Il suffit juste d'inclure le fichier d'entête de la bibliothèque et de vérifier si l'extension est présente :

Utilisation de GLEE
Sélectionnez
if (GLEE_ARB_multitexture)    // Est-ce que le support des multitexture est présent
{
  glMultiTexCoord2fARB(...);  // Aucun risque d'utiliser la fonction.
}
else
{
   // Retour d'erreur
}

IV. Lire la documentation d'une extension

Savoir lire une documentation est la chose la plus importante que doit savoir faire un programmeur.

La documentation des extensions est, d'après moi, loin d'être la plus simple. Elle est contenue dans un fichier texte presque sans aucune miss en page.

Le fichier a la structure générale suivante :

  • Name : le nom de l'extension ;
  • Name string : le nom comme on le verra dans la liste des extensions lors de l'exécution de notre programme ;
  • Contact : les personnes à contacter au sujet de l'extension ;
  • Status : l'état actuel de l'extension (approuvée ou non pas l'ARB) ;
  • Version : la version actuelle de l'extension ;
  • Number : numéro de l'extension ;
  • Dependencies : les dépendances que nécessite l'extension et la liste des extensions impactées par celle-ci ;
  • Overview : une brève description de l'extension ;
  • New Procedures and Functions : les nouvelles fonctions qu'implémente l'extension ;
  • New Tokens : les nouvelles variables qu'introduit l'extension ;
  • Additions ;suivi de l'emplacement de l'ajout : Ajout de documentation dans le document de la norme d'OpenGL. Ici se trouve le détail complet de l'extension et de son implémentation ;
  • Dependencies ;et le nom de l'extension que cela affecte : Explication de l'ajout des fonctionnalité de cette extension, au sein d'autres extensions ;
  • Errors : liste des erreurs que peut provoquer l'extension ;
  • New states : liste des nouveaux états provoqués par l'extension ;
  • Issues : toutes les questions qui ont été posées durant la conception pour clarifier des détails sur l'extension ;
  • Revision history : la liste des changements au fil des versions.

Il se peut qu'il y ait une légère différence avec cette énumération, mais dans la globalité, cela reste toujours de cette forme là.

V. Liens

VI. Remerciements

Je tiens à remercier raptor70 pour m'avoir poussé à écrire des tutoriels et m'avoir conseillé pour la réussite de l'écriture de ceux-ci, Axel Mamode pour sa remarque sur la fonction de vérification des extensions, ainsi que Mahefasoa, Caro-Line et jacques_jean pour avoir relu et corrigé le tutoriel.