Sommaire:
– Introduction
– Un peu de code
– Références
1 – Introduction
Les shader subroutines (ou sous-routines de shader mais je vais garder le terme de subroutine dans la suite de cet article) font parties des fonctionnalités introduites avec OpenGL 4.0. Les subroutines sont définies par l’extension GL_ARB_shader_subroutine. Les subroutines permettent de sélectionner dynamiquement une fonction à éxecuter au sein d’un shader au lieu d’utiliser des tests conditionnels (les if(…)) ou des #define. Grâce aux subroutines, on peut avoir un shader multi-fonction genre tout-en-un ce qui présente l’avantage de n’avoir à binder qu’un seul programme GLSL au lieu de gérer (bind()…) une collection de shaders et GPU programmes spécialisés.
Les subroutines sont simples à mettre en oeuvre et efficaces alors il ne faut pas hésiter à s’en servir si cette solution est disponible.
Pour illustrer cet article, j’ai codé une petite demo toute simple avec GLSL Hacker, mon demotool préféré 😉 Cette demo dessine un PQ torus en trois couleurs différentes: rouge, bleu et jaune. Chaque couleur est gérée par une subroutine. Il suffit d’appuyer sur la touche ESPACE pour passer d’une subroutine à une autre et ainsi faire défiler les couleurs.
La demo est disponible dans le répertoire host_api/gl-400-arb-shader-subroutine/ du demopack. Elle fonctionne pour tous les OS (Windows, OS X et Linux) et une carte graphique supportant OpenGL 4 est nécessaire (NVIDIA GeForce GTX 400+, AMD Radeon HD 5000+, Intel HD Graphics 4000+).
2 – Un peu de code
Regardons directement le pixel shader, seul endroit de la demo où les subroutines sont utilisées.
#version 400 out vec4 FragColor; subroutine vec4 color_t(); subroutine uniform color_t Color; subroutine(color_t) vec4 ColorRed() { return vec4(1, 0, 0, 1); } subroutine(color_t) vec4 ColorBlue() { return vec4(0, 0.4, 1, 1); } subroutine(color_t) vec4 ColorYellow() { return vec4(1, 1, 0, 1); } void main() { FragColor = Color(); }
La première chose à faire est de déclarer le prototype de la subroutine:
subroutine vec4 color_t();
IL faut aussi déclarer une variable uniform de type subroutine. Cette variable est appellée Color et peut être vue comme un pointeur sur les vraies subroutines. Color() sera appellé pour dessiner le torus knot avec FragColor = Color();
subroutine uniform color_t Color;
Maintenant les vraies subroutines. Chaque subroutine doit suivre le prototype déclaré plus-haut. Pour dire au compilo GLSL qu’une fonction est une subroutine, il suffit d’utiliser le mot clé subroutine avec le nom du prototype avant le nom de la fonction:
subroutine(color_t) vec4 ColorRed() { return vec4(1, 0, 0, 1); }
Maintenant ColorRed() est une subroutine.
Dernière chose, appel de la subroutine:
void main() { FragColor = Color(); }
Vraiment simple. Mais comment Color() sait sur quelle subroutine pointer? Tout simplement en jouant avec les indices de subroutines au niveau application. Pour cela on va utiliser deux fonctions offertes par l’API OpenGL: glGetSubroutineIndex() et glUniformSubroutinesuiv().
Chaque subroutine possède un index (qui peut varier entre 0 et une valeur max que l’on peut récupérer à l’aide de GL_MAX_SUBROUTINES) dont la valeur nous est donnée avec glGetSubroutineIndex(). Voyons comment trouver l’index de la subroutine ColorRed() dans le pixel shader:
GLuint color_red_index = glGetSubroutineIndex(program, GL_FRAGMENT_SHADER, "ColorRed");
Une fois que tous les indices des subroutines sont connus, on peut sélectionner une subroutine particulière avec son index. Sélectionnons la subroutine ColorRed():
glUseProgram(program); glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &color_red_index); ... drawTorusKnot();
Dans la demo GLSL Hacker, la sélection de la subroutine ColorRed() se fait avec:
gh_gpu_program.set_uniform_subroutine(gpu_prog, GPU_SHADER_PIXEL, "Color", "ColorRed")
3 – Références
– GL_ARB_shader_subroutine specification
– Shader subroutine @ OpenGL wiki
– GLSL Core Tutorial – Subroutines
– OpenGL 4.0 review / subroutines