DirectX11, bagaimana cara saya mengelola dan memperbarui beberapa buffer konstan shader?

13

Baiklah, saya mengalami kesulitan memahami bagaimana buffer konstan terikat ke tahap pipeline dan diperbarui. Saya mengerti bahwa DirectX11 dapat memiliki hingga 15 buffer konstan shader per stage dan setiap buffer dapat menampung hingga 4.096 konstanta. Namun, saya tidak mengerti apakah ID3D11Buffer COM yang digunakan untuk berinteraksi dengan buffer konstan hanya mekanisme (atau pegangan) yang digunakan untuk mengisi slot buffer ini atau jika objek benar-benar referensi contoh tertentu dari data buffer yang didorong maju dan mundur antara GPU dan CPU.

Saya pikir kebingungan saya pada topik adalah penyebab masalah saya menggunakan dua buffer konstan yang berbeda.

Berikut ini beberapa contoh kode shader.

cbuffer PerFrame : register(b0) {
    float4x4 view;
};

cbuffer PerObject : register(b1) {
    float4x4 scale;
    float4x4 rotation;
    float4x4 translation;
};

Cara kode saya diatur, kamera akan menangani memperbarui data per frame yang relevan dan GameObjects akan memperbarui sendiri per objek data. Kedua kelas memiliki ID3D11Buffer mereka sendiri yang digunakan untuk melakukan ini (Menggunakan arsitektur hub, sehingga satu kelas GameObject akan menangani rendering semua GameObjects yang dipasang di dunia).

Masalahnya adalah saya hanya bisa mendapatkan satu yang diperbarui pada satu waktu, tergantung pada slot dan saya menganggap urutan pembaruan satu buffer terisi sementara yang lain mendapat nol keluar.

Ini pada dasarnya adalah kode saya. Kedua kelas menggunakan logika pembaruan yang identik.

static PerObjectShaderBuffer _updatedBuffer; // PerFrameShaderBuffer if Camera class
_updatedBuffer.scale       = _rScale;
_updatedBuffer.rotation    = _rRotation;
_updatedBuffer.translation = _rTranslation;
pDeviceContext->UpdateSubresource(pShaderBuffer, 0 , 0, &_updatedBuffer, 0, 0);

pDeviceContext->VSSetShader(pVShader->GetShaderPtr(), 0, 0);
pDeviceContext->PSSetShader(pPShader->GetShaderPtr(), 0, 0);
pDeviceContext->VSSetConstantBuffers(1, 1, &pShaderBuffer);
pDeviceContext->IASetVertexBuffers(0, 1, &pVertexBuffer, &vStride, &_offset );
pDeviceContext->IASetPrimitiveTopology(topologyType);
pDeviceContext->Draw(bufSize, 0);

Pertanyaan utama saya adalah -

  • Apakah saya perlu mengatur atau mengikat ShaderBuffer untuk memperbaruinya dengan panggilan UpdateSubresource? (Artinya memanipulasi hanya ketika itu dalam pipa) Atau apakah itu gumpalan data yang akan dikirim dengan panggilan VSSetConstantBuffer? (Artinya urutan pengikatan dan pemutakhiran data tidak masalah, saya dapat memperbaruinya dalam pipa atau entah bagaimana di cpu)
  • Saat mengatur atau mengikat buffer, apakah saya perlu referensi slot 0 untuk memperbarui buffer PerFrame dan slot 1 untuk memperbarui buffer PerObject? Bisakah beberapa jenis kebingungan dengan panggilan ini dalam kode saya menyebabkan semua buffer ditimpa?
  • Bagaimana D3D11 tahu buffer mana yang ingin saya perbarui atau petakan? Apakah itu tahu dari ID3D11Buffer COM yang digunakan?

Edit -

Mengubah tag register buffer konstan pada contoh di atas. Menggunakan (cb #) alih-alih (b #) memengaruhi buffer untuk memperbarui dengan benar karena beberapa alasan. Tidak yakin di mana saya mengambil sintaks asli atau apakah itu valid sama sekali, tetapi tampaknya itu menjadi masalah utama saya.

KlashnikovKid
sumber

Jawaban:

18

Referensi ID3D11Buffer sepotong memori aktual yang menyimpan data Anda, apakah itu buffer vertex, buffer konstan, atau apa pun.

Buffer konstan bekerja dengan cara yang sama seperti buffer verteks dan jenis buffer lainnya. Yaitu, data di dalamnya tidak diakses oleh GPU sampai benar-benar merender frame, jadi buffer harus tetap valid sampai GPU selesai dengannya. Anda harus menggandakan setiap buffer konstan, sehingga Anda memiliki satu salinan untuk memperbarui untuk frame berikutnya, dan satu salinan untuk GPU untuk dibaca saat membuat frame saat ini. Ini mirip dengan bagaimana Anda akan melakukan buffer vertex dinamis untuk sistem partikel atau sejenisnya.

The register(cb0), register(cb1)pengaturan dalam berkorespondensi HLSL dengan slot di VSSetConstantBuffers. Ketika Anda memperbarui konstanta per-bingkai yang akan Anda lakukan VSSetConstantBuffers(0, 1, &pBuffer)untuk mengatur CB0 dan ketika Anda memperbarui konstanta per-objek yang akan Anda lakukan VSSetConstantBuffers(1, 1, &pBuffer)untuk mengatur CB1. Setiap panggilan hanya memperbarui buffer yang disebut oleh parameter mulai / hitung, dan tidak menyentuh yang lain.

Anda tidak perlu mengikat buffer untuk memperbaruinya dengan UpdateSubresource. Bahkan, itu tidak boleh terikat ketika Anda memperbaruinya, atau ini dapat memaksa pengemudi untuk membuat salinan memori tambahan secara internal (lihat halaman MSDN untuk UpdateSubresource, terutama komentar pada pertengkaran tentang halaman ke bawah).

Saya tidak yakin apa yang Anda maksud dengan "Bagaimana D3D11 tahu buffer mana yang ingin saya perbarui atau petakan?" Itu memperbarui atau memetakan yang penunjuknya Anda lewati.

Nathan Reed
sumber
3

Tampaknya ada banyak kebingungan seputar topik perlunya untuk mengikat kembali buffer konstan setelah memperbaruinya. Ketika saya belajar tentang hal ini sendiri, saya telah melihat banyak topik dan diskusi dengan pendapat yang berbeda tentang ini. Yaitu jawaban terbaik di sini, merekomendasikan menelepon XXSetConstantBufferssetelah Anda memperbarui melalui UpdateSubresourceatau Map/Unmap.

Juga, beberapa sampel dan dokumentasi D3D MSDN tampaknya menggunakan pola ini, mengikat (memanggil XXSetConstantBuffers) pada per frame atau bahkan per objek yang ditarik, meskipun mereka hanya memperbarui buffer yang ada, dan tidak mengubah slot tertentu dengan buffer yang sama sekali berbeda .

Saya pikir kesalahpahaman terburuk adalah bahwa XXSetConstantBufferssebenarnya "mengirim data yang sebelumnya Anda perbarui ke GPU atau memberitahukannya tentang pembaruan, sehingga dibutuhkan nilai-nilai baru - yang tampaknya benar-benar salah.

Memang, ketika menggunakan UpdateSubresourceatau Map/Unmap, dokumentasi menyatakan bahwa secara internal beberapa salinan dapat dibuat oleh GPU jika masih membutuhkan data lama, tetapi ini bukan masalah bagi pengguna API ketika datang untuk memperbarui buffer yang sudah terikat. Oleh karena itu kebutuhan untuk tidak terikat secara eksplisit tampaknya berlebihan.

Selama percobaan saya, saya sampai pada kesimpulan bahwa tidak perlu mengikat kembali buffer melalui XXSetConstantBufferssetelah memperbarui mereka, kecuali mereka belum terikat! Selama Anda menggunakan buffer yang sama (dibagi di antara shader, tahap pipa yang merata) yang pernah terikat (pada fase start-up, misalnya), Anda tidak perlu mengikat kembali mereka - cukup perbarui saja.

Beberapa kode untuk menampilkan sifat eksperimen saya lebih baik:

// Memory double of the buffer (static)
ConstBuffer* ShaderBase::CBuffer = (ConstBuffer*)_aligned_malloc(sizeof(ConstBuffer), 16);
// Hardware resource pointer (static)
ID3D11Buffer* ShaderBase::m_HwBuffer = nullptr;

void ShaderBase::Buffer_init()
{
     // Prepare buffer description etc.
     // Create one global buffer shared across shaders
     result = device->CreateBuffer(&cBufferDesc, NULL, &m_HwBuffer);
     BindConstBuffer();
}

...

void ShaderBase::BindConstBuffer()
{
     // Bind buffer to both VS and PS stages since it's a big global one
     deviceContext->VSSetConstantBuffers(0, 1, &m_HwBuffer);
     deviceContext->PSSetConstantBuffers(0, 1, &m_HwBuffer);
}

...
bool ShaderBase::UpdateConstBuffers()
{
    ...
    D3D11_MAPPED_SUBRESOURCE mappedResource;

    // Lock the constant buffer so it can be written to.
    deviceContext->Map(m_HwBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);

    // Get a pointer to the data in the constant buffer.
    ConstBuffer* dataPtr = (ConstBuffer*)mappedResource.pData;
    memcpy(dataPtr, CBuffer, sizeof(ConstBuffer));

    // Unlock the constant buffer.
    deviceContext->Unmap(m_HwBuffer, 0);
    return true;
}

// May be called multiple times per frame (multiple render passes)
void DrawObjects()
{
    // Simplified version
    for each Mesh _m to be drawn
    {
        // Some changes are per frame - but since we have only one global buffer to which we 
        // write with write-discard we need to set all of the values again when we update per-object
        ShaderBase::CBuffer->view = view;
        ShaderBase::CBuffer->projection = projection;
        ShaderBase::CBuffer->cameraPosition = m_Camera->GetPosition();

        ... 

        ShaderBase::CBuffer->lightDirection = m_Light->GetDirection();

        ShaderBase::CBuffer->lightView = lightView;
        ShaderBase::CBuffer->lightProjection = lightProjection;
        ShaderBase::CBuffer->world = worldTransform;

        // Only update! No rebind!
        if (ShaderBase::UpdateConstBuffers() == false)
            return false;

        _m->LoadIABuffers(); // Set the vertex and index buffers for the mesh
        deviceContext->DrawIndexed(_m->indexCount, 0, 0);
    }
}

Berikut adalah beberapa topik dari internet (forum gamedev) yang tampaknya mengadopsi dan merekomendasikan pendekatan ini: http://www.gamedev.net/topic/649410-set-constant-buffers-every-frame/?view=findpost&p=5105032 dan http://www.gamedev.net/topic/647203-updating-constant-buffers/#entry5090000

Untuk menyimpulkan, sepertinya mengikat tidak diperlukan kecuali Anda mengubah buffer sepenuhnya tetapi selama Anda berbagi buffer dan tata letak mereka antara shader (praktik yang disarankan) mengikat harus dilakukan dalam kasus ini:

  • Saat start-up - penjilidan awal - setelah membuat buffer misalnya.
  • Jika Anda perlu / telah dirancang untuk menggunakan lebih dari satu buffer yang terikat pada slot tertentu dari satu atau lebih tahapan.
  • Setelah menghapus status deviceContext (ketika mengubah ukuran buffer / windows)
Rakun berbatu
sumber