다음을 통해 공유


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

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

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 참조하세요.

리소스의 초기 상태

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

  • 업로드 힙은 D3D12_RESOURCE_STATE_GENERIC_READ 상태로 시작해야 하며, 이는 비트 OR 조합으로 이루어집니다.
    • 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 상태로 전환해야 합니다. 그러나 업로드 힙에서 만든 리소스는 시작되어야 하며 CPU만 쓰기를 수행하므로 GENERIC_READ 상태에서 변경할 수 없습니다. 반대로 READBACK 힙에서 생성된 커밋된 리소스는 COPY_DEST 상태에서 시작해야 하며, 이 상태를 변경할 수 없습니다.

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

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

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

버퍼를 다시 표시하기 위한 리소스 상태

백 버퍼를 표시하기 전에, D3D12_RESOURCE_STATE_COMMON 상태에 있어야 합니다. 리소스 상태 D3D12_RESOURCE_STATE_PRESENT D3D12_RESOURCE_STATE_COMMON 동의어이며 둘 다 값이 0입니다. 현재 이 상태가 아닌 리소스에서 IDXGISwapChain::P resent (또는 IDXGISwapChain1::P resent1)가 호출되면 디버그 계층 경고가 내보내집니다.

리소스 삭제

ID3D12GraphicsCommandList::DiscardResource가 호출될 때, 리소스의 모든 하위 리소스는 각각 렌더링 대상 리소스의 경우 RENDER_TARGET 상태에, 깊이 스텐실 리소스의 경우 DEPTH_WRITE 상태에 있어야 합니다.

암시적 상태 전환

리소스는 오직 D3D12_RESOURCE_STATE_COMMON에서만 전환할 수 있습니다. 마찬가지로 리소스는 D3D12_RESOURCE_STATE_COMMON "감쇠"만 합니다.

일반 상태 승격

모든 버퍼 자원 및 D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS 플래그가 설정된 텍스처는 첫 번째 GPU 접근 시 암시적으로 D3D12_RESOURCE_STATE_COMMON 상태에서 관련 상태로 전환되며, 이는 모든 읽기 시나리오를 포함할 수 있도록 GENERIC_READ로 설정됩니다. COMMON 상태의 모든 리소스는 단일 상태에 있는 것처럼 액세스할 수 있습니다.

쓰기 플래그 1개 또는 읽기 플래그 1개 이상

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

상태 플래그 버퍼 및 동시 액세스 텍스처 동시 액세스가 불가능한 텍스처
VERTEX_AND_CONSTANT_BUFFER Yes 아니오
INDEX_BUFFER Yes 아니오
렌더 타겟 Yes 아니오
비순서 액세스 Yes 아니오
DEPTH_WRITE (깊이 쓰기) * 없음 아니오
DEPTH_READ * 없음 아니오
NON_PIXEL_SHADER_RESOURCE Yes Yes
픽셀 셰이더 리소스 Yes Yes
STREAM_OUT Yes 아니오
간접_인수 Yes 아니오
복사_대상 Yes Yes
COPY_SOURCE Yes Yes
RESOLVE_DEST Yes 아니오
원본_해결 Yes 아니오
단언 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 명령 큐에서 다음 액세스가 필요로 하는 경우, 리소스가 COMMON 상태에서 시작해야 합니다). 일반 상태로 과도하게 전환하면 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));
}

마침은 프레임이 종료되면 호출되어 그림자 맵을 공통 상태로 전환합니다.

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 리소스 상태 장벽 내부 살펴보기