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:
- Quali delle linee guida sopra sono valide e in quale misura dovrei seguirle?
- In che modo la GPU gestisce i cambiamenti di stato?
- Cosa succede se cambio uno stato che non viene mai utilizzato? (Non è stata effettuata alcuna chiamata di disegno mentre è attiva.)
- Quali sono le penalità prestazionali effettive per l'associazione dei diversi componenti?
- 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.
- Come gestite normalmente tutti i componenti e le risorse grafiche?
- 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();
}