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
- Il faudra coder votre shader dans un fichier source. (Un simple bloc-note peut suffir, mais avec des couleurs et la possibilité de voir le résultat, c'est mieux ! Exemple un éditeur en ligne: http://www.kickjs.org/example/shader_editor/shader_editor.html).
- Charger ce fichier source lors de l'exécution de votre application
en mémoire.
- Il s'agit de charger un fichier source en mémoire.
- Indiquer à OpenGL que vous allez utiliser un/des shaders.
- D'où une gestion d'un objet shader (création/suppression/test).
- Associer ce(s) shader(s) à un fichier source précédemment chargé.
- Compiler ce(s) shader(s) pour produire des binaires.
- Créer un objet programme sous OpenGL.
- D'où une gestion des programmes sous OpenGL.
- Linker les binaires de ce programme.
- Valider le programme ce qui permettra de pouvoir l'exécuter ensuite.
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é !).
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.
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.
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éé.
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.
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.
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.
- 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.
Lorsqu'un shader
est marqué comme supprimé, celui-ci sera effectivement effacé
lors du glDetachShader s'il n'est plus attaché à un quelconque
programme.
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
Annexes
Les explications équivalentes pour OpenGL sous Android.
Les explications équivalentes pour OpenGL sous Windows.