Często mam do czynienia z przedwczesną optymalizacją grafiki. Jest kilka zasad, których zawsze staram się przestrzegać:
- Ogranicz liczbę komponentów D3D do minimum. (Renderuj stany, bufory, shadery itp.)
- Elementy należy wiązać tylko w razie absolutnej konieczności. (Nie jest już związany itp.)
- Specjalizuj komponenty w jak największym stopniu. (Ustaw tylko niezbędne BindFlags itp.)
Doprowadziło mnie to do zbudowania bardzo skomplikowanych opakowań do zarządzania tworzonymi komponentami i bieżącym stanem potokowania. To nie tylko pochłania dużo mojego cennego czasu na rozwój, ale także dodaje kolejną dużą warstwę złożoności.
A co najgorsze: nawet nie wiem, czy to wszystko jest warte kłopotu.
Niektóre z moich rozważań dotyczących optymalizacji mogą być już zaimplementowane na niższym poziomie i po prostu je replikuję, dodatkowo marnując czas na procesor. Inne względy mogą być całkowicie niepotrzebne, ponieważ wpływ na wydajność jest znikomy.
Więc moje pytania to:
- Które z powyższych wytycznych są ważne i do jakiego stopnia mam ich przestrzegać?
- Jak GPU obsługuje zmiany stanu?
- Co się stanie, jeśli zmienię stan, który nigdy nie jest używany? (Nie jest wykonywane żadne połączenie losowania, gdy jest ono aktywne).
- Jakie są rzeczywiste kary wydajnościowe za wiązanie różnych elementów?
- Jakie inne czynniki należy wziąć pod uwagę?
Nie mów mi tylko, że nie powinienem dbać o wydajność, dopóki nie osiągnę rzeczywistych limitów. Chociaż z praktycznego punktu widzenia jest to oczywiście prawda, interesuje mnie głównie teoria. Muszę w jakiś sposób zwalczyć potrzebę zbudowania optymalnego frameworka graficznego i nie sądzę, że mogę to zrobić za pomocą zwykłego „przedwczesnego wykładu optymalizacyjnego”.
Zarządzanie komponentami
Obecnie piszę aplikacje DirectX 11 w języku C #, używając SlimDX jako zarządzanego opakowania. Jest to opakowanie na bardzo niskim poziomie, a moja obecna abstrakcja jest na nim zbudowana.
Istnieją pewne oczywiste zalety korzystania z abstrakcji Direct3D. Konfigurowanie środowiska, ładowanie programów cieniujących, ustawianie stałych i rysowanie siatki jest znacznie prostsze i zużywa znacznie mniej kodu. Ponadto, ponieważ zarządza tworzeniem i usuwaniem większości komponentów, mogą one być automatycznie ponownie wszędzie używane i prawie całkowicie unikam wycieków pamięci.
- Jak zwykle zarządzasz wszystkimi komponentami graficznymi i zasobami?
- Czy możesz polecić jakieś zarządzane opakowania, które robią coś podobnego do mojego przykładu poniżej?
Oto przykład mojej obecnej implementacji. Jestem całkiem zadowolony z interfejsu. Ma wystarczającą elastyczność dla moich potrzeb i jest bardzo prosty w użyciu i zrozumieniu:
// 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();
}