Je me retrouve souvent à copier-coller du code entre plusieurs shaders. Cela inclut à la fois certains calculs ou données partagés entre tous les shaders dans un même pipeline, et des calculs communs dont tous mes vertex shaders ont besoin (ou toute autre étape).
Bien sûr, c'est une pratique horrible: si je dois changer le code n'importe où, je dois m'assurer de le changer partout ailleurs.
Existe-t-il une meilleure pratique acceptée pour garder au SEC ? Les gens ajoutent-ils simplement un seul fichier commun à tous leurs shaders? Écrivent-ils leur propre préprocesseur rudimentaire de style C qui analyse les #include
directives? S'il existe des modèles acceptés dans l'industrie, j'aimerais les suivre.
Réponses:
Il y a un tas d'approches, mais aucune n'est parfaite.
Il est possible de partager du code en utilisant
glAttachShader
pour combiner des shaders, mais cela ne permet pas de partager des choses comme des déclarations de structure ou des#define
constantes -d. Cela fonctionne pour le partage des fonctions.Certaines personnes aiment utiliser le tableau de chaînes passé pour
glShaderSource
ajouter des définitions communes avant votre code, mais cela présente certains inconvénients:#version
, en raison de la déclaration suivante dans la spécification GLSL:En raison de cette déclaration,
glShaderSource
ne peut pas être utilisé pour ajouter du texte avant les#version
déclarations. Cela signifie que la#version
ligne doit être incluse dans vosglShaderSource
arguments, ce qui signifie que votre interface de compilation GLSL doit en quelque sorte être informée de la version de GLSL qui devrait être utilisée. De plus, le fait de ne pas spécifier de#version
rendra le compilateur GLSL par défaut pour utiliser GLSL version 1.10. Si vous souhaitez laisser les auteurs de shader spécifier le#version
contenu du script de manière standard, vous devez en quelque sorte insérer#include
-s après l'#version
instruction. Cela pourrait être fait en analysant explicitement le shader GLSL pour trouver la#version
chaîne (si elle est présente) et faire vos inclusions après, mais en ayant accès à un#include
La directive pourrait être préférable de contrôler plus facilement quand ces inclusions doivent être faites. D'autre part, puisque GLSL ignore les commentaires avant la#version
ligne, vous pouvez ajouter des métadonnées pour les inclus dans les commentaires en haut de votre fichier (beurk).La question est maintenant: existe-t-il une solution standard pour
#include
, ou avez-vous besoin de rouler votre propre extension de préprocesseur?Il y a l'
GL_ARB_shading_language_include
extension, mais elle a quelques inconvénients:"/buffers.glsl"
(utilisée dans#include "/buffers.glsl"
) correspond au contenu du fichierbuffer.glsl
(que vous avez chargé précédemment)."/"
, comme les chemins absolus de style Linux. Cette notation n'est généralement pas familière aux programmeurs C et signifie que vous ne pouvez pas spécifier de chemins relatifs.Une conception courante consiste à implémenter votre propre
#include
mécanisme, mais cela peut être délicat car vous devez également analyser (et évaluer) d'autres instructions de préprocesseur comme#if
pour gérer correctement la compilation conditionnelle (comme les gardes d'en-tête.)Si vous implémentez le vôtre
#include
, vous avez également quelques libertés dans la façon dont vous souhaitez l'implémenter:GL_ARB_shading_language_include
).Pour simplifier, vous pouvez insérer automatiquement des protections d'en-tête pour chaque inclusion dans votre couche de prétraitement, afin que votre couche de processeur ressemble à:
(Nous remercions Trent Reed de m'avoir montré la technique ci-dessus.)
En conclusion , il n'existe pas de solution automatique, standard et simple. Dans une future solution, vous pourriez utiliser une interface OpenGL SPIR-V, auquel cas le compilateur GLSL vers SPIR-V pourrait être en dehors de l'API GL. Le fait d'avoir le compilateur en dehors du runtime OpenGL simplifie considérablement la mise en œuvre de choses comme,
#include
car c'est un endroit plus approprié pour s'interfacer avec le système de fichiers. Je crois que la méthode répandue actuelle consiste simplement à implémenter un préprocesseur personnalisé qui fonctionne d'une manière que tout programmeur C devrait être familier.la source
J'utilise généralement le fait que glShaderSource (...) accepte un tableau de chaînes comme entrée.
J'utilise un fichier de définition de shader basé sur json, qui spécifie comment un shader (ou un programme pour être plus correct) est composé, et là je spécifie le préprocesseur définit dont j'ai besoin, les uniformes qu'il utilise, le fichier de shaders de vertex / fragment, et tous les fichiers "dépendances" supplémentaires. Ce ne sont que des collections de fonctions qui sont ajoutées à la source avant la source réelle du shader.
Juste pour ajouter, AFAIK, l'Unreal Engine 4 utilise une directive #include qui est analysée et ajoute tous les fichiers pertinents, avant la compilation, comme vous le suggérez.
la source
Je ne pense pas qu'il existe une convention commune, mais si je devine, je dirais que presque tout le monde implémente une forme simple d'inclusion textuelle comme étape de prétraitement (une
#include
extension), car c'est très facile à faire alors. (En JavaScript / WebGL, vous pouvez le faire avec une simple expression régulière, par exemple). L'avantage de cela est que vous pouvez effectuer le prétraitement dans une étape hors ligne pour les versions "release", lorsque le code du shader n'a plus besoin d'être modifié.En fait, une indication que cette approche est commune est le fait qu'une extension ARB a été introduite pour que:
GL_ARB_shading_language_include
. Je ne sais pas si cela est devenu une fonctionnalité principale maintenant ou non, mais l'extension a été écrite contre OpenGL 3.2.la source
Certaines personnes ont déjà souligné que cela
glShaderSource
peut prendre un certain nombre de chaînes.De plus, dans GLSL, la compilation (
glShaderSource
,glCompileShader
) et la liaison (glAttachShader
,glLinkProgram
) du shader sont séparées.Je l'ai utilisé dans certains projets pour diviser les shaders entre la partie spécifique et les parties communes à la plupart des shaders, qui est ensuite compilée et partagée avec tous les programmes de shaders. Cela fonctionne et n'est pas difficile à implémenter: il suffit de maintenir une liste de dépendances.
En termes de maintenabilité cependant, je ne suis pas sûr que ce soit une victoire. L'observation était la même, essayons de factoriser. Bien qu'il évite en effet la répétition, les frais généraux de la technique semblent importants. De plus, le shader final est plus difficile à extraire: vous ne pouvez pas simplement concaténer les sources du shader, car les déclarations se terminent dans un ordre que certains compilateurs rejetteront ou seront dupliqués. Il est donc plus difficile de faire un test de shader rapide dans un outil séparé.
Au final, cette technique résout certains problèmes SECS, mais elle est loin d'être idéale.
Sur un sujet secondaire, je ne sais pas si cette approche a un impact en termes de temps de compilation; J'ai lu que certains pilotes ne compilent vraiment le programme de shader que sur la liaison, mais je n'ai pas mesuré.
la source