Qual è un livello di astrazione migliore per la gestione dei dati dei vertici D3D9 e OpenGL?


8

Il mio codice di rendering è sempre stato OpenGL. Ora devo supportare una piattaforma che non ha OpenGL, quindi devo aggiungere un livello di astrazione che avvolge OpenGL e Direct3D 9. Sosterrò Direct3D 11 in seguito.

TL; DR: le differenze tra OpenGL e Direct3D causano ridondanza per il programmatore e il layout dei dati risulta instabile.

Per ora, la mia API funziona un po 'così. Ecco come viene creato uno shader:

Shader *shader = Shader::Create(
    " ... GLSL vertex shader ... ", " ... GLSL pixel shader ... ",
    " ... HLSL vertex shader ... ", " ... HLSL pixel shader ... ");
ShaderAttrib a1 = shader->GetAttribLocation("Point", VertexUsage::Position, 0);
ShaderAttrib a2 = shader->GetAttribLocation("TexCoord", VertexUsage::TexCoord, 0);
ShaderAttrib a3 = shader->GetAttribLocation("Data", VertexUsage::TexCoord, 1);
ShaderUniform u1 = shader->GetUniformLocation("WorldMatrix");
ShaderUniform u2 = shader->GetUniformLocation("Zoom");

C'è già un problema qui: una volta compilato uno shader Direct3D, non c'è modo di interrogare un attributo di input con il suo nome; apparentemente solo la semantica rimane significativa. Ecco perché GetAttribLocationha questi argomenti extra, che vengono nascosti in ShaderAttrib.

Ora è così che creo una dichiarazione di vertici e due buffer di vertici:

VertexDeclaration *decl = VertexDeclaration::Create(
        VertexStream<vec3,vec2>(VertexUsage::Position, 0,
                                VertexUsage::TexCoord, 0),
        VertexStream<vec4>(VertexUsage::TexCoord, 1));

VertexBuffer *vb1 = new VertexBuffer(NUM * (sizeof(vec3) + sizeof(vec2));
VertexBuffer *vb2 = new VertexBuffer(NUM * sizeof(vec4));

Un altro problema: le informazioni VertexUsage::Position, 0sono totalmente inutili per il backend OpenGL / GLSL perché non si preoccupano della semantica.

Una volta che i buffer dei vertici sono stati riempiti o puntati ai dati, questo è il codice di rendering:

shader->Bind();
shader->SetUniform(u1, GetWorldMatrix());
shader->SetUniform(u2, blah);
decl->Bind();
decl->SetStream(vb1, a1, a2);
decl->SetStream(vb2, a3);
decl->DrawPrimitives(VertexPrimitive::Triangle, NUM / 3);
decl->Unbind();
shader->Unbind();

Vedi che declè un po 'più di una semplice dichiarazione di vertice simile a D3D, si occupa anche del rendering. Ha senso per niente? Quale sarebbe un design più pulito? O una buona fonte di ispirazione?


Quale versione di OpenGL stai prendendo di mira?
Nicol Bolas,

@NicolBolas al momento utilizzo OpenGL 2.1 e OpenGL ES 2.0 e ho intenzione di supportare OpenGL 3.3 o 4.0, ma non ho deciso se abbandonerò il supporto per le versioni precedenti. Il mio problema attuale è che uso anche un sottoinsieme del vecchio OpenGL su PS3, che è subottimale ma piuttosto conveniente ...
sam hocevar

Probabilmente ne sei già a conoscenza, ma controlla la fonte di Ogre per vedere come hanno fatto ogre3d.org
Aralox,

4
@Aralox: OGRE è un casino infestato da Singleton e non consiglierei mai a nessuno di seguire il loro progetto.
DeadMG

Risposte:


8

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.


Grazie per una risposta estremamente completa. Il suggerimento aggiuntivo sui commenti semantici è semplice ma ha molto senso!
sam hocevar,

Accetto finalmente la tua risposta, anche se altri si sono dimostrati molto utili. Ho trascorso molto tempo a meditare su come farlo correttamente e ho finito per scrivere un parser GLSL / HLSL completo che mi aiuta a emulare la posizione degli attributi espliciti quando non è supportato.
Sam Hocevar,

5

In primo luogo, suggerirei di utilizzare VertexBuffer<T>per migliorare la sicurezza dei tipi, ma in secondo luogo, penso che le differenze tra le due API siano troppo a questo livello. Personalmente incapsulerei completamente i renderer dietro un'interfaccia che non si occupa di cose come dichiarazioni di vertici o impostazione di attributi shader.


distaccato; il tuo livello di astrazione è attualmente a un livello troppo basso e deve essere più elevato per poter davvero far fronte alle differenze API.
Maximus Minimus,

2

Personalmente, stabilirei (e applicherei) una convenzione standardizzata per gli indici degli attributi. GL indice 0 è la posizione. L'indice GL 1 è il colore. L'indice 2 è un normale, con 3 e 4 per tangenti e binormali (se necessario). L'indice 5-7 sono coordinate della trama. Forse 8 e 9 sono per pesi ossei. 10 può essere un secondo colore se necessario. Se non sei in grado di utilizzare GL_ARB_explicit_attrib_locationo GL 3.3+, devi anche stabilire una convenzione standardizzata per la denominazione degli attributi .

In questo modo, D3D ha convenzioni e OpenGL ha convenzioni. Quindi l'utente non deve nemmeno chiedere quale sia l'indice di una "posizione"; sanno che è 0. E il tuo astrazione sa che 0 significa, nella terra di D3D, VertexUsage::Position.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.