DirectX11, 여러 셰이더 상수 버퍼를 어떻게 관리하고 업데이트합니까?


13

좋아, 상수 버퍼가 파이프 라인 단계에 바인딩되고 업데이트되는 방법을 파악하는 데 어려움을 겪고 있습니다. DirectX11은 스테이지 당 최대 15 개의 쉐이더 상수 버퍼를 가질 수 있으며 각 버퍼는 최대 4096 개의 상수를 보유 할 수 있음을 이해합니다. 그러나 상수 버퍼와 상호 작용하는 데 사용되는 ID3D11Buffer COM이 이러한 버퍼 슬롯을 채우는 데 사용되는 메커니즘 (또는 핸들)인지 또는 객체가 실제로 앞뒤로 밀리는 특정 버퍼 데이터 인스턴스를 참조하는지 여부는 이해할 수 없습니다. GPU와 CPU 사이.

나는이 주제에 대한 혼란이 두 개의 다른 상수 버퍼를 사용하는 데있어 문제의 원인이라고 생각합니다.

다음은 셰이더 코드의 예입니다.

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

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

내 코드가 구성되는 방식으로 카메라는 프레임 당 관련 데이터 업데이트를 처리하고 GameObjects는 객체 당 자체 데이터를 업데이트합니다. 두 클래스에는 모두이를 수행하는 데 사용되는 고유 ID3D11Buffer가 있습니다 (허브 아키텍처를 사용하므로 하나의 GameObject 클래스가 월드의 모든 인스턴스화 된 게임 오브젝트의 렌더링을 처리합니다).

문제는 슬롯에 따라 한 번에 하나만 업데이트 할 수 있으며 하나의 버퍼가 채워지고 다른 버퍼는 0이되는 업데이트 순서를 가정합니다.

이것은 본질적으로 내 코드입니다. 두 클래스 모두 동일한 업데이트 논리를 사용합니다.

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

내 주요 질문은-

  • UpdateSubresource 호출로 업데이트하려면 ShaderBuffer를 설정하거나 바인딩해야합니까? (이것은 파이프 라인에있을 때만 조작합니다) 아니면 VSSetConstantBuffer 호출과 함께 전송 될 데이터 덩어리입니까? (데이터 바인딩 및 업데이트 순서를 의미하는 것은 중요하지 않습니다. 파이프 라인이나 CPU에서 업데이트 할 수 있습니다)
  • 버퍼를 설정하거나 바인딩 할 때 PerFrame 버퍼를 업데이트하려면 슬롯 0을, PerObject 버퍼를 업데이트하려면 슬롯 1을 참조해야합니까? 내 코드 에서이 호출과 혼동하여 모든 버퍼를 덮어 쓸 수 있습니까?
  • D3D11은 업데이트하거나 매핑하려는 버퍼를 어떻게 알 수 있습니까? 사용 된 ID3D11Buffer COM에서 알고 있습니까?

편집하다 -

위 예제에서 상수 버퍼 레지스터 태그를 변경했습니다. (b #) 대신 (cb #)을 사용하면 어떤 이유로 버퍼가 올바르게 업데이트되지 않습니다. 원래 구문을 어디에서 가져 왔는지 또는 전혀 유효한지 확실하지 않지만 주요 문제인 것 같습니다.

답변:


18

ID3D11Buffer는 버텍스 버퍼, 상수 버퍼 등 무엇이든 데이터를 보유하는 실제 메모리 청크를 참조합니다.

상수 버퍼는 정점 버퍼 및 다른 종류의 버퍼와 같은 방식으로 작동합니다. 즉, 실제로 프레임을 렌더링 할 때까지 GPU에서 데이터에 액세스 할 수 없으므로 GPU가 처리 될 때까지 버퍼는 계속 유효해야합니다. 각 상수 버퍼를 이중 버퍼링해야하므로 다음 프레임에 대해 업데이트 할 사본 한 개와 현재 프레임을 렌더링하는 동안 GPU에서 읽을 사본 한 개가 있어야합니다. 이것은 파티클 시스템 등에 동적 버텍스 버퍼를하는 방법과 유사합니다.

HLSL 의 register(cb0), register(cb1)설정은 VSSetConstantBuffers의 슬롯에 해당합니다. 프레임 별 상수 VSSetConstantBuffers(0, 1, &pBuffer)를 업데이트 할 때 CB0을 설정하고 객체 별 상수 를 업데이트 할 때 VSSetConstantBuffers(1, 1, &pBuffer)CB1을 설정 해야 합니다. 각 호출은 시작 / 카운트 매개 변수가 참조하는 버퍼 만 업데이트하고 다른 매개 변수는 건드리지 않습니다.

UpdateSubresource로 버퍼를 업데이트하기 위해 버퍼를 바인딩 할 필요는 없습니다. 실제로 업데이트 할 때 바인딩 되지 않아야합니다. 그렇지 않으면 드라이버가 내부적으로 추가 메모리 복사본을 만들어야 할 수 있습니다 ( UpdateSubresource에 대한 MSDN 페이지 , 특히 페이지 다운 관련 경합에 대한 설명 참조).

"D3D11이 어떤 버퍼를 업데이트하거나 매핑 하려는지 어떻게 알 수 있습니까?"라는 것이 무슨 의미인지 잘 모르겠습니다. 포인터를 전달한 사람을 업데이트하거나 매핑합니다.


3

상수 버퍼를 업데이트 한 후 다시 바인딩해야한다는 주제에 대해 많은 혼란이있는 것 같습니다. 내가 이것에 대해 배우면서 나는 이것에 반대 의견을 가진 많은 주제와 토론을 보았습니다. 즉 여기에 가장 좋은 대답은, 추천 호출 XXSetConstantBuffers이를 통해 업데이트 한 후 UpdateSubresource또는 Map/Unmap.

또한 일부 D3D MSDN 샘플 및 문서 는 기존 버퍼 만 업데이트하고 완전히 다른 버퍼로 특정 슬롯을 변경하지 않더라도 프레임 또는 그려진 객체별로 바인딩 (호출 ) 패턴을 사용하는 것으로 보입니다. .XXSetConstantBuffers

최악의 오해는 XXSetConstantBuffers실제로 "이전에 GPU에 업데이트 한 데이터를 보내거나 업데이트를 알리기 때문에 완전히 새로운 값을 가져 오는 것"이라고 생각합니다.

실제로 사용하는 경우 UpdateSubresourceMap/Unmap, 여전히 이전 데이터를 필요로하지만 이미 바인딩 버퍼를 갱신에 관해서이이 API의 사용자에 대한 우려가 아닌 경우 내부적으로 여러 복사본이 GPU에 의해 만들어 질 수있다 문서 상태. 따라서 명시 적으로 언 바운드해야 할 필요성이 불필요합니다.

실험하는 동안 버퍼 XXSetConstantBuffers가 아직 바인딩되지 않은 경우 버퍼를 업데이트 한 후 다시 바인딩 할 필요가 없다는 결론에 도달했습니다 ! 예를 들어 시작 단계에서 한 번 바인딩 된 동일한 버퍼 (쉐이더, 균등 한 파이프 라인 단계)를 사용하는 경우 다시 바인딩 할 필요가 없습니다. 업데이트 만하면됩니다.

실험 성격을 더 잘 보여주는 코드 :

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

다음은이 접근 방식을 채택하고 권장하는 것처럼 보이는 인터넷 (gamedev 포럼)의 일부 주제입니다. http://www.gamedev.net/topic/649410-set-constant-buffers-every-frame/?view=findpost&p=5105032http://www.gamedev.net/topic/647203-updating-constant-buffers/#entry5090000

결론적으로 말하자면 버퍼를 완전히 변경하지 않는 한 버퍼를 공유하지 않고 버퍼를 공유하고 쉐이더 (권장되는 연습) 바인딩 사이의 레이아웃을 다음과 같은 경우 바인딩 해야하는 한 바인딩이 필요하지 않은 .

  • 예를 들어 버퍼 생성 후 시작시-초기 바인딩-.
  • 하나 이상의 스테이지의 특정 슬롯에 바인딩 된 둘 이상의 버퍼를 사용하도록 설계 / 필요한 경우.
  • deviceContext의 상태를 지운 후 (버퍼 / 윈도우 크기를 조정할 때)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.