Compilation/linkage des shaders sous Linux

 

Principe

Pour ceux qui développent en C, C++ voire assembleur, rien de nouveau en fait.

Il s'agit à partir de sources de produire un programme qui s'exécutera sur le GPU.

Il faut donc compiler tous les sources, pour produire des binaires, puis linker ces binaires pour produire l'exécutable.

 

Pourquoi livrer des sources

L'idée est de permettre d'avoir un exécutable juste au moment de l'exécution, rendant le programme indépendant des plateformes, et évite ainsi de devoir fournir autant de programmes que de cartes graphiques par exemple.

En effet, le compilateur/linker fait parti intégrante d'OpenGL, mais au niveau driver. Les constructeurs peuvent ainsi optimiser le code suivant leur carte graphique, voire ajouter certaines fonctions spécifiques.

Le problème étant que certains développeurs n'aiment pas trop la distribution de codes ainsi aussi facilement recopiable. L'ARB est donc en train d'étudier la possibilité de fournir du code précompilé plutôt que des sources.

 

Les différentes phases de la compilation/linkage

 

Gestion d'un objet shader

Création d'un shader

Il s'agit de créer un objet shader, sous OpenGL, vous utiliserez la fonction GLuint glCreateShader (GLenum type).

En entrée:

type est le type de shader que vous voulez utiliser:GL_VERTEX_SHADER, GL_FRAGMENT_SHADER

En sortie:

Un identifiant sur un nouveau shader qu'il vous faudra bien conserver ! Si à 0, il n'a pas été possible de le créer (Par exemple, OpenGL non initialisé !).

Image non trouvée !Vous avez une "instance" de shader spécialisée sur un type précis de shader, mais il est vide pour le moment !

 

Suppression d'un shader

Puisque vous avez créé un shader, il faudra le supprimer à un moment ou un autre: void glDeleteShader (GLuint shader)

En entrée:

shader est l'identifiant du shader (pensez à repositionner cet identifiant à 0 pour éviter de le réutiliser alors qu'il n'existe plus).

En sortie:

GL_INVALID_VALUE si l'identifiant n'est pas un identifiant généré par OpenGL.

 

Image non trouvée !Un shader attaché à un programme sera marqué comme suppimé, mais ne sera supprimé effectivement que lorsque ce programme sera lui aussi supprimé.

 

Tester la validité d'un shader

Il sera possible de vérifier la validité d'un identifiant en utilisant la fonction GLIsShader (GLuint shader). La fonction retournant GL_TRUE si l'identifiant est valide . GL_FALSE sinon.

Image non trouvée !Utilisez glGetShader avec GL_DELETE_STATUS pour vérifier qu'un shader est évetuellement supprimé.

 

Associer un shader à un fichier source glsl

Les fichiers sources de shaders sont maintenant à associer aux différents shaders correspondant. Il faudra utiliser la fonction glShaderSource()

void glShaderSource( GLuint shader,
GLsizei count,
const GLchar **string,
const GLint *length);

En entrée:

shader est l'identifiant de l'objet shader précédemment créé.

Image non trouvée !Les sources précédemments chargés pour ce shader seront purement et simplement écrasés par les nouveaux sources.

**string est un tableau de strings. chacun des strings contenant un "fichier source". Généralement, vous n'aurez qu'un élément dans ce tableau pour un fichier source préalablement chargé. Si vous en avez besoin de plusieurs, on peut comparer ces n string à des "includes" (par correspondance avec les langages de programmation comme le C). Dans ce cas, à vous de les ordonnées comme il faut pour produire un source final correct (le source final étant composé de l'ensemble ordonné par leur position des éléments du tableau) !

*length est un tableau contenant la taille du/des strings dans le tableau. Si à NULL (à priori, ce sera le cas le plus fréquent), les éléments n'ont pas de taille prédéterminée et il doivent se terminer par un null pour indiquer la fin de la chaîne.

et count qui indique le nombre d'éléments (ou d'includes) dans le tableau (sera généralement positionné à 1, pour 1 fichier source).

 

En sortie:

Le fichier source est recopié dans le shader (vous pouvez libérer la mémoire en passant),

ou erreur:

GL_INVALID_VALUE l'identifiant shader n'a pas été généré via OpenGL

GL_INVALID_OPERATION pour un objet existe pour cet identifiant, mais ce n'est pas un shader !

GL_INVALID_VALUE si count est < 0.

 

Compiler le source d'un shader

Une fois le source renseigné dans le shader, vous pouvez le compiler: glCompileShader()

void glCompileShader(GLuint shader);

En entrée:

L'identifiant du shader.

 

En sortie:

Il faudra utiliser la fonction glGetShaderiv avec GL_COMPILE_STATUS pour vérifier qu'il n'y a pas eu d'erreur lors de la compilation.

Si erreur, afficher le résultat de la compilation pour comprendre ce qui se passe !

 

Gestion d'un objet programme sous OpenGL

La dernière phase consiste à produire un programme exécutable. Mais il faut d'abord indiquer à OpenGL que vous désrirez créer un programme, puis attacher à ce programme les différents shaders qui seront utilisés pour produire ce programme.

Création d'un objet programme

La fonction glCreateProgram permet de récupérer un identifiant sur un objet programme. Ce programme sera "vide", vous obtenez juste une instance d'un programme qu'il faudra ensuite initialiser.

GLuint glCreateProgram( void);

En sortie:

0 si problème, sinon, identifiant d'un programme qu'il faudra mémoriser pour un usage ultérieur !

 

Supprimer un objet programme

Il faudra à un moment supprimer ce programme.

void glDeleteProgram( GLuint program);

En entrée:

program, l'identifiant de votre objet program à supprimer

En sortie:

GL_INVALID_VALUE si l'identifiant n'est pas un identifiant généré par OpenGL.

Image non trouvée !La suppression d'un programme détache automatiquement les shaders. Ceux-ci ne seront pas effacés sauf s'ils étaient eux même déjà marqués comme supprimés.

 

Tester la validité d'un identifiant d'un objet programme

GLboolean glIsProgram( GLuint program);

retoune GL_TRUE si l'identifiant est valide.

Image non trouvée !Utilisez glGetProgram avec GL_DELETE_STATUS pour vérifier qu'un programme est évetuellement supprimé.

 

Attacher/détacher des shaders

Attacher des shaders à un programme permet d'indiquer les shaders qui seront utiles pour produire le programme final:

void glAttachShader( GLuint program, GLuint shader);

 

Il sera aussi possible de les détacher:

void glDetachShader( GLuint program, GLuint shader); pour détacher un shader d'un programme.

 

En sortie de ces fonctions

GL_INVALID_VALUE si l'identifiant program ou shader n'a pas été généré par OpenGL.

GL_INVALID_OPERATION si:

  • program n'est pas un object programme ou shader n'est pas un identifiant de shader,
  • ou si glDetachShader avec un shader qui n'est pas attaché au programme,
  • ou si glAttachShader et que le shader est déjà associé au programme.

Image non trouvée !Lorsqu'un shader est marqué comme supprimé, celui-ci sera effectivement effacé lors du glDetachShader s'il n'est plus attaché à un quelconque programme.

Image non trouvée !Vous ne pouvez pas vous contentez de fournir une code de shader de type vertex à un programme, il faut aussi livrer un code pour le shader de type fragment, même s'il ne fait rien de spécial. Sinon, le linkage se passera sans problème, mais le résultat de l'exécution du programme sera indéterminé !

 

Linker les binaires de ce programme

Avant de produire un programme, il faut attacher des shaders à celui-ci. C'est en effet les shaders qui ont les binaires qui permettront de produire le code final.

Produire un programme se fait dans une phase de linkage (lié les binaires entre eux).

L'opération de linkage est simple, puisqu'il suffit d'appeler la fonction

void glLinkProgram( GLuint program);

qui attend en entrée l'identifiant de l'objet programme à linker.

 

En sortie, le programme est peut-être linké ou

GL_INVALID_VALUE si l'identifiant n'est pas un identifiant généré par OpenGL.

GL_INVALID_OPERATION si l'identifiant program n'est pas un objet programme ou si program est déjà actif et que le mode transform feedback est actif.

 

Pour vérifier qu'il est effectivement linké, il sera nécessaire d'utiliser la fonction glGetProgramiv avec GL_LINK_STATUS pour vérifier qu'il n'y a pas eu d'erreur lors du linkage. Si erreur, affichez le log pour comprendre ce qui se passe !

 

 

Valider le programme produit

Pour pouvoir utiliser un programme, il faut tout d'abord contrôler que celui-ci peut s'exécuter sous OpenGL.

void glValidateProgram( GLuint program);

qui attend en entrée l'identifiant de l'objet programme.

ou

GL_INVALID_VALUE si l'identifiant n'est pas un identifiant généré par OpenGL.

GL_INVALID_OPERATION si l'identifiant program n'est pas un objet programme

Là encore, il sera possible d'utiliser la fonction glGetProgramiv avec GL_VALIDATE_STATUS cette fois-ci pour vérifier qu'il n'y a pas eu d'erreur. Si erreur, affichez le log pour comprendre ce qui se passe !

Exemples de codes

Exemple pour gérer les shaders

Voici un exemple de classe en C++ pour gérer des shaders:

Shader.cpp

Et son include Shader.h

Et comme j'ai bon coeur, une factory: FactoryShader.cpp

Suivie de son inlude FactoryShader.h

 

Annexe

Les explications équivalente pour OpenGL SE sous Android.

 

 

Exemple pour gérer les programmes

un exemple de classe en C++ pour gérer des programmes:

ProgramGLSL.cpp

Et son include ProgramGLSL.h

Et une factory: FactoryProgramGLSL.cpp

Suivie de son inlude FactoryProgramGLSL.h

Vous pouvez créer une classe helper qui va produire un programme en fournissant en paramètres les codes sources de shaders par exemple...

 

Exemple d'appel

Avec vertexSource et fragmentSource contenant les sources des shaders.

 

Et les sources au format zip

Image non trouvée !

 

Annexes

Pb. lors de la compilation

Les explications équivalentes pour OpenGL sous Android.

Les explications équivalentes pour OpenGL sous Windows.