Udostępnij za pośrednictwem


Optymalizacje wydajności (Direct3D 9)

Każdy deweloper, który tworzy aplikacje w czasie rzeczywistym korzystające z grafiki 3D, jest zaniepokojony optymalizacją wydajności. Ta sekcja zawiera wskazówki dotyczące uzyskiwania najlepszej wydajności kodu.

Ogólne porady dotyczące wydajności

  • Wyczyść tylko wtedy, gdy musisz.
  • Zminimalizuj zmiany stanu i pogrupuj pozostałe zmiany stanu.
  • Jeśli możesz to zrobić, użyj mniejszych tekstur.
  • Rysuj obiekty w scenie od przodu do tyłu.
  • Używaj pasków trójkątnych zamiast list i wentylatorów. Aby uzyskać optymalną wydajność pamięci podręcznej wierzchołków, rozmieść paski, aby użyć wierzchołków trójkątów wcześniej, a nie później.
  • Bezpiecznie obniżają wydajność efektów specjalnych, które wymagają nieproporcjonalnego udziału zasobów systemowych.
  • Stale testuje wydajność aplikacji.
  • Minimalizuj przełączniki buforu wierzchołka.
  • W miarę możliwości używaj wierzchołków statycznych.
  • Użyj jednego dużego buforu wierzchołka statycznego na FVF dla obiektów statycznych, a nie jednego na obiekt.
  • Jeśli aplikacja potrzebuje losowego dostępu do buforu wierzchołka w pamięci protokołu AGP, wybierz rozmiar formatu wierzchołka, który jest wielokrotną 32 bajtami. W przeciwnym razie wybierz najmniejszy odpowiedni format.
  • Rysuj przy użyciu indeksowanych elementów pierwotnych. Może to umożliwić wydajniejsze buforowanie wierzchołków w ramach sprzętu.
  • Jeśli format buforu głębokości zawiera kanał wzornika, zawsze wyczyść kanały głębokości i wzornika jednocześnie.
  • Połącz instrukcję cieniowania i dane wyjściowe danych, jeśli to możliwe. Na przykład:
    // Rather than doing a multiply and add, and then output the data with 
    //   two instructions:
    mad r2, r1, v0, c0
    mov oD0, r2
    
    // Combine both in a single instruction, because this eliminates an  
    //   additional register copy.
    mad oD0, r1, v0, c0 
    

Bazy danych i wytrychy

Tworzenie niezawodnej bazy danych obiektów na świecie jest kluczem do doskonałej wydajności w usłudze Direct3D. Jest to ważniejsze niż ulepszenia rasteryzacji lub sprzętu.

Należy zachować najniższą liczbę wielokątów, którą można ewentualnie zarządzać. Projektowanie pod kątem małej liczby wielokątów przez tworzenie modeli niskokątnych od samego początku. Dodaj wielokąty, jeśli możesz to zrobić bez poświęcania wydajności w dalszej części procesu programowania. Pamiętaj, że najszybsze wielokąty to te, których nie rysujesz.

Dzielenie na partie elementów pierwotnych

Aby uzyskać najlepszą wydajność renderowania podczas wykonywania, spróbuj pracować z elementami pierwotnymi w partiach i zachować możliwie małą liczbę zmian stanu renderowania. Jeśli na przykład masz obiekt z dwiema teksturami, pogrupuj trójkąty, które używają pierwszej tekstury, i postępuj zgodnie z nimi z wymaganym stanem renderowania, aby zmienić teksturę. Następnie pogrupuj wszystkie trójkąty, które używają drugiej tekstury. Najprostsza obsługa sprzętu direct3D jest wywoływana z partiami stanów renderowania i partii elementów pierwotnych za pośrednictwem warstwy abstrakcji sprzętu (HAL). Tym bardziej efektywnie instrukcje są wykonywane wsadowe, tym mniej wywołań HAL jest wykonywanych podczas wykonywania.

Porady dotyczące oświetlenia

Ponieważ światła dodają koszt na wierzchołek do każdej renderowanej ramki, możesz znacznie poprawić wydajność, ostrożnie określając sposób ich używania w aplikacji. Większość poniższych wskazówek wynika z maksymy: "najszybszym kodem jest kod, który nigdy nie jest wywoływany".

  • Użyj jak najmniejszej liczby źródeł światła. Aby zwiększyć ogólny poziom oświetlenia, na przykład należy użyć światła otoczenia zamiast dodawać nowe źródło światła.
  • Światła kierunkowe są bardziej wydajne niż światła punktowe lub reflektory. W przypadku świateł kierunkowych kierunek światła jest stały i nie musi być obliczany na podstawie wierzchołka.
  • Reflektory mogą być wydajniejsze niż światła punktowe, ponieważ obszar poza stożek światła jest obliczany szybko. Niezależnie od tego, czy reflektory są wydajniejsze, czy nie, zależy od tego, ile sceny jest oświetlone przez centrum uwagi.
  • Użyj parametru zakresu, aby ograniczyć światła tylko do części sceny, które należy podświetlić. Wszystkie typy światła kończą się dość wcześnie, gdy są poza zasięgiem.
  • Specular podkreśla prawie dwukrotnie koszt światła. Używaj ich tylko wtedy, gdy musisz. Ustaw stan renderowania D3DRS_SPECULARENABLE na 0, wartość domyślną, zawsze, gdy jest to możliwe. Podczas definiowania materiałów należy ustawić wartość mocy specularnej na zero, aby wyłączyć wyróżnienia widmowe dla tego materiału; wystarczy ustawienie koloru widmowego na 0,0,0.

Rozmiar tekstury

Wydajność mapowania tekstur jest w dużym stopniu zależna od szybkości pamięci. Istnieje wiele sposobów zmaksymalizowania wydajności pamięci podręcznej tekstur aplikacji.

  • Zachowaj tekstury małe. Im mniejsze są tekstury, tym większa szansa na utrzymanie ich w pomocniczej pamięci podręcznej procesora CPU.
  • Nie zmieniaj tekstur na podstawie pierwotnej. Staraj się zachować wielokąty zgrupowane w kolejności używanych tekstur.
  • Używaj tekstur kwadratowych, jeśli jest to możliwe. Tekstury, których wymiary są 256x256 są najszybsze. Jeśli aplikacja używa na przykład czterech tekstur 128x128, spróbuj upewnić się, że używa tej samej palety i umieścić je wszystkie w jednej teksturze 256x256. Ta technika zmniejsza również ilość zamiany tekstury. Oczywiście nie należy używać tekstur 256x256, chyba że aplikacja wymaga tak dużej ilości tekstu, ponieważ, jak wspomniano, tekstury powinny być przechowywane tak małe, jak to możliwe.

Przekształcenia macierzy

Funkcja Direct3D używa macierzy świata i wyświetlania ustawionych w celu skonfigurowania kilku wewnętrznych struktur danych. Za każdym razem, gdy ustawiasz nowy świat lub macierz widoków, system ponownie oblicza skojarzone struktury wewnętrzne. Ustawienie tych macierzy często — na przykład tysięcy razy na ramę — jest czasochłonne w obliczeniach. Możesz zminimalizować liczbę wymaganych obliczeń, łącząc świat i wyświetlając macierze w macierzy widoku świata ustawionej jako macierz świata, a następnie ustawiając macierz widoków na tożsamość. Zachowaj buforowane kopie poszczególnych środowisk i wyświetl macierze, aby można było modyfikować, łączyć i resetować macierz świata zgodnie z potrzebami. Aby uzyskać jasność w tej dokumentacji, przykłady direct3D rzadko stosują tę optymalizację.

Używanie tekstur dynamicznych

Aby dowiedzieć się, czy sterownik obsługuje tekstury dynamiczne, sprawdź flagę D3DCAPS2_DYNAMICTEXTURES struktury D3DCAPS9.

Podczas pracy z teksturami dynamicznymi należy pamiętać o następujących kwestiach.

  • Nie można zarządzać nimi. Na przykład ich pula nie może być D3DPOOL_MANAGED.
  • Dynamiczne tekstury można zablokować, nawet jeśli są tworzone w D3DPOOL_DEFAULT.
  • D3DLOCK_DISCARD jest prawidłową flagą blokady dla tekstur dynamicznych.

Dobrym pomysłem jest utworzenie tylko jednej tekstury dynamicznej na format i ewentualnie rozmiar. Dynamiczne mipmapy, moduły i woluminy nie są zalecane ze względu na dodatkowe obciążenie podczas blokowania każdego poziomu. W przypadku map mipmap D3DLOCK_DISCARD jest dozwolona tylko na najwyższym poziomie. Wszystkie poziomy są odrzucane przez zablokowanie tylko najwyższego poziomu. To zachowanie jest takie samo w przypadku woluminów i modułów. W przypadku modułów górny poziom i twarz 0 są zablokowane.

Poniższy pseudokod przedstawia przykład użycia tekstury dynamicznej.

DrawProceduralTexture(pTex)
{
    // pTex should not be very small because overhead of 
    //   calling driver every D3DLOCK_DISCARD will not 
    //   justify the performance gain. Experimentation is encouraged.
    pTex->Lock(D3DLOCK_DISCARD);
    <Overwrite *entire* texture>
    pTex->Unlock();
    pDev->SetTexture();
    pDev->DrawPrimitive();
}

Używanie dynamicznych wierzchołków i indeksu

Zablokowanie statycznego buforu wierzchołka, gdy procesor graficzny korzysta z buforu, może mieć znaczną karę za wydajność. Wywołanie blokady musi czekać, aż procesor graficzny zakończy odczytywanie danych wierzchołka lub indeksu z buforu, zanim powróci do aplikacji wywołującej, co jest dużym opóźnieniem. Blokowanie i renderowanie z buforu statycznego kilka razy na ramkę uniemożliwia również procesorowi graficznemu buforowanie poleceń renderowania, ponieważ musi zakończyć polecenia przed zwróceniem wskaźnika blokady. Bez buforowanych poleceń procesor graficzny pozostaje bezczynny do momentu zakończenia wypełniania buforu wierzchołka lub buforu indeksu i wystawia polecenie renderowania.

Najlepiej, aby dane wierzchołka lub indeksu nigdy się nie zmieniły, jednak nie zawsze jest to możliwe. Istnieje wiele sytuacji, w których aplikacja musi zmienić wierzchołek lub indeksować dane każdej ramki, być może nawet wiele razy na ramkę. W takich sytuacjach wierzchołek lub bufor indeksu należy utworzyć za pomocą D3DUSAGE_DYNAMIC. Ta flaga użycia powoduje, że funkcja Direct3D optymalizuje częste operacje blokowania. D3DUSAGE_DYNAMIC jest przydatna tylko wtedy, gdy bufor jest często zablokowany; dane, które pozostają stałe, powinny być umieszczane w statycznym wierzchołku lub buforze indeksu.

Aby uzyskać poprawę wydajności podczas korzystania z dynamicznych wierzchołków, aplikacja musi wywołać IDirect3DVertexBuffer9::Lock lub IDirect3DIndexBuffer9::Lock z odpowiednimi flagami. D3DLOCK_DISCARD wskazuje, że aplikacja nie musi przechowywać starych wierzchołków ani danych indeksu w buforze. Jeśli procesor graficzny nadal używa buforu, gdy blokada jest wywoływana z D3DLOCK_DISCARD, wskaźnik do nowego regionu pamięci jest zwracany zamiast starych danych buforu. Dzięki temu procesor graficzny może nadal korzystać ze starych danych, gdy aplikacja umieszcza dane w nowym buforze. W aplikacji nie jest wymagane żadne dodatkowe zarządzanie pamięcią; stary bufor jest ponownie używany lub niszczony automatycznie po zakończeniu pracy procesora graficznego. Należy pamiętać, że blokowanie buforu z D3DLOCK_DISCARD zawsze odrzuca cały bufor, określając niezerowe przesunięcie lub pole o ograniczonym rozmiarze nie zachowuje informacji w odblokowanych obszarach buforu.

Istnieją przypadki, w których ilość danych, które aplikacja musi przechowywać na blokadę, jest mała, na przykład dodanie czterech wierzchołków w celu renderowania sprite. D3DLOCK_NOOVERWRITE wskazuje, że aplikacja nie zastąpi danych już używanych w buforze dynamicznym. Wywołanie blokady zwróci wskaźnik do starych danych, co umożliwi aplikacji dodanie nowych danych w nieużywanych regionach wierzchołka lub buforu indeksu. Aplikacja nie powinna modyfikować wierzchołków ani indeksów używanych w operacji rysowania, ponieważ nadal może być używana przez procesor graficzny. Następnie aplikacja powinna użyć D3DLOCK_DISCARD po zapełnieniu buforu dynamicznego, aby otrzymać nowy region pamięci, odrzucając stare wierzchołki lub dane indeksu po zakończeniu procesora graficznego.

Mechanizm zapytań asynchronicznych jest przydatny do określenia, czy wierzchołki są nadal używane przez procesor graficzny. Wydaj zapytanie typu D3DQUERYTYPE_EVENT po ostatnim wywołaniu DrawPrimitive, które używa wierzchołków. Wierzchołki nie są już używane, gdy IDirect3DQuery9::GetData zwraca S_OK. Zablokowanie buforu za pomocą D3DLOCK_DISCARD lub brak flag zawsze gwarantuje, że wierzchołki są prawidłowo synchronizowane z procesorem graficznym, jednak użycie blokady bez flag spowoduje naliczenie opisanej wcześniej kary za wydajność. Inne wywołania interfejsu API, takie jak IDirect3DDevice9::BeginScene, IDirect3DDevice9::EndScenei IDirect3DDevice9::P resent nie gwarantują zakończenia procesora graficznego przy użyciu wierzchołków.

Poniżej przedstawiono sposoby używania dynamicznych i odpowiednich flag blokady.

    // USAGE STYLE 1
    // Discard the entire vertex buffer and refill with thousands of vertices.
    // Might contain multiple objects and/or require multiple DrawPrimitive 
    //   calls separated by state changes, etc.
 
    // Determine the size of data to be moved into the vertex buffer.
    UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
 
    // Discard and refill the used portion of the vertex buffer.
    CONST DWORD dwLockFlags = D3DLOCK_DISCARD;
    
    // Lock the vertex buffer.
    BYTE* pBytes;
    if( FAILED( m_pVertexBuffer->Lock( 0, 0, &pBytes, dwLockFlags ) ) )
        return false;
    
    // Copy the vertices into the vertex buffer.
    memcpy( pBytes, pVertices, nSizeOfData );
    m_pVertexBuffer->Unlock();
 
    // Render the primitives.
    m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, nNumberOfVertices/3)
    // USAGE STYLE 2
    // Reusing one vertex buffer for multiple objects
 
    // Determine the size of data to be moved into the vertex buffer.
    UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
 
    // No overwrite will be used if the vertices can fit into 
    //   the space remaining in the vertex buffer.
    DWORD dwLockFlags = D3DLOCK_NOOVERWRITE;
    
    // Check to see if the entire vertex buffer has been used up yet.
    if( m_nNextVertexData > m_nSizeOfVB - nSizeOfData )
    {
        // No space remains. Start over from the beginning 
        //   of the vertex buffer.
        dwLockFlags = D3DLOCK_DISCARD;
        m_nNextVertexData = 0;
    }
    
    // Lock the vertex buffer.
    BYTE* pBytes;
    if( FAILED( m_pVertexBuffer->Lock( (UINT)m_nNextVertexData, nSizeOfData, 
               &pBytes, dwLockFlags ) ) )
        return false;
    
    // Copy the vertices into the vertex buffer.
    memcpy( pBytes, pVertices, nSizeOfData );
    m_pVertexBuffer->Unlock();
 
    // Render the primitives.
    m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 
               m_nNextVertexData/m_nVertexStride, nNumberOfVertices/3)
 
    // Advance to the next position in the vertex buffer.
    m_nNextVertexData += nSizeOfData;

Korzystanie z siatki

Siatki można zoptymalizować przy użyciu trójkątów indeksowanych Direct3D zamiast indeksowanych pasków trójkątów. Sprzęt odkryje, że 95 procent kolejnych trójkątów faktycznie tworzy paski i odpowiednio dostosowuje się. Wiele sterowników robi to również w przypadku starszego sprzętu.

Obiekty siatki D3DX mogą mieć każdy trójkąt lub twarz, oznaczone DWORD, nazywane atrybutem tej twarzy. Semantyka DWORD jest definiowana przez użytkownika. Są one używane przez D3DX do klasyfikowania siatki w podzbiorach. Aplikacja ustawia atrybuty twarzy przy użyciu wywołania ID3DXMesh::LockAttributeBuffer. Metoda ID3DXMesh::Optimize umożliwia grupowanie wierzchołków siatki i twarzy na atrybutach przy użyciu opcji D3DXMESHOPT_ATTRSORT. Po wykonaniu tej czynności obiekt siatki oblicza tabelę atrybutów, którą można uzyskać przez aplikację, wywołując ID3DXBaseMesh::GetAttributeTable. To wywołanie zwraca wartość 0, jeśli siatka nie jest sortowana według atrybutów. Nie ma możliwości ustawienia tabeli atrybutów przez aplikację, ponieważ jest ona generowana przez metodę ID3DXMesh::Optimize. Sortowanie atrybutów jest poufne dla danych, więc jeśli aplikacja wie, że siatka jest posortowana, nadal musi wywołać ID3DXMesh::Optimize w celu wygenerowania tabeli atrybutów.

W poniższych tematach opisano różne atrybuty siatki.

Identyfikator atrybutu

Identyfikator atrybutu to wartość, która kojarzy grupę twarzy z grupą atrybutów. W tym identyfikatorze opisano, który podzbiór twarzy ID3DXBaseMesh::D rawSubset powinien być rysowany. Identyfikatory atrybutów są określane dla twarzy w buforze atrybutów. Rzeczywiste wartości identyfikatorów atrybutów mogą być wszystkim, co pasuje do 32 bitów, ale często używa się wartości 0 do n, gdzie n jest liczbą atrybutów.

Bufor atrybutów

Bufor atrybutu to tablica DWORD (jedna na twarz), która określa, która grupa atrybutów należy do każdej twarzy. Ten bufor jest inicjowany do zera podczas tworzenia siatki, ale jest wypełniony przez procedury ładowania lub musi być wypełniony przez użytkownika, jeśli wymagany jest więcej niż jeden atrybut o identyfikatorze 0. Ten bufor zawiera informacje używane do sortowania siatki na podstawie atrybutów w ID3DXMesh::Optimize. Jeśli tabela atrybutów nie istnieje, ID3DXBaseMesh::D rawSubset skanuje ten bufor, aby wybrać twarze danego atrybutu do rysowania.

Tabela atrybutów

Tabela atrybutów jest strukturą własnością i utrzymywaną przez siatkę. Jedynym sposobem generowania elementu jest wywołanie ID3DXMesh::Optymalizowanie z włączonym sortowaniem atrybutów lub silniejszą optymalizacją. Tabela atrybutów służy do szybkiego inicjowania pojedynczego wywołania pierwotnego rysowania w celu ID3DXBaseMesh::D rawSubset. Jedynym innym zastosowaniem jest to, że postęp siatki również utrzymuje tę strukturę, więc możliwe jest, aby zobaczyć, jakie twarze i wierzchołki są aktywne na bieżącym poziomie szczegółów.

Wydajność buforu Z

Aplikacje mogą zwiększyć wydajność podczas buforowania z i texturing, zapewniając, że sceny są renderowane od przodu do tyłu. Teksturowane z buforowane pierwotne elementy pierwotne są wstępnie testowane względem buforu z na podstawie linii skanowania. Jeśli linia skanowania jest ukryta przez wcześniej renderowany wielokąt, system szybko i wydajnie go odrzuca. Buforowanie Z może poprawić wydajność, ale technika jest najbardziej przydatna, gdy scena rysuje te same piksele więcej niż raz. Jest to trudne do obliczenia dokładnie, ale często można zamknąć przybliżenie. Jeśli te same piksele są rysowane mniej niż dwa razy, możesz osiągnąć najlepszą wydajność, wyłączając buforowanie z i renderując scenę z tyłu do przodu.

porady dotyczące programowania