Teljesítményoptimalizálás (Direct3D 9)
A 3D-s ábrákat használó valós idejű alkalmazásokat létrehozó fejlesztők mindegyike a teljesítményoptimalizálás miatt aggódik. Ez a szakasz útmutatást nyújt a legjobb teljesítmény eléréséhez a kódból.
- Általános teljesítménytippek
- adatbázisok és selejtezési
- Primitívek kötegelése
- világítási tippek
- textúraméret
- Mátrix-átalakítások
- Dinamikus textúrák használata
- Dinamikus csúcspontok és indexpufferek használata
- Meshes használata
- Z-puffer teljesítmény
Általános teljesítménytippek
- Csak akkor törölje a jelet, ha kell.
- Az állapotváltozások minimalizálása és a fennmaradó állapotváltozások csoportosítása.
- Használjon kisebb textúrákat, ha teheti.
- Objektumok rajzolása a jelenetben elölről hátra.
- Használjon háromszögcsíkokat listák és ventilátorok helyett. Az optimális csúcsgyorsítótár-teljesítmény érdekében a sávokat úgy kell elrendezni, hogy a háromszög csúcsait előbb, mint később használhassa fel.
- Kecsesen csökkenti a különleges hatásokat, amelyek aránytalanul nagy mennyiségű rendszererőforrást igényelnek.
- Folyamatosan tesztelje az alkalmazás teljesítményét.
- Kis méretű csúcspufferkapcsolók.
- Ha lehetséges, használjon statikus csúcspontpuffereket.
- Használjon egy nagy statikus csúcspontpuffert FVF-enként statikus objektumokhoz, és ne objektumonként egyet.
- Ha az alkalmazásnak véletlenszerű hozzáférésre van szüksége az AGP-memóriában lévő csúcspufferhez, válasszon egy 32 bájtos többszörös csúcsformátumot. Ellenkező esetben válassza ki a legkisebb megfelelő formátumot.
- Rajzolás indexelt primitívekkel. Ez hatékonyabb csúcspont-gyorsítótárazást tehet lehetővé a hardveren belül.
- Ha a mélységi puffer formátuma rajzsablon csatornát tartalmaz, mindig törölje a mélységet és a rajzsablon csatornát egyszerre.
- Ha lehetséges, kombinálja a shader utasítást és az adatkimenetet. Például:
// 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
Adatbázisok és selejtezés
A Direct3D kiváló teljesítményéhez kulcsfontosságú, hogy megbízható adatbázist hoz létre a világ objektumaiból. Ez fontosabb, mint a raszterizálás vagy a hardver fejlesztése.
A lehető legkisebb sokszögszámot kell fenntartania. Alacsony sokszögszám tervezése alacsony sokszögű modellek készítésével a kezdetektől. Adjon hozzá sokszöget, ha ezt anélkül teheti meg, hogy a fejlesztési folyamat későbbi részében feláldozná a teljesítményt. Ne feledje, hogy a leggyorsabb sokszögek azok, amelyeket nem rajzol.
Primitívek kötegelése
A végrehajtás során a lehető legjobb renderelési teljesítmény eléréséhez próbáljon meg primitívekkel dolgozni kötegekben, és a renderelési állapot változásainak számát a lehető legalacsonyabban tartani. Ha például egy két textúrájú objektummal rendelkezik, csoportosítsa az első textúrát használó háromszögeket, és kövesse azokat a szükséges renderelési állapottal a textúra módosításához. Ezután csoportosítsa a második textúrát használó háromszögeket. A Direct3D legegyszerűbb hardvertámogatását renderelési állapotok kötegeivel és primitív kötegekkel hívjuk meg a hardver absztrakciós rétegén (HAL) keresztül. Minél hatékonyabban kötegelik az utasításokat, annál kevesebb HAL-hívás történik a végrehajtás során.
Világítási tippek
Mivel a fények csúcsonkénti költséget adnak az egyes renderelt keretekhez, jelentősen javíthatja a teljesítményt, ha körültekintően használja őket az alkalmazásban. A következő tippek többsége a maximából származik: "a leggyorsabb kód a soha el nem hívott kód".
- Használjon minél több fényforrást. A teljes megvilágítási szint növeléséhez például használja a környezeti fényt új fényforrás hozzáadása helyett.
- Az irányjelző lámpák hatékonyabbak, mint a pontfények vagy a reflektorfények. Az irányfények esetében a fény iránya rögzített, és nem kell csúcsonkénti alapon kiszámítani.
- A reflektorfények hatékonyabbak lehetnek, mint a pontfények, mivel a fénycsúcson kívüli terület gyorsan kiszámítható. Attól függ, hogy a reflektorfények hatékonyabbak-e vagy sem, attól függ, hogy a jelenet mekkora részét megvilágítja a reflektorfény.
- A tartományparaméter használatával a fényeket csak a megvilágítandó jelenet részeire korlátozhatja. Az összes fénytípus meglehetősen korán kilép, amikor kívül vannak a tartományon.
- A specular highlights majdnem megduplázza a fény költségét. Csak akkor használja őket, ha kell. Ha lehetséges, állítsa a D3DRS_SPECULARENABLE renderelési állapotát 0-ra, az alapértelmezett értékre. Az anyagok meghatározásakor a spekuláris teljesítményértéket nullára kell állítania, hogy kikapcsolja az adott anyag specularis kiemeléseit; a spekuláris szín 0,0,0 értékre állítása nem elég.
Anyagminta mérete
A textúra-leképezés teljesítménye nagymértékben függ a memória sebességétől. Számos módon maximalizálhatja az alkalmazás textúráinak gyorsítótár-teljesítményét.
- Tartsa kicsi a textúrákat. Minél kisebbek a textúrák, annál nagyobb az esély arra, hogy megmaradnak a fő CPU másodlagos gyorsítótárában.
- Ne módosítsa a textúrákat primitív alapon. Próbálja meg csoportosítani a sokszögeket az általuk használt textúrák szerint.
- Ha lehetséges, használjon négyzet alakú textúrákat. A leggyorsabbak azok a textúrák, amelyek mérete 256x256. Ha az alkalmazás például négy 128x128-at használ, próbálja meg biztosítani, hogy ugyanazt a palettát használják, és helyezze őket egy 256x256-os textúrába. Ez a technika csökkenti a textúra felcserélésének mennyiségét is. Természetesen nem szabad 256x256 textúrákat használni, kivéve, ha az alkalmazás megköveteli, hogy sok textúra, mert, mint említettük, textúrák kell tartani a lehető legkisebb.
Mátrix-átalakítások
A Direct3D a világot használja, és több belső adatstruktúra konfigurálásához beállított mátrixokat tekint meg. Minden alkalommal, amikor beállít egy új világot vagy egy mátrixot, a rendszer újraszámítja a kapcsolódó belső struktúrákat. Ezeknek az mátrixoknak a gyakran – például keretenként több ezer alkalommal – történő beállítása számításilag időigényes. Minimalizálhatja a szükséges számítások számát, ha összefűzi a világot, és a mátrixokat egy világnézeti mátrixba alakítja, amelyet világmátrixként állít be, majd a nézetmátrixot az identitásra állítja. Tartsa meg az egyes világ gyorsítótárazott másolatait, és tekintse meg a mátrixokat, hogy szükség szerint módosíthassa, összefűzhesse és visszaállíthassa a világmátrixot. A dokumentáció egyértelműsége érdekében a Direct3D-minták ritkán alkalmazzák ezt az optimalizálást.
Dinamikus textúrák használata
Ha szeretné megtudni, hogy az illesztőprogram támogatja-e a dinamikus textúrákat, ellenőrizze a D3DCAPS9 szerkezet D3DCAPS2_DYNAMICTEXTURES jelzőjét.
A dinamikus textúrák használatakor tartsa szem előtt az alábbi szempontokat.
- Nem kezelhetők. A készletük például nem D3DPOOL_MANAGED.
- A dinamikus textúrák zárolhatók, még akkor is, ha D3DPOOL_DEFAULT vannak létrehozva.
- D3DLOCK_DISCARD a dinamikus textúrák érvényes zárolási jelzője.
Érdemes formátumonként és méretenként csak egy dinamikus textúrát létrehozni. A dinamikus mipmapek, kockák és kötetek nem ajánlottak, mivel a minden szint zárolása további többletterhelést okoz. Mipmaps esetén D3DLOCK_DISCARD csak a legfelső szinten engedélyezett. A rendszer minden szintet elvet, ha csak a legfelső szintet zárolja. Ez a viselkedés a kötetek és kockák esetében is ugyanaz. Kockák esetén a felső szint és a 0 arc zárolva van.
Az alábbi pszeudokód egy dinamikus textúra használatát szemlélteti.
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();
}
Dinamikus csúcspontok és indexpufferek használata
A statikus csúcspontpuffer zárolása, miközben a grafikus processzor a puffert használja, jelentős teljesítménybeli büntetést vonhat maga után. A zárolási hívásnak meg kell várnia, amíg a grafikus processzor befejezi a csúcspontok vagy indexadatok olvasását a pufferből, mielőtt visszatérhet a hívó alkalmazáshoz, ami jelentős késést jelent. A képkockánként többszöri statikus puffer zárolása és renderelése szintén megakadályozza, hogy a grafikus processzor pufferelje a renderelési parancsokat, mivel a zárolási mutató visszaadása előtt be kell fejeznie a parancsokat. Pufferelt parancsok nélkül a grafikus processzor tétlen marad, amíg az alkalmazás be nem tölti a csúcspuffert vagy az indexpuffert, és kiad egy renderelési parancsot.
Ideális esetben a csúcs- vagy indexadatok soha nem változnának, de ez nem mindig lehetséges. Sok olyan helyzet van, amikor az alkalmazásnak minden keretben módosítania kell a csúcspont- vagy indexadatokat, akár keretenként akár többször is. Ilyen helyzetekben a csúcs- vagy indexpuffert D3DUSAGE_DYNAMIC kell létrehozni. Ez a használati jelző miatt a Direct3D a gyakori zárolási műveletekre optimalizál. D3DUSAGE_DYNAMIC csak akkor hasznos, ha a puffert gyakran zárolják; az állandó maradó adatokat statikus csúcspontba vagy indexpufferbe kell helyezni.
Ha dinamikus csúcspontpufferek használatakor teljesítménybeli javulást szeretne kapni, az alkalmazásnak meg kell hívnia IDirect3DVertexBuffer9::Lock vagy IDirect3DIndexBuffer9::Lock a megfelelő jelzőkkel. D3DLOCK_DISCARD azt jelzi, hogy az alkalmazásnak nem kell megőriznie a régi csúcs- vagy indexadatokat a pufferben. Ha a grafikus processzor továbbra is használja a puffert, amikor a zárolást D3DLOCK_DISCARD hívja meg, a rendszer a régi pufferadatok helyett egy új memóriaterületre mutató mutatót ad vissza. Ez lehetővé teszi, hogy a grafikus processzor továbbra is a régi adatokat használja, miközben az alkalmazás adatokat helyez el az új pufferben. Nincs szükség további memóriakezelésre az alkalmazásban; a régi puffert a rendszer automatikusan újra felhasználja vagy megsemmisíti, amikor a grafikus processzor befejeződött. Vegye figyelembe, hogy a puffer D3DLOCK_DISCARD való zárolása mindig elveti a teljes puffert, ha nem tér eltolást vagy korlátozott méretű mezőt ad meg, az nem őrzi meg a puffer feloldott területein található információkat.
Vannak olyan esetek, amikor az alkalmazás által a zárolásonként tárolandó adatok mennyisége kicsi, például négy csúcspontot ad hozzá a sprite megjelenítéséhez. D3DLOCK_NOOVERWRITE azt jelzi, hogy az alkalmazás nem írja felül a dinamikus pufferben már használt adatokat. A zárolási hívás egy mutatót ad vissza a régi adatokhoz, így az alkalmazás új adatokat vehet fel a csúcspont vagy indexpuffer nem használt régióiba. Az alkalmazás nem módosíthatja a rajzműveletben használt csúcspontokat vagy indexeket, mivel a grafikus processzor továbbra is használhatja őket. Az alkalmazásnak ezután D3DLOCK_DISCARD kell használnia, miután a dinamikus puffer megtelt egy új memóriaterület fogadásához, és a grafikus processzor befejezése után elveti a régi csúcs- vagy indexadatokat.
Az aszinkron lekérdezési mechanizmus hasznos annak megállapításához, hogy a csúcsok továbbra is használatban vannak-e a grafikus processzorban. Adjon ki egy D3DQUERYTYPE_EVENT típusú lekérdezést a csúcspontokat használó legutóbbi DrawPrimitive hívás után. A csúcspontok már nem használhatók, ha IDirect3DQuery9::GetData S_OK ad vissza. Ha a puffert D3DLOCK_DISCARD vagy jelző nélkül zárolja, az mindig garantálja, hogy a csúcsok megfelelően szinkronizálódnak a grafikus processzorral, de a jelölők nélküli zárolás a korábban ismertetett teljesítménybeli büntetést fogja érinteni. Más API-hívások, például IDirect3DDevice9::BeginScene, IDirect3DDevice9::EndSceneés IDirect3DDevice9::P resent nem garantálják, hogy a grafikus processzor csúcspontok használatával befejeződött.
Az alábbiakban bemutatjuk a dinamikus pufferek és a megfelelő zárolási jelzők használatát.
// 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;
Hálók használata
A hálókat indexelt háromszögek helyett Direct3D indexelt háromszögekkel optimalizálhatja. A hardver rájön, hogy az egymást követő háromszögek 95%-a valójában csíkokat alkot, és ennek megfelelően igazítja. Sok illesztőprogram ezt a régebbi hardverek esetében is megteszi.
A D3DX hálóobjektumok minden háromszöget vagy arcot egy DWORD címkével, az arc attribútumának neveznek. A DWORD szemantikája felhasználó által definiált. Ezeket a D3DX használja a háló alhalmazokba való besorolásához. Az alkalmazás arconkénti attribútumokat állít be az ID3DXMesh::LockAttributeBuffer hívással. Az ID3DXMesh::Optimize metódussal a D3DXMESHOPT_ATTRSORT beállítással csoportosíthatja a háló csúcspontjait és arcait attribútumokra. Ha ez megtörtént, a mesh objektum egy attribútumtáblát számít ki, amelyet az alkalmazás ID3DXBaseMesh::GetAttributeTablemeghívásával szerezhet be. Ez a hívás 0 értéket ad vissza, ha a háló nem attribútumok szerint van rendezve. Az alkalmazás nem állíthat be attribútumtáblát, mert az ID3DXMesh::Optimize metódus hozza létre. Az attribútumrendezés adatérzékeny, ezért ha az alkalmazás tudja, hogy egy háló van rendezve, akkor is meg kell hívnia ID3DXMesh::Optimize az attribútumtábla létrehozásához.
Az alábbi témakörök a háló különböző attribútumait ismertetik.
Attribútumazonosító
Az attribútumazonosító egy olyan érték, amely arccsoportot társít egy attribútumcsoporthoz. Ez az azonosító az arcok ID3DXBaseMesh::D rawSubset rajzolását ismerteti. Az attribútumpufferben lévő arcokhoz attribútumazonosítók vannak megadva. Az attribútumazonosítók tényleges értékei 32 bitesek lehetnek, de gyakori, hogy 0–n értéket használnak, ahol n az attribútumok száma.
Attribútumpuffer
Az attribútumpuffer a DWORD-k tömbje (arconként egy), amely meghatározza, hogy az egyes arcok melyik attribútumcsoportba tartoznak. Ez a puffer egy háló létrehozásakor nullára van inicializálva, de vagy a terhelési rutinok töltik ki, vagy a felhasználónak kell kitöltenie, ha több, 0 azonosítójú attribútumra van szükség. Ez a puffer azokat az információkat tartalmazza, amelyek a háló rendezésére szolgálnak ID3DXMesh::Optimizeattribútumai alapján. Ha nincs attribútumtábla, ID3DXBaseMesh::D rawSubset megvizsgálja ezt a puffert, hogy kijelölje a rajzolni kívánt attribútum arcait.
Attribútumtábla
Az attribútumtábla a háló tulajdonában és fenntartásában lévő struktúra. Az egyiket csak úgy lehet létrehozni, ha meghívja ID3DXMesh::Optimize attribútum rendezéssel vagy erősebb optimalizálással engedélyezve. Az attribútumtáblával gyorsan kezdeményezhet egyszerű rajzhívást ID3DXBaseMesh::D rawSubset. Az egyetlen másik használat az, hogy a fejlődésben lévő hálók is fenntartják ezt a struktúrát, így látható, hogy milyen arcok és csúcsok aktívak a jelenlegi részletességi szinten.
Z-puffer teljesítménye
Az alkalmazások a z-pufferelés és a szövegezés használatakor növelhetik a teljesítményt, ha biztosítják, hogy a jelenetek elölről hátulra legyenek renderelve. A strukturált z-pufferelt primitíveket a rendszer előre teszteli a z-pufferrel egy vizsgálati vonal alapján. Ha egy korábban renderelt sokszög elrejti a vizsgálati vonalat, a rendszer gyorsan és hatékonyan elutasítja azt. A Z-pufferelés javíthatja a teljesítményt, de a technika akkor hasznos, ha egy jelenet többször rajzolja meg ugyanazt a képpontot. Ezt nehéz pontosan kiszámítani, de gyakran lehet közelíteni. Ha ugyanazt a képpontot kevesebb mint kétszer rajzolja meg, a legjobb teljesítményt úgy érheti el, hogy kikapcsolja a z-pufferelést, és hátulról elölről rendereli a jelenetet.
Kapcsolódó témakörök