Share via


Direct3D 12에서 리소스 장벽을 사용하여 리소스 상태 동기화

전체 CPU 사용량을 줄이고 드라이버 다중 스레딩 및 전처리를 사용하도록 설정하기 위해 Direct3D 12는 리소스별 상태 관리의 책임을 그래픽 드라이버에서 애플리케이션으로 이동합니다. 리소스별 상태의 예로, 현재 셰이더 리소스 보기 또는 렌더링 대상 보기를 통해 텍스처 리소스에 액세스 중인지 여부가 있습니다. Direct3D 11에서는 드라이버에서 이 상태를 백그라운드로 추적해야 했습니다. 이 경우 CPU 측면에서 비용이 많이 들고 모든 종류의 다중 스레드 설계를 상당히 복잡하게 만듭니다. Microsoft Direct3D 12에서는 애플리케이션에서 하나의 ID3D12GraphicsCommandList::ResourceBarrier API를 사용하여 대부분의 리소스별 상태를 관리합니다.

ResourceBarrier API를 사용하여 리소스별 상태 관리

ResourceBarrier는 드라이버에서 리소스가 저장된 메모리에 대한 다중 액세스를 동기화해야 하는 상황을 그래픽 드라이버에 알립니다. 이 메서드는 선언되는 리소스 장벽의 유형을 나타내는 하나 이상의 리소스 장벽 설명 구조체를 사용하여 호출됩니다.

리소스 장벽에는 세 가지 유형이 있습니다.

  • 전환 장벽 - 전환 장벽은 하위 리소스 세트를 서로 다른 사용 간에 전환함을 나타냅니다. D3D12_RESOURCE_TRANSITION_BARRIER 구조체는 전환 중인 하위 리소스와 하위 리소스의 이전이후 상태를 지정하는 데 사용됩니다.

    시스템은 명령 목록의 하위 리소스 전환이 동일한 명령 목록의 이전 전환과 일치하는지 확인합니다. 디버그 계층은 하위 리소스 상태를 추가로 추적하여 다른 오류를 찾지만, 이 유효성 검사는 보수적이며 완전하지 않습니다.

    D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES 플래그를 사용하여 리소스 내의 모든 하위 리소스가 전환되도록 지정할 수 있습니다.

  • 앨리어싱 장벽 - 앨리어싱 장벽은 동일한 힙에 매핑이 겹치는 서로 다른 두 리소스의 사용 간의 전환을 나타냅니다. 이는 예약된 리소스와 배치된 리소스 모두에 적용됩니다. D3D12_RESOURCE_ALIASING_BARRIER 구조체는 이전 리소스와 리소스를 모두 지정하는 데 사용됩니다.

    하나 또는 둘 다 NULL일 수 있습니다. 이는 타일식 리소스로 인해 앨리어싱이 발생할 수 있음을 나타냅니다. 타일식 리소스를 사용하는 방법에 대한 자세한 내용은 타일식 리소스볼륨 타일식 리소스를 참조하세요.

  • UAV(순서가 지정되지 않은 액세스 보기) 장벽 - UAV 장벽은 특정 리소스에 대한 읽기 또는 쓰기 모두의 모든 UAV 액세스가 향후 UAV 액세스(읽기 또는 쓰기 모두) 간에 완료되어야 함을 나타냅니다. 애플리케이션에서 UAV로부터만 읽는 두 개의 그리기 또는 디스패치 호출 간에 UAV 장벽을 적용할 필요가 없습니다. 또한 애플리케이션에서 어떤 순서로 UAV 액세스를 실행하는 것이 안전하다는 것을 인식하고 있는 경우에도 동일한 UAV에 쓰는 두 개의 그리기 또는 디스패치 호출 간에 UAV 장벽을 적용할 필요가 없습니다. D3D12_RESOURCE_UAV_BARRIER 구조체는 장벽이 적용되는 UAV 리소스를 지정하는 데 사용됩니다. 애플리케이션은 장벽의 UAV에 NULL을 지정할 수 있습니다. 이는 UAV 액세스에 장벽이 필요할 수 있음을 나타냅니다.

ResourceBarrier가 리소스 장벽 설명의 배열을 사용하여 호출되면 API는 제공된 순서대로 각 요소에 대해 한 번 호출한 것처럼 작동합니다.

지정된 시간에 하위 리소스는 ResourceBarrier에 제공된 D3D12_RESOURCE_STATES 플래그 집합에 의해 결정되는 정확히 하나의 상태에 있습니다. 애플리케이션은 ResourceBarrier에 대한 연속 호출의 이전이후 상태가 동의하는지 확인해야 합니다.

애플리케이션은 가능한 한 여러 전환을 하나의 API 호출로 일괄 처리해야 합니다.

 

리소스 상태

리소스가 전환할 수 있는 리소스 상태의 전체 목록은 D3D12_RESOURCE_STATES 열거형에 대한 참조 항목을 참조하세요.

분할 리소스 장벽의 경우 D3D12_RESOURCE_BARRIER_FLAGS 참조하세요.

초기 리소스 상태

리소스는 다음 예외를 제외하고 사용자가 지정한 초기 상태(리소스 설명에 유효함)로 만들 수 있습니다.

  • 업로드 힙은 비트 OR 조합인 상태 D3D12_RESOURCE_STATE_GENERIC_READ 시작되어야 합니다.
    • D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER
    • D3D12_RESOURCE_STATE_INDEX_BUFFER
    • D3D12_RESOURCE_STATE_COPY_SOURCE
    • D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE
    • D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
    • D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT
  • 다시 읽기 힙은 D3D12_RESOURCE_STATE_COPY_DEST 상태에서 시작해야 합니다.
  • 스왑 체인 백 버퍼는 D3D12_RESOURCE_STATE_COMMON 상태에서 자동으로 시작됩니다.

힙이 GPU 복사 작업의 대상이 되기 전에 일반적으로 힙을 먼저 D3D12_RESOURCE_STATE_COPY_DEST 상태로 전환해야 합니다. 그러나 UPLOAD 힙에서 만든 리소스는 에서 시작해야 하며 CPU만 쓰기를 수행하므로 GENERIC_READ 상태에서 변경할 수 없습니다. 반대로 READBACK 힙에서 만든 커밋된 리소스는 에서 시작해야 하며 COPY_DEST 상태에서 변경할 수 없습니다.

읽기/쓰기 리소스 상태 제한 사항

리소스 상태를 설명하는 데 사용되는 리소스 상태 사용 비트는 읽기 전용 및 읽기/쓰기 상태로 나뉩니다. D3D12_RESOURCE_STATES 대한 참조 항목은 열거형의 각 비트에 대한 읽기/쓰기 액세스 수준을 나타냅니다.

모든 리소스에 대해 최소한 하나의 읽기/쓰기 비트만 설정할 수 있습니다. 쓰기 비트가 설정된 경우 해당 리소스에 대한 읽기 전용 비트가 설정되지 않을 수 있습니다. 쓰기 비트가 설정되지 않은 경우 임의 개수의 읽기 비트를 설정할 수 있습니다.

백 버퍼 표시 리소스 상태

백 버퍼가 표시되기 전에 D3D12_RESOURCE_STATE_COMMON 상태여야 합니다. 리소스 상태 D3D12_RESOURCE_STATE_PRESENT D3D12_RESOURCE_STATE_COMMON 동의어이며 둘 다 값이 0입니다. 현재 이 상태가 아닌 리소스에 대해 IDXGISwapChain::Present(또는 IDXGISwapChain1::Present1)가 호출되면 디버그 계층 경고를 내보냅니다.

리소스 삭제

ID3D12GraphicsCommandList::D iscardResource가 호출될 때 리소스의 모든 하위 리소스는 렌더링 대상/깊이 스텐실 리소스에 대해 각각 RENDER_TARGET 상태 또는 DEPTH_WRITE 상태여야 합니다.

암시적 상태 전환

리소스는 D3D12_RESOURCE_STATE_COMMON "승격"될 수 있습니다. 마찬가지로 리소스는 D3D12_RESOURCE_STATE_COMMON "감쇠"됩니다.

일반 상태 승격

모든 버퍼 리소스와 D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS 플래그가 설정된 텍스처는 읽기 시나리오를 다루는 GENERIC_READ 포함하여 첫 번째 GPU 액세스 시 D3D12_RESOURCE_STATE_COMMON 관련 상태로 암시적으로 승격됩니다. COMMON 상태의 모든 리소스는

1 WRITE 플래그 또는 1개 이상의 READ 플래그

리소스는 다음 표에 따라 COMMON 상태에서 승격할 수 있습니다.

상태 플래그 버퍼 및 동시 액세스 텍스처 비동시 액세스 텍스처
VERTEX_AND_CONSTANT_BUFFER Yes 아니요
INDEX_BUFFER Yes 아니요
RENDER_TARGET Yes 아니요
UNORDERED_ACCESS Yes 아니요
DEPTH_WRITE 아니요* No
DEPTH_READ 아니요* No
NON_PIXEL_SHADER_RESOURCE Yes Yes
PIXEL_SHADER_RESOURCE Yes Yes
STREAM_OUT Yes 아니요
INDIRECT_ARGUMENT Yes 아니요
COPY_DEST Yes Yes
COPY_SOURCE Yes Yes
RESOLVE_DEST Yes 아니요
RESOLVE_SOURCE Yes 아니요
PREDICATION Yes 아니요

 

*깊이 스텐실 리소스는 비동시 액세스 텍스처여야 하므로 암시적으로 승격할 수 없습니다.

이 액세스가 발생하면 승격은 암시적 리소스 장벽처럼 작동합니다. 후속 액세스의 경우 필요에 따라 리소스 상태를 변경하기 위해 리소스 장벽이 필요합니다. 승격된 읽기 상태를 여러 읽기 상태로 승격하는 것은 유효하지만 쓰기 상태에는 그렇지 않습니다.
예를 들어 공용 상태의 리소스가 그리기 호출에서 PIXEL_SHADER_RESOURCE 승격되는 경우에도 여전히 NON_PIXEL_SHADER_RESOURCE | 로 승격될 수 있습니다. 다른 그리기 호출에서 PIXEL_SHADER_RESOURCE. 그러나 복사 대상, 결합된 승격된 읽기 상태의 리소스 상태 전환 장벽과 같은 쓰기 작업에 사용되는 경우 여기 NON_PIXEL_SHADER_RESOURCE | PIXEL_SHADER_RESOURCE COPY_DEST 필요합니다.
마찬가지로 COMMON에서 COPY_DEST 승격되는 경우에도 COPY_DEST RENDER_TARGET 전환하려면 장벽이 필요합니다.

GPU에서 동기화 대기를 수행할 필요가 없다는 점에서 일반 상태 승격은 "무료"입니다. 승격은 COMMON 상태의 리소스에서 특정 액세스를 지원하기 위해 추가 GPU 작업 또는 드라이버 추적을 요구하지 않아야 한다는 사실을 나타냅니다.

일반 상태로 강등

일반적인 상태 승격의 플립 사이드는 D3D12_RESOURCE_STATE_COMMON 다시 부패. 특정 요구 사항을 충족하는 리소스는 상태 비저장으로 간주되며, GPU에서 ExecuteCommandLists 작업의 실행이 완료되면 효과적으로 일반 상태로 돌아갑니다. 동일한 ExecuteCommandLists 호출에서 함께 실행된 명령 목록 간에는 강등이 발생하지 않습니다.

GPU에서 ExecuteCommandLists 작업이 완료되면 강등되는 리소스는 다음과 같습니다.

  • 복사 큐에서 액세스 중인 리소스 또는
  • 모든 큐 유형의 버퍼 리소스 또는
  • D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS 플래그가 설정된 큐 형식의 텍스처 리소스 또는
  • 암시적으로 읽기 전용 상태로 승격된 모든 리소스

일반 상태 승격과 마찬가지로, 추가 동기화가 필요하지 않다는 점에서 강등도 무료입니다. 일반 상태 승격과 강등을 결합하면 불필요한 ResourceBarrier 전환을 대부분 제거할 수 있습니다. 경우에 따라 이로 인해 성능이 크게 향상될 수 있습니다.

버퍼 및 Simultaneous-Access 리소스는 리소스 장벽을 사용하여 명시적으로 전환되었는지 또는 암시적으로 승격되었는지 여부에 관계없이 공통 상태로 감소합니다.

성능 의미

공용 상태의 리소스에 대한 명시적 ResourceBarrier 전환을 기록하는 경우 D3D12_RESOURCE_TRANSITION_BARRIER 구조에서 D3D12_RESOURCE_STATE_COMMON 또는 승격 가능한 상태를 BeforeState 값으로 사용하는 것이 좋습니다. 이렇게 하면 통해 버퍼와 동시 액세스 텍스처의 자동 강등을 무시하는 기존 상태 관리를 허용합니다. 그러나 일반 상태로 알려진 리소스를 사용하여 ResourceBarrier 호출을 전환하지 않도록 방지하면 성능이 크게 향상될 수 있으므로 이 작업은 바람직하지 않을 수 있습니다. 리소스 장벽은 비용이 많이 들 수 있습니다. 캐시 플러시, 메모리 레이아웃 변경 및 이미 일반 상태에 있는 리소스에 필요하지 않을 수 있는 다른 동기화를 강제 적용하도록 설계되었습니다. 현재 일반 상태에 있는 리소스에서 일반 상태가 아닌 상태에서 또 다른 일반 상태가 아닌 상태로의 리소스 장벽을 사용하는 명령 목록은 불필요한 오버헤드를 많이 발생시킬 수 있습니다.

또한 절대적으로 필요한 경우가 아니면 명시적 ResourceBarrier 가 D3D12_RESOURCE_STATE_COMMON 전환하지 않도록 합니다(예: 다음 액세스는 리소스가 공통 상태에서 시작해야 하는 COPY 명령 큐에 있습니다). 과도하게 일반 상태로 전환하면 GPU 성능이 크게 저하될 수 있습니다.

요약하면, ResourceBarrier 호출을 실행하지 않고 해당 의미 체계에서 벗어날 수 있을 때마다 일반 상태 승격 및 강등을 사용하려고 시도합니다.

분할 장벽

D3D12_RESOURCE_BARRIER_FLAG_BEGIN_ONLY 플래그가 있는 리소스 전환 장벽은 분할 장벽을 시작하고 전환 장벽은 보류 중이라고 합니다. 장벽이 보류 중인 동안 리소스(하위)는 GPU에서 읽거나 쓸 수 없습니다. 보류 중인 장벽이 있는 (하위) 리소스에 적용할 수 있는 유일한 법적 전환 장벽은 상태전후와 D3D12_RESOURCE_BARRIER_FLAG_END_ONLY 플래그가 같으며, 이 장벽은 보류 중인 전환을 완료합니다.

분할 장벽은 A 상태의 리소스가 나중에 B 상태에서 사용된다는 힌트를 GPU에 제공합니다. 이렇게 하면 전환 워크로드를 최적화할 수 있는 옵션을 GPU에 제공하여 실행 지연을 줄이거나 제거할 수 있습니다. 최종 전용 장벽을 발급하면 다음 명령으로 이동하기 전에 모든 GPU 전환 작업이 완료됩니다.

분할 장벽을 사용하면 특히 다중 엔진 시나리오에서 또는 하나 이상의 명령 목록 전체에 걸쳐 리소스의 읽기/쓰기 전환이 드물게 수행되는 경우에 성능을 향상시킬 수 있습니다.

리소스 장벽 예제 시나리오

다음 코드 조각에서는 다중 스레딩 샘플에서 ResourceBarrier 메서드를 사용하는 방법을 보여 줍니다.

깊이 스텐실 보기를 만들고 쓰기 가능한 상태로 전환합니다.

// Create the depth stencil.
{
    CD3DX12_RESOURCE_DESC shadowTextureDesc(
        D3D12_RESOURCE_DIMENSION_TEXTURE2D,
        0,
        static_cast<UINT>(m_viewport.Width), 
        static_cast<UINT>(m_viewport.Height), 
        1,
        1,
        DXGI_FORMAT_D32_FLOAT,
        1, 
        0,
        D3D12_TEXTURE_LAYOUT_UNKNOWN,
        D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE);

    D3D12_CLEAR_VALUE clearValue;    // Performance tip: Tell the runtime at resource creation the desired clear value.
    clearValue.Format = DXGI_FORMAT_D32_FLOAT;
    clearValue.DepthStencil.Depth = 1.0f;
    clearValue.DepthStencil.Stencil = 0;

    ThrowIfFailed(m_device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &shadowTextureDesc,
        D3D12_RESOURCE_STATE_DEPTH_WRITE,
        &clearValue,
        IID_PPV_ARGS(&m_depthStencil)));

    // Create the depth stencil view.
    m_device->CreateDepthStencilView(m_depthStencil.Get(), nullptr, m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
}

꼭짓점 버퍼 보기를 만들고, 먼저 일반 상태에서 대상으로 변경한 다음, 대상에서 일반 읽기 가능 상태로 변경합니다.

// Create the vertex buffer.
{
    ThrowIfFailed(m_device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(SampleAssets::VertexDataSize),
        D3D12_RESOURCE_STATE_COPY_DEST,
        nullptr,
        IID_PPV_ARGS(&m_vertexBuffer)));

    {
        ThrowIfFailed(m_device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(SampleAssets::VertexDataSize),
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&m_vertexBufferUpload)));

        // Copy data to the upload heap and then schedule a copy 
        // from the upload heap to the vertex buffer.
        D3D12_SUBRESOURCE_DATA vertexData = {};
        vertexData.pData = pAssetData + SampleAssets::VertexDataOffset;
        vertexData.RowPitch = SampleAssets::VertexDataSize;
        vertexData.SlicePitch = vertexData.RowPitch;

        PIXBeginEvent(commandList.Get(), 0, L"Copy vertex buffer data to default resource...");

        UpdateSubresources<1>(commandList.Get(), m_vertexBuffer.Get(), m_vertexBufferUpload.Get(), 0, 0, 1, &vertexData);
        commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_vertexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER));

        PIXEndEvent(commandList.Get());
    }

인덱스 버퍼 보기를 만들어 먼저 일반 상태에서 대상으로 변경한 다음, 대상에서 제네릭 읽기 가능 상태로 변경합니다.

// Create the index buffer.
{
    ThrowIfFailed(m_device->CreateCommittedResource(
        &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
        D3D12_HEAP_FLAG_NONE,
        &CD3DX12_RESOURCE_DESC::Buffer(SampleAssets::IndexDataSize),
        D3D12_RESOURCE_STATE_COPY_DEST,
        nullptr,
        IID_PPV_ARGS(&m_indexBuffer)));

    {
        ThrowIfFailed(m_device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(SampleAssets::IndexDataSize),
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&m_indexBufferUpload)));

        // Copy data to the upload heap and then schedule a copy 
        // from the upload heap to the index buffer.
        D3D12_SUBRESOURCE_DATA indexData = {};
        indexData.pData = pAssetData + SampleAssets::IndexDataOffset;
        indexData.RowPitch = SampleAssets::IndexDataSize;
        indexData.SlicePitch = indexData.RowPitch;

        PIXBeginEvent(commandList.Get(), 0, L"Copy index buffer data to default resource...");

        UpdateSubresources<1>(commandList.Get(), m_indexBuffer.Get(), m_indexBufferUpload.Get(), 0, 0, 1, &indexData);
        commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_indexBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_INDEX_BUFFER));

        PIXEndEvent(commandList.Get());
    }

    // Initialize the index buffer view.
    m_indexBufferView.BufferLocation = m_indexBuffer->GetGPUVirtualAddress();
    m_indexBufferView.SizeInBytes = SampleAssets::IndexDataSize;
    m_indexBufferView.Format = SampleAssets::StandardIndexFormat;
}

텍스처 및 셰이더 리소스 보기를 만듭니다. 텍스처를 일반 상태에서 대상으로 변경한 다음, 대상에서 픽셀 셰이더 리소스로 변경합니다.

    // Create each texture and SRV descriptor.
    const UINT srvCount = _countof(SampleAssets::Textures);
    PIXBeginEvent(commandList.Get(), 0, L"Copy diffuse and normal texture data to default resources...");
    for (int i = 0; i < srvCount; i++)
    {
        // Describe and create a Texture2D.
        const SampleAssets::TextureResource &tex = SampleAssets::Textures[i];
        CD3DX12_RESOURCE_DESC texDesc(
            D3D12_RESOURCE_DIMENSION_TEXTURE2D,
            0,
            tex.Width, 
            tex.Height, 
            1,
            static_cast<UINT16>(tex.MipLevels),
            tex.Format,
            1, 
            0,
            D3D12_TEXTURE_LAYOUT_UNKNOWN,
            D3D12_RESOURCE_FLAG_NONE);

        ThrowIfFailed(m_device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
            D3D12_HEAP_FLAG_NONE,
            &texDesc,
            D3D12_RESOURCE_STATE_COPY_DEST,
            nullptr,
            IID_PPV_ARGS(&m_textures[i])));

        {
            const UINT subresourceCount = texDesc.DepthOrArraySize * texDesc.MipLevels;
            UINT64 uploadBufferSize = GetRequiredIntermediateSize(m_textures[i].Get(), 0, subresourceCount);
            ThrowIfFailed(m_device->CreateCommittedResource(
                &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
                D3D12_HEAP_FLAG_NONE,
                &CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize),
                D3D12_RESOURCE_STATE_GENERIC_READ,
                nullptr,
                IID_PPV_ARGS(&m_textureUploads[i])));

            // Copy data to the intermediate upload heap and then schedule a copy 
            // from the upload heap to the Texture2D.
            D3D12_SUBRESOURCE_DATA textureData = {};
            textureData.pData = pAssetData + tex.Data->Offset;
            textureData.RowPitch = tex.Data->Pitch;
            textureData.SlicePitch = tex.Data->Size;

            UpdateSubresources(commandList.Get(), m_textures[i].Get(), m_textureUploads[i].Get(), 0, 0, subresourceCount, &textureData);
            commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_textures[i].Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
        }

        // Describe and create an SRV.
        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.Format = tex.Format;
        srvDesc.Texture2D.MipLevels = tex.MipLevels;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        m_device->CreateShaderResourceView(m_textures[i].Get(), &srvDesc, cbvSrvHandle);

        // Move to the next descriptor slot.
        cbvSrvHandle.Offset(cbvSrvDescriptorSize);
    }

프레임을 시작합니다. 여기서는 ResourceBarrier를 사용하여 백 버퍼를 렌더링 대상으로 사용할 것임을 나타낼 뿐만 아니라 깊이 스텐실 버퍼에서 ResourceBarrier를 호출하는 프레임 리소스도 초기화합니다.

// Assemble the CommandListPre command list.
void D3D12Multithreading::BeginFrame()
{
    m_pCurrentFrameResource->Init();

    // Indicate that the back buffer will be used as a render target.
    m_pCurrentFrameResource->m_commandLists[CommandListPre]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));

    // Clear the render target and depth stencil.
    const float clearColor[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
    m_pCurrentFrameResource->m_commandLists[CommandListPre]->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
    m_pCurrentFrameResource->m_commandLists[CommandListPre]->ClearDepthStencilView(m_dsvHeap->GetCPUDescriptorHandleForHeapStart(), D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

    ThrowIfFailed(m_pCurrentFrameResource->m_commandLists[CommandListPre]->Close());
}

// Assemble the CommandListMid command list.
void D3D12Multithreading::MidFrame()
{
    // Transition our shadow map from the shadow pass to readable in the scene pass.
    m_pCurrentFrameResource->SwapBarriers();

    ThrowIfFailed(m_pCurrentFrameResource->m_commandLists[CommandListMid]->Close());
}

프레임을 종료합니다. 이제 백 버퍼가 표시하는 데 사용됨을 나타냅니다.

// Assemble the CommandListPost command list.
void D3D12Multithreading::EndFrame()
{
    m_pCurrentFrameResource->Finish();

    // Indicate that the back buffer will now be used to present.
    m_pCurrentFrameResource->m_commandLists[CommandListPost]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[m_frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));

    ThrowIfFailed(m_pCurrentFrameResource->m_commandLists[CommandListPost]->Close());
}

프레임 리소스를 초기화합니다. 프레임을 시작할 때 호출되며 깊이 스텐실 버퍼를 쓰기 가능으로 전환합니다.

void FrameResource::Init()
{
    // Reset the command allocators and lists for the main thread.
    for (int i = 0; i < CommandListCount; i++)
    {
        ThrowIfFailed(m_commandAllocators[i]->Reset());
        ThrowIfFailed(m_commandLists[i]->Reset(m_commandAllocators[i].Get(), m_pipelineState.Get()));
    }

    // Clear the depth stencil buffer in preparation for rendering the shadow map.
    m_commandLists[CommandListPre]->ClearDepthStencilView(m_shadowDepthView, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);

    // Reset the worker command allocators and lists.
    for (int i = 0; i < NumContexts; i++)
    {
        ThrowIfFailed(m_shadowCommandAllocators[i]->Reset());
        ThrowIfFailed(m_shadowCommandLists[i]->Reset(m_shadowCommandAllocators[i].Get(), m_pipelineStateShadowMap.Get()));

        ThrowIfFailed(m_sceneCommandAllocators[i]->Reset());
        ThrowIfFailed(m_sceneCommandLists[i]->Reset(m_sceneCommandAllocators[i].Get(), m_pipelineState.Get()));
    }
}

장벽이 중간 프레임으로 바뀝니다. 섀도 맵을 쓰기 가능에서 읽기 가능으로 전환합니다.

void FrameResource::SwapBarriers()
{
    // Transition the shadow map from writeable to readable.
    m_commandLists[CommandListMid]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_shadowTexture.Get(), D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
}

프레임이 종료되면 Finish가 호출됩니다. 섀도 맵을 일반 상태로 전환합니다.

void FrameResource::Finish()
{
    m_commandLists[CommandListPost]->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_shadowTexture.Get(), D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_DEPTH_WRITE));
}

일반 상태 승격 및 강등 샘플

    // Create a buffer resource using D3D12_RESOURCE_STATE_COMMON as the init state
    ID3D12Resource *pResource;
    CreateCommittedVertexBufferInCommonState(1024, &pResource);

    // Copy data to the buffer without the need for a barrier.
    // Promotes pResource state to D3D12_RESOURCE_STATE_COPY_DEST.
    pCommandList->CopyBufferRegion(pResource, 0, pOtherResource, 0, 1024); 

    // To use pResource as a vertex buffer a transition barrier is needed.
    // Note the StateBefore is D3D12_RESOURCE_STATE_COPY_DEST.
    D3D12_RESOURCE_BARRIER BarrierDesc = {};
    BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    BarrierDesc.Transition.pResource = pResource;
    BarrierDesc.Transition.Subresource = 0;
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER;
    pCommandList->ResourceBarrier( 1, &BarrierDesc );

    // Use pResource as a vertex buffer
    D3D12_VERTEX_BUFFER_VIEW vbView;
    vbView.BufferLocation = pResource->GetGPUVirtualAddress();
    vbView.SizeInBytes = 1024;
    vbView.StrideInBytes = sizeof(MyVertex);

    pCommandList->IASetVertexBuffers(0, 1, &vbView);
    pCommandList->Draw();

    pCommandQueue->ExecuteCommandLists(1, &pCommandList); 
    pCommandList->Reset(pAllocator, pPipelineState);

    // Since the reset command list will be executed in a separate call to 
    // ExecuteCommandLists, the previous state of pResource
    // will have decayed to D3D12_RESOURCE_STATE_COMMON so, again, no barrier is needed
    pCommandList->CopyBufferRegion(pResource, 0, pDifferentResource, 0, 1024);

    FinishRecordingCommandList(pCommandList);
    pCommandQueue->ExecuteCommandLists(1, &pCommandList); 

    WaitForQueue(pCommandQueue);

    // The previous ExecuteCommandLists call has finished so 
    // pResource has decayed to D3D12_RESOURCE_STATE_COMMON

분할 장벽 예제

다음 예제에서는 분할 장벽을 사용하여 파이프라인 지연을 줄이는 방법을 보여 줍니다. 다음 코드에서는 분할 장벽을 사용하지 않습니다.

 D3D12_RESOURCE_BARRIER BarrierDesc = {};
    BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TRANSITION;
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_NONE;
    BarrierDesc.Transition.pResource = pResource;
    BarrierDesc.Transition.Subresource = 0;
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;

    pCommandList->ResourceBarrier( 1, &BarrierDesc );
    
    Write(pResource); // ... render to pResource
    OtherStuff(); // .. other gpu work

    // Transition pResource to PIXEL_SHADER_RESOURCE
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
    
    pCommandList->ResourceBarrier( 1, &BarrierDesc );

    Read(pResource); // ... read from pResource

다음 코드에서는 분할 장벽을 사용합니다.

D3D12_RESOURCE_BARRIER BarrierDesc = {};
    BarrierDesc.Type = D3D12_RESOURCE_BARRIER_TRANSITION;
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_NONE;
    BarrierDesc.Transition.pResource = pResource;
    BarrierDesc.Transition.Subresource = 0;
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_COMMON;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;

    pCommandList->ResourceBarrier( 1, &BarrierDesc );
    
    Write(pResource); // ... render to pResource

    // Done writing to pResource. Start barrier to PIXEL_SHADER_RESOURCE and
    // then do other work
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_BEGIN_ONLY;
    BarrierDesc.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    BarrierDesc.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
    pCommandList->ResourceBarrier( 1, &BarrierDesc );

    OtherStuff(); // .. other gpu work

    // Need to read from pResource so end barrier
    BarrierDesc.Flags = D3D12_RESOURCE_BARRIER_END_ONLY;

    pCommandList->ResourceBarrier( 1, &BarrierDesc );
    Read(pResource); // ... read from pResource

DirectX 고급 학습 비디오 자습서: 리소스 장벽 및 상태 추적

다중 엔진 동기화

Direct3D 12의 작업 제출

D3D12 리소스 상태 장벽 내부 살펴보기