Vous rencontrez essentiellement le genre de situation qui fait de NVIDIA Cg un logiciel si attrayant (à part le fait qu'il ne prend pas en charge GL | ES, que vous avez dit utiliser).
Notez également que vous ne devez vraiment pas utiliser glGetAttribLocation. Cette fonction est un mauvais juju depuis les premiers jours de GLSL avant que les responsables de GL ne commencent vraiment à comprendre comment un bon langage d'ombrage devrait fonctionner. Ce n'est pas obsolète car il a une utilisation occasionnelle, mais en général, préférez glBindAttibLocation ou l'extension de localisation d'attribut explicite (noyau dans GL 3.3+).
Gérer les différences dans les langages de shaders est de loin la partie la plus difficile du portage de logiciels entre GL et D3D. Les problèmes d'API que vous rencontrez concernant la définition de la disposition des sommets peuvent également être considérés comme un problème de langage de shader, car les versions GLSL avant 3.30 ne prennent pas en charge l'emplacement explicite des attributs (similaire dans l'esprit à la sémantique des attributs dans HLSL) et les versions GLSL avant 4.10 iirc ne prend pas en charge les liaisons uniformes explicites.
La "meilleure" approche consiste à disposer d'une bibliothèque de langages d'ombrage de haut niveau et d'un format de données qui encapsule vos packages de shaders. Ne vous contentez PAS d'alimenter un groupe de GLSL / HLSL bruts vers une classe Shader mince et attendez-vous à pouvoir proposer n'importe quel type d'API sensée.
Au lieu de cela, mettez vos shaders dans un fichier. Enveloppez-les dans un peu de métadonnées. Vous pouvez utiliser XML et écrire des packages de shader comme:
<shader name="bloom">
<profile type="glsl" version="1.30">
<source type="vertex"><![CDATA[
glsl vertex shader code goes here
]]></source>
<source type="fragment"><![CDATA[
glsl fragment shader code goes here
]]></source>
</profile>
<profile type="hlsl" version="sm3">
<source type="fx"><![CDATA[
hlsl effects code goes here
you could also split up the source elements for hlsl
]]></source>
</profile>
</shader>
Écrire un analyseur minimal pour cela est trivial (utilisez simplement TinyXML par exemple). Laissez votre bibliothèque de shaders charger ce package, sélectionnez le profil approprié pour votre rendu cible actuel et compilez les shaders.
Notez également que si vous préférez, vous pouvez conserver la source externe à la définition du shader, mais conserver le fichier. Mettez simplement les noms de fichiers au lieu de source dans les éléments source. Cela peut être avantageux si vous prévoyez de précompiler des shaders, par exemple.
La partie la plus difficile maintenant est bien sûr de traiter avec GLSL et ses lacunes. Le problème est que vous devez lier les emplacements d'attributs à quelque chose qui ressemble à la sémantique HLSL. Cela peut être fait en définissant cette sémantique dans votre API, puis en utilisant glBindAttribLocation avant de lier le profil GLSL. Votre cadre de package de shader peut gérer cela explicitement, sans que votre API graphique ait besoin d'exposer les détails.
Vous pouvez le faire en étendant le format XML ci-dessus avec de nouveaux éléments dans le profil GLSL pour spécifier explicitement les emplacements des attributs, par exemple
<shader name="bloom">
<profile type="glsl" version="1.30">
<attrib name="inPosition" semantic="POSITION"/>
<attrib name="inColor" semantic="COLOR0"/>
<source type="vertex"><![CDATA[
#version 150
in vec4 inPosition;
in vec4 inColor;
out vec4 vColor;
void main() {
vColor = inColor;
gl_Position = position;
}
]]></source>
</profile>
</shader>
Le code de votre package de shader lirait tous les éléments attrib dans le XML, en saisirait le nom et la sémantique, rechercherait l'index d'attribut prédéfini pour chaque sémantique, puis appeler automatiquement glBindAttribLocation pour vous lors de la liaison du shader.
Le résultat final est que votre API peut maintenant se sentir beaucoup plus agréable que votre ancien code GL n'a probablement jamais semblé, et même un peu plus propre que D3D11 permettrait:
// simple example, easily improved
VertexLayout layout = api->createLayout();
layout.bind(gfx::POSITION, buffer0, gfx::FLOATx4, sizeof(Vertex), offsetof(Vertex, position));
layout.bind(gfx::COLOR0, buffer0, gfx::UBYTEx4, sizeof(Vertex), offsetof(Vertex, color));
Notez également que vous n'avez pas strictement besoin du format de package de shader. Si vous voulez garder les choses simples, vous êtes libre d'avoir juste une sorte de fonction loadShader (const char * name) qui saisit automatiquement les fichiers GLSL name.vs et name.fs en mode GL et les compile et les relie. Cependant, vous allez absolument vouloir ces métadonnées d'attribut. Dans le cas simple, vous pouvez augmenter votre code GLSL avec des commentaires spéciaux faciles à analyser, comme:
#version 150
/// ATTRIB(inPosition,POSITION)
in vec4 inPosition;
/// ATTRIB(inColor,COLOR0)
in vec4 inColor;
out vec4 vColor
void main() {
vColor = inColor;
gl_Position = inPosition;
}
Vous pouvez obtenir autant de fantaisie que vous le souhaitez dans l'analyse des commentaires. Plus de quelques moteurs professionnels iront jusqu'à faire des extensions de langage mineures qu'ils analyseront et modifieront même, comme simplement ajouter carrément des déclarations sémantiques de style HLSL. Si votre connaissance de l'analyse est solide, vous devriez pouvoir trouver de manière fiable ces déclarations étendues, extraire les informations supplémentaires, puis remplacer le texte par le code compatible GLSL.
Peu importe comment vous le faites, la version courte consiste à augmenter votre GLSL avec les informations sémantiques d'attribut manquantes et à faire en sorte que votre abstraction de chargeur de shader traite l'appel à glBindAttribLocation pour corriger les choses et les rendre plus semblables aux versions GLSL et HLSL modernes et faciles à utiliser.