Fondamentalmente stai incontrando il tipo di situazione che rende NVIDIA Cg un software così attraente (a parte il fatto che non supporta GL | ES, che hai detto che stai usando).
Nota anche che non dovresti davvero usare glGetAttribLocation. Quella funzione è cattiva juju dai primi giorni di GLSL prima che i responsabili di GL iniziassero davvero a capire come dovrebbe funzionare un buon linguaggio di ombreggiatura. Non è deprecato in quanto ha un uso occasionale, ma in generale preferisce glBindAttibLocation o l'estensione di posizione degli attributi espliciti (core in GL 3.3+).
Affrontare le differenze nei linguaggi shader è di gran lunga la parte più difficile del software di porting tra GL e D3D. I problemi dell'API che stai riscontrando riguardo alla definizione del layout dei vertici possono anche essere considerati un problema di linguaggio shader, poiché le versioni GLSL prima delle 3.30 non supportano la posizione degli attributi espliciti (simile nello spirito alla semantica degli attributi in HLSL) e le versioni GLSL prima 4.10 iirc non supporta attacchi espliciti uniformi.
L'approccio "migliore" consiste nell'avere una libreria di lingue di ombreggiatura di alto livello e un formato dati che incapsuli i pacchetti shader. NON semplicemente inserire un sacco di GLSL / HLSL non elaborate in una classe Shader sottile e aspettarti di essere in grado di inventare qualsiasi tipo di API sana.
Invece, metti i tuoi shader in un file. Racchiudili in un po 'di metadati. È possibile utilizzare XML e scrivere pacchetti shader come:
<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>
Scrivere un parser minimo per questo è banale (basta usare TinyXML per esempio). Consenti alla tua libreria di shader di caricare quel pacchetto, seleziona il profilo appropriato per il tuo renderer di destinazione corrente e compila gli shader.
Si noti inoltre che se si preferisce, è possibile mantenere l'origine esterna alla definizione dello shader, ma avere comunque il file. Inserisci semplicemente i nomi dei file anziché i sorgenti negli elementi dei sorgenti. Questo può essere utile se hai intenzione di precompilare gli shader, per esempio.
La parte difficile ora, ovviamente, riguarda la GLSL e le sue carenze. Il problema è che è necessario associare le posizioni degli attributi a qualcosa di simile alla semantica HLSL. Questo può essere fatto definendo tali semantiche nell'API e quindi utilizzando glBindAttribLocation prima di collegare il profilo GLSL. Il framework del pacchetto shader può gestirlo in modo esplicito, senza che l'API grafica debba assolutamente esporre i dettagli.
Puoi farlo estendendo il formato XML sopra riportato con alcuni nuovi elementi nel profilo GLSL per specificare esplicitamente le posizioni degli attributi, ad es
<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>
Il codice del pacchetto shader dovrebbe leggere tutti gli elementi attrib nell'XML, prendere il nome e la semantica da essi, cercare l'indice degli attributi predefinito per ogni semantica e quindi chiamare automaticamente glBindAttribLocation quando si collega lo shader.
Il risultato finale è che la tua API ora può sembrare molto più bella di quanto il tuo vecchio codice GL probabilmente non abbia mai visto, e anche un po 'più pulita di D3D11 consentirebbe:
// 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));
Si noti inoltre che non è strettamente necessario il formato del pacchetto shader. Se vuoi mantenere le cose semplici, sei libero di avere solo un tipo di funzione loadShader (const char * name) che acquisisce automaticamente i file GLSL name.vs e name.fs in modalità GL e li compila e li collega. Tuttavia, vorrai assolutamente quei metadati di attributo. Nel caso semplice, puoi aumentare il tuo codice GLSL con speciali commenti facili da analizzare, come:
#version 150
/// ATTRIB(inPosition,POSITION)
in vec4 inPosition;
/// ATTRIB(inColor,COLOR0)
in vec4 inColor;
out vec4 vColor
void main() {
vColor = inColor;
gl_Position = inPosition;
}
Puoi ottenere fantasioso come ti senti a tuo agio nell'analisi dei commenti. Più di alcuni motori professionali si spingeranno fino a fare estensioni di lingua minori che analizzano e modificano, anche, come semplicemente aggiungendo dichiarazioni semantiche in stile HLSL. Se la tua conoscenza dell'analisi è solida, dovresti essere in grado di trovare in modo affidabile quelle dichiarazioni estese, estrarre le informazioni aggiuntive e quindi sostituire il testo con il codice compatibile GLSL.
Indipendentemente da come lo fai, la versione breve è quella di aumentare la tua GLSL con le informazioni semantiche dell'attributo mancante e far sì che l'astrazione del tuo caricatore di shader si occupi di chiamare glBindAttribLocation per sistemare le cose e renderle più simili alle versioni GLSL moderne ed efficienti e HLSL.