Оптимизация мешей в DX9 (даже и в OGL)
Автор: Sergey Miloykov
Меши нужно оптимизировать, для лучшего использования кеша вершин на карточках, особенно при тяжелых вертексных шейдерах.
Оптимизация меша это:
1. Более оптимальй порядок треугольников, чтобы по возможности каждый новый треугольник брал свои вертексы из кеша, а не читал из VRAM/AGP и не трансформировал заново.
2. Более оптимальнъй порядок вертексов, так чтобы доступ к ним был более локальный/последовательный, так как карточка читает их блоками/или кеширует VB-память кусками размером больше чем один вертекс — отсюда прочесть следующий вертекс из кеша/кешированного куска быстрее, чем из другого конца вертекс буфера.
Надо отметить, что обе оптимизации независимы, можно сделать только одну из них, но как правило обе дают наибольший выигрыш. Для этих целей не надо самому выдумывать алгоритмы, ибо эта задача есть NP-полная, т.е. нужен N! поребор всех возможностей, что непрактично. Как решение, есть немало эвристических алгоритмов, над которыми бились люди. Хочу просто сказать, что надо юзать готовые библиотеки... :)
У юзеров DX9 есть возможность использовать средства D3DX, где есть довольно хороший оптимайзер. Есть два способа — ID3DXMesh::OptimizeInplace() или же пара D3DXOptimizeFaces()/D3DXOptimizeVertices(). Для того чтобы юзать второй метод надо скачать последнию версию SDK, ибо в первом DX9 SDK ее нет. Ее преимущество в сравнении с ID3DXMesh::Optimize() в том, что не надо свой меш заталкивать в ID3DXMesh и потом обратно брать, а кроме того не надо создавать сам ID3DXMesh, для чего надо сперва создать сам IDirect3DDevice9, что иногда в тулах — неприятно.
Ну, начнем — у нас есть:
std::vector<CustomVertex> vb; std::vector<unsigned short> ib;
Их нужно соптимизировать. CustomVertex может быть хоть чем, его структурой и содержанием мы не интересуемся. Сначала оптимизируем порядок треугольников, потом вертексов и сделаем ремаппинг.
IB в нашем случае — лист из треугольников, а не стрип, т.е. D3DPT_TRIANGLELIST, и состоит из 16-битных индексов.
void OptimizeFaces(unsigned short * pIB, const int numFaces, const int numVerts ) { DWORD * pdwRemap = new DWORD[numFaces]; D3DXOptimizeFaces( pIB, numFaces, numVerts, FALSE, pdwRemap ); unsigned short * pCopyIB = new unsigned short[numFaces*3]; memcpy( pCopyIB, pIB, numFaces*6 ); for( int i = 0; i < numFaces; ++i ) { int newFace = ( int)pdwRemap[i]; for( int j = 0; j < 3; ++j ) { pIB[newFace*3+j] = pCopyIB[i*3+j]; } } delete[] pCopyIB; delete[] pdwRemap; }
А сейчас соптимизируем вертексы, переиндексируем их, потом преиндексируем и треугольники. Я использовал template для более удобной интеграции кода, если он не нужен (оптимизируем только один вид вертексов), то тогда template просто можно убрать и поставить на место _V свой тип вертекса.
template< class _V > void OptimizeVertices(std::vector<_V> & vb, unsigned short * pIB, const int numFaces ) { const int numVerts = ( int)vb.size( ); DWORD * pdwRemap = new DWORD[numVerts]; D3DXOptimizeVertices( pIB, numFaces, numVerts, FALSE, pdwRemap )); // --- remap vertices std::vector<_V> copyVB( vb ); for( int i = 0; i < numVerts; ++i ) { vb[ pdwRemap[i] ] = copyVB[i]; } // --- remap triangles for( int i = 0; i < numFaces; ++i ) { for( int j = 0; j < 3; ++j ) { pIB[i*3+j] = ( unsigned short)pdwRemap[ pIB[i*3+j] ]; } } delete[] pdwRemap; }
Вот и все. В догонку:
1. ID3DXMesh::Optimize() иногда использовать можно, особенно если надо делать и welding (т.е. сливать одинаковые вертексы в один), тогда D3DXWeldVertices() спасет, а он работает только над ID3DXMesh, так что и оптимизацию можно делать там же.
2. Юзать ID3DXMesh можно, но надо перед этим создать IDirect3DDevice9, что делать в тулах только для этого — неприятно. И надо делать это внимательно, скажем в MAX-е надо хорошо делать reference дивайс, который multithreaded и сохраняет флаги процессора, т.е.: