Gestire lo stato e i componenti grafici?


11

Tendo spesso a fare molta ottimizzazione precoce quando mi occupo di grafica. Ci sono alcuni principi che cerco sempre di seguire:

  • Mantenere il numero di componenti D3D al minimo. (Stati di rendering, buffer, shader, ecc.)
  • Associare i componenti solo se assolutamente necessario. (Non associato già, ecc.)
  • Specializzare i componenti il ​​più possibile. (Impostare solo i BindFlags necessari, ecc.)

Questo mi ha portato a costruire wrapper molto elaborati per la gestione dei componenti creati e lo stato di piping corrente. Questo non solo consuma molto del mio prezioso tempo di sviluppo, ma aggiunge anche un altro grande livello di complessità.

E la cosa peggiore: non so nemmeno se valga la pena.

Alcune delle mie considerazioni sull'ottimizzazione potrebbero già essere implementate a un livello inferiore e le sto solo replicando, sprecando ulteriormente tempo sulla CPU. Altre considerazioni potrebbero essere completamente inutili, poiché l'effetto sulle prestazioni è trascurabile.

Quindi le mie domande sono:

  1. Quali delle linee guida sopra sono valide e in quale misura dovrei seguirle?
  2. In che modo la GPU gestisce i cambiamenti di stato?
  3. Cosa succede se cambio uno stato che non viene mai utilizzato? (Non è stata effettuata alcuna chiamata di disegno mentre è attiva.)
  4. Quali sono le penalità prestazionali effettive per l'associazione dei diversi componenti?
  5. Quali altre considerazioni sulle prestazioni dovrebbero essere fatte?

Per favore, non dirmi solo che non dovrei preoccuparmi delle prestazioni finché non raggiungo i limiti effettivi. Sebbene ciò sia ovviamente vero da un punto di vista pratico, sono principalmente interessato alla teoria. In qualche modo ho bisogno di combattere l'impulso di costruire il quadro grafico ottimale e non credo di poterlo fare con la solita "lezione di ottimizzazione prematura".

Gestione dei componenti

Attualmente sto scrivendo applicazioni DirectX 11 in C # usando SlimDX come wrapper gestito. È un wrapper di livello molto basso e la mia attuale astrazione è costruita su di essa.

Ci sono alcuni ovvi vantaggi quando si utilizza un'astrazione Direct3D. Configurare l'ambiente, caricare shader, impostare costanti e disegnare una mesh è molto più semplice e usa molto meno codice. Inoltre, poiché gestisce la creazione e lo smaltimento della maggior parte dei componenti, possono essere riutilizzati automaticamente ovunque e quasi completamente evito perdite di memoria.

  1. Come gestite normalmente tutti i componenti e le risorse grafiche?
  2. Puoi consigliare qualche wrapper gestito che fa qualcosa di simile al mio esempio di seguito?

Ecco un esempio della mia attuale implementazione. Sono abbastanza soddisfatto dell'interfaccia. Ha abbastanza flessibilità per le mie esigenze ed è molto semplice da usare e da capire:

// Init D3D environment
var window = new RenderForm();
var d3d = new Direct3D(window, GraphicsSettings.Default);
var graphics = new GraphicsManager(d3d.Device);

// Load assets
var mesh = GeometryPackage.FromFile(d3d, "teapot.gp");
var texture = Texture.FromFile(d3d, "bricks.dds");

// Render states
graphics.SetViewports(new Viewport(0, 0, 800, 600);
graphics.SetRasterizer(wireFrame: false, culling: CullMode.Back);
graphics.SetDepthState(depthEnabled: true, depthWriteEnabled: true);
graphics.SetBlendState(BlendMethod.Transparency);

// Input layout
graphics.SetLayout("effect.fx", "VS", "vs_4_0",
    new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
    new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0)
);

// Vertex shader
graphics.SetShader(Shader.Vertex, "effect.fx", "VS", "vs_4_0");
graphics.SetConstants(Shader.Vertex, 0, 4, stream => stream.Write(wvpMatrix));

// Pixel shader
graphics.SetShader(Shader.Pixel, "effect.fx", "PS", "ps_4_0");
graphics.SetTexture(Shader.Pixel, 0, texture);
graphics.SetSampler(Shader.Pixel, 0, Sampler.AnisotropicWrap);
graphics.SetConstants(Shader.Pixel, 0, 1, stream => stream.Write(new Color4(1, 0, 1, 0);

d3d.Run(() =>
{
    // Draw and present
    d3d.BackBuffer.Clear(new Color4(1, 0, 0.5f, 1));
    graphics.SetOutput(d3d.BackBuffer);
    graphics.Draw(mesh);
    d3d.Present();
}

8
Per questo tipo di domanda non darei la lezione di "ottimizzazione prematura", ti darei la lezione "Cambiamenti di profilo in modo che tu possa vedere di persona".
Tetrad

@Tetrad Mi vergogno quasi di ammettere che questo è un consiglio abbastanza decente. Dovrei assolutamente fare più profilazione.
Lucius

1
I numeri di profilazione sono la versione gamedev di "foto o non è successo" =)
Patrick Hughes

Risposte:


3

Mi piace l'approccio di astrazione delineato da Hodgman in questi thread su gamedev.net:

Descrive un sistema di rendering a tre livelli:

  1. API di rendering di basso livello che accetta "comandi", astrattando nient'altro che le differenze tra le diverse API grafiche, come Direct3D 9, Direct3D 11 e OpenGL. Ogni "comando" si associa a uno stato o richiamo diverso, come ad esempio il collegamento di un flusso o trama di vertici o il disegno delle primitive.
  2. API che accetta "elementi di rendering", che raggruppano tutti gli stati e una singola chiamata di richiamo necessaria per eseguire il rendering di un determinato oggetto e li ordina e li traduce in comandi che vengono inviati al primo livello. Uno stato di rendering contiene un richiamo e una pila di "gruppi di stati", che raggruppano logicamente i cambiamenti di stato. Ad esempio, avresti un gruppo di stati per il passaggio di rendering, un gruppo di stati per il materiale, un gruppo di stati per la geometria, un gruppo di stati per l'istanza e così via. Questo livello è responsabile dell'ordinamento di questi elementi di rendering per ridurre i cambiamenti di stato ridondanti e l'abbattimento di eventuali cambiamenti di stato che sono, di fatto, ridondanti.
  3. Sistemi di alto livello come un grafico di scena o un renderizzatore GUI che inviano elementi di rendering al secondo livello. La cosa importante da notare è che questi sistemi non conoscono né gli algoritmi di ordinamento dello stato né l'API di rendering specifica, rendendoli completamente indipendenti dalla piattaforma. Sono anche facili da usare una volta implementate le API di livello inferiore.

In conclusione, questo modello risolve entrambi i problemi contemporaneamente.

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.