Zarządzasz stanem graficznym i komponentami?

11

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:

  1. Które z powyższych wytycznych są ważne i do jakiego stopnia mam ich przestrzegać?
  2. Jak GPU obsługuje zmiany stanu?
  3. 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).
  4. Jakie są rzeczywiste kary wydajnościowe za wiązanie różnych elementów?
  5. 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.

  1. Jak zwykle zarządzasz wszystkimi komponentami graficznymi i zasobami?
  2. 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();
}
Lucjusz
źródło
8
W przypadku tego rodzaju pytań nie wygłosiłbym wykładu „przedwczesnej optymalizacji”, dałbym wykład „zmiany profilu, abyś sam mógł zobaczyć”.
Tetrad
@Tetrad Prawie wstydzę się przyznać, że jest to całkiem przyzwoita rada. Zdecydowanie powinienem zrobić więcej profilowania.
Lucjusz
1
Numery profilujące to na pewno gamedevska wersja „fotki albo to się nie wydarzyło” =)
Patrick Hughes

Odpowiedzi:

3

Podoba mi się podejście do abstrakcji przedstawione przez Hodgmana w tych wątkach na gamedev.net:

Opisuje trójwarstwowy system renderowania:

  1. Niskopoziomowy interfejs API renderowania, który akceptuje „polecenia”, wyodrębniając nie więcej niż różnice między różnymi graficznymi interfejsami API, takimi jak Direct3D 9, Direct3D 11 i OpenGL. Każde „polecenie” odwzorowuje na inny stan lub wywołanie losowania, takie jak wiązanie strumienia wierzchołków lub tekstury lub rysowanie prymitywów.
  2. Interfejs API, który akceptuje „elementy renderowania”, które grupują wszystkie stany i jedno wywołanie losowania potrzebne do renderowania określonego obiektu oraz sortują i tłumaczą je na polecenia wysyłane na pierwszy poziom. Stan renderowania zawiera wywołanie losowania i stos „grup stanów”, które logicznie grupują zmiany stanu. Na przykład masz grupę stanów dla przejścia renderowania, grupę stanów dla materiału, grupę stanów dla geometrii, grupę stanów dla instancji i tak dalej. Ten poziom odpowiada za sortowanie tych elementów renderowania w celu ograniczenia zmian stanu nadmiarowego i wycofania wszelkich zmian stanu, które w rzeczywistości są zbędne.
  3. Systemy wysokiego poziomu, takie jak wykres sceny lub renderer GUI, które wysyłają elementy renderowania na drugi poziom. Należy zauważyć, że systemy te nie wiedzą ani o algorytmach sortowania stanów, ani o specyficznym API renderującym, co czyni je całkowicie niezależnymi od platformy. Są również łatwe w użyciu po wdrożeniu interfejsów API niższego poziomu.

Podsumowując, ten model rozwiązuje oba problemy naraz.

jmegaffin
źródło