Pengoptimalan Performa (Direct3D 9)

Setiap pengembang yang membuat aplikasi real-time yang menggunakan grafik 3D khawatir tentang pengoptimalan performa. Bagian ini menyediakan panduan untuk mendapatkan performa terbaik dari kode Anda.

Tips Performa Umum

  • Jelas hanya ketika Anda harus.
  • Minimalkan perubahan status dan kelompokkan perubahan status yang tersisa.
  • Gunakan tekstur yang lebih kecil, jika Anda dapat melakukannya.
  • Gambar objek di adegan Anda dari depan ke belakang.
  • Gunakan strip segitiga alih-alih daftar dan penggemar. Untuk performa cache vertex yang optimal, atur strip untuk menggunakan kembali simpul segitiga lebih cepat, bukan nanti.
  • Menurunkan efek khusus dengan baik yang membutuhkan bagian sumber daya sistem yang tidak proporsional.
  • Uji performa aplikasi Anda secara konstan.
  • Minimalkan sakelar buffer vertex.
  • Gunakan buffer vertex statis jika memungkinkan.
  • Gunakan satu buffer vertex statis besar per FVF untuk objek statis, bukan satu per objek.
  • Jika aplikasi Anda memerlukan akses acak ke buffer vertex dalam memori AGP, pilih ukuran format vertex yang merupakan kelipatan 32 byte. Jika tidak, pilih format terkecil yang sesuai.
  • Gambar menggunakan primitif terindeks. Ini dapat memungkinkan penembolokan vertex yang lebih efisien dalam perangkat keras.
  • Jika format buffer kedalaman berisi saluran stensil, selalu bersihkan saluran kedalaman dan stensil secara bersamaan.
  • Gabungkan instruksi shader dan output data jika memungkinkan. Contohnya:
    // 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 
    

Database dan Culling

Membangun database objek yang andal di dunia Anda adalah kunci untuk performa yang sangat baik di Direct3D. Ini lebih penting daripada perbaikan rasterisasi atau perangkat keras.

Anda harus mempertahankan jumlah poligon terendah yang mungkin dapat Anda kelola. Desain untuk jumlah poligon rendah dengan membangun model poligon rendah sejak awal. Tambahkan poligon jika Anda dapat melakukannya tanpa mengorbankan performa nanti dalam proses pengembangan. Ingat, poligon tercepat adalah poligon yang tidak Anda gambar.

Primitif Batching

Untuk mendapatkan performa penyajian terbaik selama eksekusi, cobalah bekerja dengan primitif dalam batch dan jaga jumlah perubahan status render serendah mungkin. Misalnya, jika Anda memiliki objek dengan dua tekstur, kelompokkan segitiga yang menggunakan tekstur pertama dan ikuti dengan status render yang diperlukan untuk mengubah tekstur. Kemudian kelompokkan semua segitiga yang menggunakan tekstur kedua. Dukungan perangkat keras paling sederhana untuk Direct3D dipanggil dengan batch status render dan batch primitif melalui lapisan abstraksi perangkat keras (HAL). Semakin efektif instruksi di-batch, semakin sedikit panggilan HAL yang dilakukan selama eksekusi.

Tips Pencahayaan

Karena lampu menambahkan biaya per puncak ke setiap bingkai yang dirender, Anda dapat meningkatkan performa secara signifikan dengan berhati-hati tentang cara Anda menggunakannya dalam aplikasi Anda. Sebagian besar tips berikut berasal dari maxim, "kode tercepat adalah kode yang tidak pernah dipanggil."

  • Gunakan sumber cahaya sesedi mungkin. Untuk meningkatkan tingkat pencahayaan keseluruhan, misalnya, gunakan cahaya sekitar alih-alih menambahkan sumber cahaya baru.
  • Lampu arah lebih efisien daripada lampu titik atau lampu sorot. Untuk lampu arah, arah ke cahaya diperbaiki dan tidak perlu dihitung berdasarkan per puncak.
  • Lampu sorot bisa lebih efisien daripada lampu titik, karena area di luar kerujuk cahaya dihitung dengan cepat. Apakah lampu sorot lebih efisien atau tidak tergantung pada seberapa banyak adegan Anda diterangi oleh sorotan.
  • Gunakan parameter rentang untuk membatasi lampu Anda hanya untuk bagian adegan yang perlu Anda iluminasi. Semua jenis cahaya keluar cukup awal ketika berada di luar jangkauan.
  • Sorotan spekular hampir dua kali lipat biaya cahaya. Gunakan hanya ketika Anda harus. Atur status D3DRS_SPECULARENABLE render ke 0, nilai default, jika memungkinkan. Saat menentukan bahan, Anda harus mengatur nilai daya spekular ke nol untuk menonaktifkan sorotan spekular untuk bahan tersebut; hanya mengatur warna spekular ke 0,0,0 tidak cukup.

Ukuran Tekstur

Performa pemetaan tekstur sangat tergantung pada kecepatan memori. Ada sejumlah cara untuk memaksimalkan performa cache tekstur aplikasi Anda.

  • Jaga tekstur tetap kecil. Semakin kecil teksturnya, semakin baik peluang mereka untuk dipertahankan di cache sekunder CPU utama.
  • Jangan mengubah tekstur secara per primitif. Cobalah untuk menjaga poligon dikelompokkan dalam urutan tekstur yang mereka gunakan.
  • Gunakan tekstur persegi jika memungkinkan. Tekstur yang dimensinya adalah 256x256 adalah yang tercepat. Jika aplikasi Anda menggunakan empat tekstur 128x128, misalnya, cobalah untuk memastikan bahwa mereka menggunakan palet yang sama dan menempatkan semuanya menjadi satu tekstur 256x256. Teknik ini juga mengurangi jumlah pertukaran tekstur. Tentu saja, Anda tidak boleh menggunakan tekstur 256x256 kecuali aplikasi Anda memerlukan banyak tekstur karena, seperti yang disebutkan, tekstur harus disimpan sesingkat mungkin.

Transformasi Matriks

Direct3D menggunakan dunia dan melihat matriks yang Anda atur untuk mengonfigurasi beberapa struktur data internal. Setiap kali Anda mengatur dunia baru atau matriks tampilan, sistem menghitung ulang struktur internal terkait. Sering mengatur matriks ini - misalnya, ribuan kali per bingkai - secara komputasi memakan waktu. Anda dapat meminimalkan jumlah perhitungan yang diperlukan dengan menggabungkan dunia Anda dan melihat matriks ke dalam matriks tampilan dunia yang Anda tetapkan sebagai matriks dunia, lalu mengatur matriks tampilan ke identitas. Simpan salinan cache dunia individual dan lihat matriks sehingga Anda dapat memodifikasi, menggabungkan, dan mengatur ulang matriks dunia sesuai kebutuhan. Untuk kejelasan dalam dokumentasi ini, sampel Direct3D jarang menggunakan pengoptimalan ini.

Menggunakan Tekstur Dinamis

Untuk mengetahui apakah driver mendukung tekstur dinamis, periksa bendera D3DCAPS2_DYNAMICTEXTURES struktur D3DCAPS9 .

Ingatlah hal-hal berikut saat bekerja dengan tekstur dinamis.

  • Mereka tidak dapat dikelola. Misalnya, kumpulan mereka tidak dapat D3DPOOL_MANAGED.
  • Tekstur dinamis dapat dikunci, bahkan jika dibuat dalam D3DPOOL_DEFAULT.
  • D3DLOCK_DISCARD adalah bendera kunci yang valid untuk tekstur dinamis.

Ada baiknya untuk membuat hanya satu tekstur dinamis per format dan mungkin per ukuran. Mipmap dinamis, kubus, dan volume tidak disarankan karena overhead tambahan dalam mengunci setiap tingkat. Untuk mipmaps, D3DLOCK_DISCARD hanya diperbolehkan di tingkat atas. Semua tingkat dibuang dengan mengunci hanya tingkat atas. Perilaku ini sama untuk volume dan kubus. Untuk kubus, tingkat atas dan wajah 0 terkunci.

Pseudocode berikut menunjukkan contoh penggunaan tekstur dinamis.

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();
}

Menggunakan Buffer Puncak dan Indeks Dinamis

Mengunci buffer vertex statis saat prosesor grafis menggunakan buffer dapat memiliki penalti performa yang signifikan. Panggilan kunci harus menunggu hingga prosesor grafis selesai membaca vertex atau data indeks dari buffer sebelum dapat kembali ke aplikasi panggilan, penundaan yang signifikan. Mengunci lalu merender dari buffer statis beberapa kali per bingkai juga mencegah prosesor grafis dari perintah penyajian buffering, karena harus menyelesaikan perintah sebelum mengembalikan penunjuk kunci. Tanpa perintah buffer, prosesor grafis tetap menganggur sampai setelah aplikasi selesai mengisi buffer vertex atau buffer indeks dan mengeluarkan perintah penyajian.

Idealnya vertex atau data indeks tidak akan pernah berubah, namun ini tidak selalu memungkinkan. Ada banyak situasi di mana aplikasi perlu mengubah vertex atau mengindeks data setiap bingkai, bahkan mungkin beberapa kali per bingkai. Untuk situasi ini, puncak atau buffer indeks harus dibuat dengan D3DUSAGE_DYNAMIC. Bendera penggunaan ini menyebabkan Direct3D mengoptimalkan operasi penguncian yang sering. D3DUSAGE_DYNAMIC hanya berguna ketika buffer sering dikunci; data yang tetap konstanta harus ditempatkan dalam puncak statis atau buffer indeks.

Untuk menerima peningkatan performa saat menggunakan buffer vertex dinamis, aplikasi harus memanggil IDirect3DVertexBuffer9::Lock atau IDirect3DIndexBuffer9::Lock dengan bendera yang sesuai. D3DLOCK_DISCARD menunjukkan bahwa aplikasi tidak perlu menyimpan vertex lama atau data indeks di buffer. Jika prosesor grafis masih menggunakan buffer saat kunci dipanggil dengan D3DLOCK_DISCARD, penunjuk ke wilayah memori baru dikembalikan alih-alih data buffer lama. Ini memungkinkan prosesor grafis untuk terus menggunakan data lama sementara aplikasi menempatkan data di buffer baru. Tidak diperlukan manajemen memori tambahan dalam aplikasi; buffer lama digunakan kembali atau dihancurkan secara otomatis ketika prosesor grafis selesai dengannya. Perhatikan bahwa mengunci buffer dengan D3DLOCK_DISCARD selalu membuang seluruh buffer, menentukan offset bukan nol atau bidang ukuran terbatas tidak mempertahankan informasi di area buffer yang tidak terkunci.

Ada kasus di mana jumlah data yang perlu disimpan aplikasi per kunci kecil, seperti menambahkan empat simpul untuk merender sprite. D3DLOCK_NOOVERWRITE menunjukkan bahwa aplikasi tidak akan menimpa data yang sudah digunakan dalam buffer dinamis. Panggilan kunci akan mengembalikan penunjuk ke data lama, memungkinkan aplikasi untuk menambahkan data baru di wilayah yang tidak digunakan dari vertex atau buffer indeks. Aplikasi tidak boleh memodifikasi simpul atau indeks yang digunakan dalam operasi gambar karena mungkin masih digunakan oleh prosesor grafis. Aplikasi kemudian harus menggunakan D3DLOCK_DISCARD setelah buffer dinamis penuh untuk menerima wilayah memori baru, membuang vertex lama atau data indeks setelah prosesor grafis selesai.

Mekanisme kueri asinkron berguna untuk menentukan apakah simpul masih digunakan oleh prosesor grafis. Terbitkan kueri jenis D3DQUERYTYPE_EVENT setelah panggilan DrawPrimitive terakhir yang menggunakan simpul. Simpul tidak lagi digunakan saat IDirect3DQuery9::GetData mengembalikan S_OK. Mengunci buffer dengan D3DLOCK_DISCARD atau tanpa bendera akan selalu menjamin simpul disinkronkan dengan benar dengan prosesor grafis, namun menggunakan kunci tanpa bendera akan dikenakan penalti performa yang dijelaskan sebelumnya. Panggilan API lain seperti IDirect3DDevice9::BeginScene, IDirect3DDevice9::EndScene, dan IDirect3DDevice9::P resent tidak menjamin prosesor grafis selesai menggunakan simpul.

Di bawah ini adalah cara untuk menggunakan buffer dinamis dan bendera kunci yang tepat.

    // 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;

Menggunakan Jala

Anda dapat mengoptimalkan jala dengan menggunakan segitiga terindeks Direct3D alih-alih strip segitiga terindeks. Perangkat keras akan menemukan bahwa 95 persen segitiga berturut-turut benar-benar membentuk strip dan menyesuaikannya. Banyak driver melakukan ini untuk perangkat keras yang lebih lama juga.

Objek jala D3DX dapat memiliki setiap segitiga, atau wajah, ditandai dengan DWORD, yang disebut atribut wajah tersebut. Semantik DWORD ditentukan pengguna. Mereka digunakan oleh D3DX untuk mengklasifikasikan jala ke dalam subset. Aplikasi menetapkan atribut per wajah menggunakan panggilan ID3DXMesh::LockAttributeBuffer . Metode ID3DXMesh::Optimize memiliki opsi untuk mengelompokkan simpul jala dan wajah pada atribut menggunakan opsi D3DXMESHOPT_ATTRSORT. Setelah selesai, objek jala menghitung tabel atribut yang dapat diperoleh oleh aplikasi dengan memanggil ID3DXBaseMesh::GetAttributeTable. Panggilan ini mengembalikan 0 jika jala tidak diurutkan berdasarkan atribut. Tidak ada cara bagi aplikasi untuk mengatur tabel atribut karena dihasilkan oleh metode ID3DXMesh::Optimize . Pengurutan atribut sensitif terhadap data, jadi jika aplikasi tahu bahwa jala diurutkan atributnya, aplikasi masih perlu memanggil ID3DXMesh::Optimize untuk menghasilkan tabel atribut.

Topik berikut menjelaskan berbagai atribut jala.

ID Atribut

Id atribut adalah nilai yang mengaitkan sekelompok wajah dengan grup atribut. Id ini menjelaskan subset wajah ID3DXBaseMesh::D rawSubset mana yang harus digambar. Id atribut ditentukan untuk wajah dalam buffer atribut. Nilai aktual dari id atribut dapat berupa apa pun yang cocok dalam 32 bit, tetapi biasanya menggunakan 0 ke n di mana n adalah jumlah atribut.

Buffer Atribut

Buffer atribut adalah array DWORD (satu per wajah) yang menentukan grup atribut mana yang dimiliki setiap wajah. Buffer ini diinisialisasi ke nol pada pembuatan jala, tetapi diisi oleh rutinitas beban atau harus diisi oleh pengguna jika lebih dari satu atribut dengan id 0 diinginkan. Buffer ini berisi informasi yang digunakan untuk mengurutkan jala berdasarkan atribut di ID3DXMesh::Optimize. Jika tidak ada tabel atribut, ID3DXBaseMesh::D rawSubset memindai buffer ini untuk memilih wajah atribut yang diberikan untuk digambar.

Tabel Atribut

Tabel atribut adalah struktur yang dimiliki dan dikelola oleh jala. Satu-satunya cara untuk satu-satunya yang akan dihasilkan adalah dengan memanggil ID3DXMesh::Optimize dengan pengurutan atribut atau pengoptimalan yang lebih kuat diaktifkan. Tabel atribut digunakan untuk memulai satu panggilan primitif gambar dengan cepat ke ID3DXBaseMesh::D rawSubset. Satu-satunya penggunaan lain adalah bahwa jala yang berkembang juga mempertahankan struktur ini, sehingga dimungkinkan untuk melihat wajah dan simpul apa yang aktif pada tingkat detail saat ini.

Performa Z-Buffer

Aplikasi dapat meningkatkan performa saat menggunakan z-buffering dan texturing dengan memastikan bahwa adegan dirender dari depan ke belakang. Primitif z-buffer bertekstur diprediksi terhadap z-buffer berdasarkan garis pemindaian. Jika garis pemindaian disembunyikan oleh poligon yang dirender sebelumnya, sistem menolaknya dengan cepat dan efisien. Buffering Z dapat meningkatkan performa, tetapi teknik ini paling berguna ketika adegan menarik piksel yang sama lebih dari sekali. Ini sulit dihitung dengan tepat, tetapi Anda sering dapat membuat perkiraan dekat. Jika piksel yang sama digambar kurang dari dua kali, Anda dapat mencapai performa terbaik dengan menonaktifkan z-buffering dan merender adegan dari belakang ke depan.

Tips Pemrograman