DirectX11, birden fazla gölgelendirici sabit arabelleğini nasıl yönetir ve güncellerim?


13

Tamam, sabit tamponların bir boru hattı aşamasına nasıl bağlandığını ve güncellendiğini kavramakta zorlanıyorum. DirectX11'in aşama başına 15'e kadar gölgelendirici sabit arabelleğe sahip olabileceğini ve her bir arabellek 4096 sabit tutabildiğini anlıyorum. Ancak, sabit arabelleklerle etkileşimde kullanılan ID3D11Buffer COM'un yalnızca bu arabellek yuvalarını doldurmak için kullanılan bir mekanizma (veya tanıtıcı) olup olmadığını veya nesnenin gerçekten ileri geri itilen bir arabellek verisine başvurup başvurmadığını anlamıyorum. GPU ve CPU arasında.

Konuyla ilgili kafa karışıklığımın, iki farklı sabit tampon kullandığım bir sorunun nedeni olduğunu düşünüyorum.

İşte bazı örnek gölgelendirici kodu.

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

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

Kodumun düzenlenme şekli, kamera, kare başına ilgili verilerin güncellenmesini yönetecek ve GameObjects nesne başına kendi verilerini güncelleyecektir. Her iki sınıf da bunu yapmak için kullanılan kendi ID3D11Buffer'ına sahiptir (Bir hub mimarisi kullanarak, bir GameObject sınıfı, dünyadaki tüm örneklenmiş GameObjects'in oluşturulmasını işleyecektir).

Sorun, yuvaya bağlı olarak sadece bir kerede bir güncelleme alabilirsiniz ve güncelleme sırası bir tampon doldurulur diğeri sıfır alır varsayalım.

Bu aslında benim kodum. Her iki sınıf da aynı güncelleme mantığını kullanır.

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

Ana sorularım -

  • UpdateSubresource çağrısıyla güncellemek için ShaderBuffer'ı ayarlamam veya bağlamam gerekir mi? (Anlamı sadece boru hattındayken manipüle eder) Yoksa VSSetConstantBuffer çağrısı ile gönderilecek bir veri bloğu mu? (Veri bağlama ve güncelleme sırası önemli değil, boru hattında veya bir şekilde cpu'da güncelleyebilirim)
  • Arabelleği ayarlarken veya bağlarken, PerFrame arabelleğini güncellemek için slot 0'a ve PerObject arabelleğini güncellemek için slot 1'e başvurmam gerekir mi? Kodumdaki bu çağrı ile ilgili bir tür karışıklık tüm arabelleklerin üzerine yazılmasına neden olabilir mi?
  • D3D11, hangi arabelleği güncellemek veya eşlemek istediğimi nasıl biliyor? Kullanılan ID3D11Buffer COM biliyor mu?

Düzenle -

Yukarıdaki örnekte sabit arabellek yazmacı etiketleri değiştirildi. (B #) yerine (cb #) kullanılması, arabelleklerin bir nedenle doğru şekilde güncellenmesini etkiliyordu. Orijinal sözdizimini nereden aldığımdan veya hiç geçerli olmadığından emin değilim, ancak ana sorunum gibi görünüyor.

Yanıtlar:


18

ID3D11Buffer, ister köşe arabelleği, ister sabit tampon olsun, isterse de verilerinizi tutan gerçek bir bellek yığınına başvurur.

Sabit tamponlar, köşe tamponları ve diğer tamponlar gibi çalışır. Yani, içindeki verilere GPU gerçekte çerçeveyi oluşturana kadar erişmez, bu nedenle arabellek GPU onunla yapılana kadar geçerli kalmalıdır. Her sabit arabelleği iki kez arabelleğe almalısınız, böylece bir sonraki kare için güncellenecek bir kopya ve geçerli kareyi oluştururken GPU'nun okuyacağı bir kopya vardır. Bu, bir parçacık sistemi veya benzeri bir şey için dinamik köşe tamponlarını nasıl yapacağınıza benzemektedir.

register(cb0), register(cb1)VSSetConstantBuffers içinde yuvaları HLSL tekabül ettiği ayarları. Kare başına sabitleri VSSetConstantBuffers(0, 1, &pBuffer)güncellediğinizde CB0'ı ve nesne başına sabitleri güncellediğinizde CB1'i VSSetConstantBuffers(1, 1, &pBuffer)ayarlarsınız. Her çağrı yalnızca start / count parametreleri tarafından belirtilen arabellekleri günceller ve diğerlerine dokunmaz.

UpdateSubresource ile güncellemek için arabelleği bağlamanız gerekmez. Aslında, güncelleştirdiğinizde bağlı olmamalıdır veya bu, sürücüyü dahili olarak fazladan bellek kopyaları yapmaya zorlayabilir ( özellikle bir sayfanın aşağı çekilmesiyle ilgili açıklamalar için UpdateSubresource için MSDN sayfasına bakın ).

"D3D11, hangi arabelleği güncellemek veya haritalamak istediğimi nasıl biliyor?" İle ne demek istediğinden emin değilim. İşaretçisini ilettiğiniz kişiyi günceller veya eşler.


3

Sabit arabellekleri güncelledikten sonra yeniden bağlamaya ihtiyaç duyma konusunda çok fazla karışıklık var gibi görünüyor. Bunu kendim öğrendiğimde, bununla ilgili ters düşüncelere sahip birçok konu ve tartışma gördüm. Yani burada en iyi yanıt, veya ile XXSetConstantBuffersgüncelledikten sonra aramayı öneriyoruz .UpdateSubresourceMap/Unmap

Ayrıca, bazı D3D MSDN örnekleri ve belgeleri , yalnızca mevcut bir arabelleği güncellemelerine ve tamamen farklı bir arabelleğe sahip belirli bir yuvayı değiştirmemelerine rağmen, çerçeve başına veya hatta çizilen nesne temelinde bağlama (çağrı ) kullanarak bu kalıbı kullanıyor gibi görünüyor. .XXSetConstantBuffers

En kötü yanılgı XXSetConstantBuffersaslında "daha önce güncellediğiniz verileri GPU'ya gönderir veya güncellemeyi bildirir, böylece yeni değerleri alır - ki bu tamamen yanlış görünüyor.

Gerçekten de, UpdateSubresourceveya kullanırken Map/Unmap, dokümantasyon, hala eski verilere ihtiyaç duyması halinde, GPU tarafından dahili olarak birden fazla kopya yapılabileceğini belirtir, ancak zaten bağlı bir arabelleğin güncellenmesi söz konusu olduğunda bu, API kullanıcısı için bir endişe değildir. Bu nedenle, açıkça bağlanma ihtiyacı gereksiz görünmektedir.

Deneylerim sırasında, XXSetConstantBufferszaten bağlı olmadıkça tamponları güncelledikten sonra yeniden bağlamanın gereksiz olduğu sonucuna vardım ! Aynı tamponları (gölgelendiriciler arasında paylaşılan boru hattı aşamaları arasında) kullandığınız sürece (örneğin başlangıç ​​aşamasında), bunları yeniden bağlamanız gerekmez - sadece güncelleyin.

Deneylerimin doğasını daha iyi sergilemek için bazı kodlar:

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

İşte bu yaklaşımı benimseyen ve öneren bazı konular (gamedev forumları): http://www.gamedev.net/topic/649410-set-constant-buffers-every-frame/?view=findpost&p=5105032 ve http://www.gamedev.net/topic/647203-güncelestirme- sabit- tamponlar/#entry5090000

Sonuç olarak, tamponu tamamen değiştirmediğiniz sürece bağlamaya gerek yoktur, ancak tamponları paylaştığınız ve gölgelendiriciler arasındaki düzenlerini (önerilen uygulama) bağlama şu durumlarda yapılmalıdır:

  • Başlangıçta - ilk ciltleme - örneğin tamponu oluşturduktan sonra.
  • Bir veya daha fazla aşamanın belirli bir yuvasına bağlı birden fazla arabellek kullanmanız gerekiyorsa / tasarladıysanız.
  • DeviceContext durumunu temizledikten sonra (arabellekleri / pencereleri yeniden boyutlandırırken)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.