다음을 통해 공유


설명자 힙 만들기

설명자 힙을 만들고 구성하려면 설명자 힙 형식을 선택하고, 포함된 설명자 수를 확인하고, CPU 표시인지 또는 셰이더 표시인지를 나타내는 플래그를 설정해야 합니다.

설명자 힙 형식

힙의 형식은 D3D12_DESCRIPTOR_HEAP_TYPE 열거형의 한 멤버에 의해 결정됩니다.

typedef enum D3D12_DESCRIPTOR_HEAP_TYPE
{
    D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,    // Constant buffer/Shader resource/Unordered access views
    D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,        // Samplers
    D3D12_DESCRIPTOR_HEAP_TYPE_RTV,            // Render target view
    D3D12_DESCRIPTOR_HEAP_TYPE_DSV,            // Depth stencil view
    D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES       // Simply the number of descriptor heap types
} D3D12_DESCRIPTOR_HEAP_TYPE;

설명자 힙 속성

힙 속성은 D3D12_DESCRIPTOR_HEAP_TYPE 열 거형과 D3D12_DESCRIPTOR_HEAP_FLAGS 열거형을 모두 참조하는 D3D12_DESCRIPTOR_HEAP_DESC 구조에 설정됩니다.

플래그 D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE 선택적으로 설명자 힙에 설정하여 셰이더가 참조하기 위해 명령 목록에 바인딩되어 있음을 나타낼 수 있습니다. 이 플래그 없이 설명자 힙을 만들면 애플리케이션에서는 설명자를 셰이더 표시 설명자 힙으로 복사하기 전에 CPU 메모리에 준비하는 옵션이 허용됩니다. 하지만 애플리케이션이 CPU에서 아무 것도 준비할 필요가 없이, 설명자를 셰이더 표시 설명자 힙으로 직접 만드는 것도 좋습니다.

이 플래그는 CBV, SRV, UAV 및 샘플러에만 적용됩니다. 셰이더가 다른 형식은 직접 참조하지 않으므로 다른 설명자 힙 형식에는 적용되지 않습니다.

예를 들어 샘플러 설명자 힙을 설명하고 만듭니다.

// Describe and create a sampler descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC samplerHeapDesc = {};
samplerHeapDesc.NumDescriptors = 1;
samplerHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
samplerHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&samplerHeapDesc, IID_PPV_ARGS(&m_samplerHeap)));

설명 하 고 CBV(상수 버퍼 뷰), SRV(셰이더 리소스 뷰) 및 UAV(순서가 지정되지 않은 액세스 뷰) 설명자 힙을 설명하고 만듭니다.

// Describe and create a shader resource view (SRV) and unordered
// access view (UAV) descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC srvUavHeapDesc = {};
srvUavHeapDesc.NumDescriptors = DescriptorCount;
srvUavHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvUavHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&srvUavHeapDesc, IID_PPV_ARGS(&m_srvUavHeap)));

m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
m_srvUavDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

설명자 핸들

D3D12_GPU_DESCRIPTOR_HANDLED3D12_CPU_DESCRIPTOR_HANDLE 구조체는 설명자 힙의 특정 설명자를 식별합니다. 핸들은 포인터와 같은 비트이지만, 애플리케이션에서 수동으로 역참조하지 않아야 합니다. 그렇지 않으면 해당 동작이 정의되지 않습니다. 핸들은 API를 통해 사용해야 합니다. 핸들 자체를 설명자에 작동하거나 설명자를 사용하는 API로 자유롭게 복사하거나 전달할 수 있습니다. 참조 계산은 없으므로 애플리케이션은 기본 설명자 힙이 삭제된 후에 핸들을 사용하지 않는다는 것을 확인해야 합니다.

애플리케이션은 지정된 설명자 힙 형식을 갖는 설명자의 증분 크기를 알 수 있으므로 핸들부터 기반까지, 설명자 힘의 모든 위치로의 핸들을 수동으로 생성할 수 있습니다. 애플리케이션은 설명자 핸들 증분 크기를 절대 하드코딩하지 않아야 하며, 항상 지정된 디바이스 인스턴스에서 쿼리해야 합니다. 그렇지 않으면 동작이 정의되지 않습니다. 또한 애플리케이션은 설명자 힙 데이터의 고유한 검사 또는 조작을 수행하기 위해 증분 크기 및 핸들을 사용하지 않아야 합니다. 이렇게 할 경우 결과가 정의되지 않습니다. 핸들은 실제로 포인터로 사용될 수 없으며, 우연한 역참조를 방지하기 위해 포인터의 프록시로 사용될 수 있습니다.

참고

D3D12_GPU_DESCRIPTOR_HANDLE 구조를 상속하고 초기화 및 기타 유용한 작업을 제공하는 CD3DX12_GPU_DESCRIPTOR_HANDLE 헤더 d3dx12.h에 정의된 도우미 구조체가 있습니다. 마찬가지로 CD3DX12_CPU_DESCRIPTOR_HANDLE 도우미 구조체는 D3D12_CPU_DESCRIPTOR_HANDLE 구조체에 대해 정의됩니다.

 

이러한 두 도우미 구조는 명령 목록을 채울 때 사용됩니다.

// Fill the command list with all the render commands and dependent state.
void D3D12nBodyGravity::PopulateCommandList()
{
    // Command list allocators can only be reset when the associated
    // command lists have finished execution on the GPU; apps should use
    // fences to determine GPU execution progress.
    ThrowIfFailed(m_commandAllocators[m_frameIndex]->Reset());

    // However, when ExecuteCommandList() is called on a particular command
    // list, that command list can then be reset at any time and must be before
    // re-recording.
    ThrowIfFailed(m_commandList->Reset(m_commandAllocators[m_frameIndex].Get(), m_pipelineState.Get()));

    // Set necessary state.
    m_commandList->SetPipelineState(m_pipelineState.Get());
    m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());

    m_commandList->SetGraphicsRootConstantBufferView(RootParameterCB, m_constantBufferGS->GetGPUVirtualAddress() + m_frameIndex * sizeof(ConstantBufferGS));

    ID3D12DescriptorHeap* ppHeaps[] = { m_srvUavHeap.Get() };
    m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);

    m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
    m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_POINTLIST);
    m_commandList->RSSetScissorRects(1, &m_scissorRect);

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

    CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
    m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);

    // Record commands.
    const float clearColor[] = { 0.0f, 0.0f, 0.1f, 0.0f };
    m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);

    // Render the particles.
    float viewportHeight = static_cast<float>(static_cast<UINT>(m_viewport.Height) / m_heightInstances);
    float viewportWidth = static_cast<float>(static_cast<UINT>(m_viewport.Width) / m_widthInstances);
    for (UINT n = 0; n < ThreadCount; n++)
    {
        const UINT srvIndex = n + (m_srvIndex[n] == 0 ? SrvParticlePosVelo0 : SrvParticlePosVelo1);

        D3D12_VIEWPORT viewport;
        viewport.TopLeftX = (n % m_widthInstances) * viewportWidth;
        viewport.TopLeftY = (n / m_widthInstances) * viewportHeight;
        viewport.Width = viewportWidth;
        viewport.Height = viewportHeight;
        viewport.MinDepth = D3D12_MIN_DEPTH;
        viewport.MaxDepth = D3D12_MAX_DEPTH;
        m_commandList->RSSetViewports(1, &viewport);

        CD3DX12_GPU_DESCRIPTOR_HANDLE srvHandle(m_srvUavHeap->GetGPUDescriptorHandleForHeapStart(), srvIndex, m_srvUavDescriptorSize);
        m_commandList->SetGraphicsRootDescriptorTable(RootParameterSRV, srvHandle);

        m_commandList->DrawInstanced(ParticleCount, 1, 0, 0);
    }

    m_commandList->RSSetViewports(1, &m_viewport);

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

    ThrowIfFailed(m_commandList->Close());
}

설명자 힙 메서드

설명자 힙(ID3D12DescriptorHeap)은 ID3D12Pageable에서 상속합니다. 따라서 리소스 힙과 마찬가지로, 애플리케이션의 설명자 힙의 경우도 상주 관리가 필요합니다. 상주 관리 방법은 비셰이더 표시 힙이 GPU에는 직접 표시되지 않으므로 셰이더 표시 힙에만 적용됩니다.

ID3D12Device::GetDescriptorHandleIncrementSize 메서드를 사용하면 애플리케이션은 핸들을 힙으로 수동으로 오프셋할 수 있습니다(설명자 힙에서 임의 위치로의 핸들 생성). 힙 시작 위치의 핸들은 ID3D12DescriptorHeap::GetCPUDescriptorHandleForHeapStart/ID3D12DescriptorHeap::GetGPUDescriptorHandleForHeapStart에서 제공됩니다. 오프셋은 증분 크기 * 설명자 힙 시작에 오프셋할 설명자 수를 추가하여 수행됩니다. 애플리케이션은 핸들이 마치 메모리인 것처럼 역참조하지 않아야 하므로 증분 크기를 바이트 크기로 간주하면 안됩니다. 가리키는 메모리는 비표준 레이아웃을 포함하며 지정된 디바이스에 따라서도 달라질 수 있기 때문입니다.

GetCPUDescriptorHandleForHeapStart는 CPU 표시 설명자 힙의 CPU 핸들을 반환합니다. 설명자 힙이 CPU 표시 힙이 아닌 경우 NULL 핸들을 반환합니다(또한 디버그 계층은 오류를 보고함).

GetGPUDescriptorHandleForHeapStart는 셰이더 표시 설명자 힙의 GPU 핸들을 반환합니다. 설명자 힙이 셰이더 표시 힙이 아닌 경우 NULL 핸들을 반환합니다(또한 디버그 계층은 오류를 보고함).

예를 들어, 11on12 디바이스를 사용하여 D2D 텍스트를 표시하기 위해 렌더링 대상 뷰를 만들 수 있습니다.

    // Create descriptor heaps.
    {
        // Describe and create a render target view (RTV) descriptor heap.
        D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
        rtvHeapDesc.NumDescriptors = FrameCount;
        rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
        rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
        ThrowIfFailed(m_d3d12Device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&m_rtvHeap)));

        m_rtvDescriptorSize = m_d3d12Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
    }

    // Create frame resources.
    {
        CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());

        // Create a RTV, D2D render target, and a command allocator for each frame.
        for (UINT n = 0; n < FrameCount; n++)
        {
            ThrowIfFailed(m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n])));
            m_d3d12Device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle);

            // Create a wrapped 11On12 resource of this back buffer. Since we are 
            // rendering all D3D12 content first and then all D2D content, we specify 
            // the In resource state as RENDER_TARGET - because D3D12 will have last 
            // used it in this state - and the Out resource state as PRESENT. When 
            // ReleaseWrappedResources() is called on the 11On12 device, the resource 
            // will be transitioned to the PRESENT state.
            D3D11_RESOURCE_FLAGS d3d11Flags = { D3D11_BIND_RENDER_TARGET };
            ThrowIfFailed(m_d3d11On12Device->CreateWrappedResource(
                m_renderTargets[n].Get(),
                &d3d11Flags,
                D3D12_RESOURCE_STATE_RENDER_TARGET,
                D3D12_RESOURCE_STATE_PRESENT,
                IID_PPV_ARGS(&m_wrappedBackBuffers[n])
                ));

            // Create a render target for D2D to draw directly to this back buffer.
            ComPtr<IDXGISurface> surface;
            ThrowIfFailed(m_wrappedBackBuffers[n].As(&surface));
            ThrowIfFailed(m_d2dDeviceContext->CreateBitmapFromDxgiSurface(
                surface.Get(),
                &bitmapProperties,
                &m_d2dRenderTargets[n]
                ));

            rtvHandle.Offset(1, m_rtvDescriptorSize);

            ThrowIfFailed(m_d3d12Device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocators[n])));
        }
    
    }

최소 설명자 힙 래퍼

애플리케이션 개발자는 설명자 핸들 및 힙을 관리하기 위해 고유한 도우미 코드를 빌드하려고 할 것입니다. 기본 예제는 다음과 같습니다. 예를 들어, 보다 정교한 래퍼는 어떤 형식의 설명자가 힙의 어디에 있는지 추적하고 설명자 생성 인수를 저장할 수 있습니다.

class CDescriptorHeapWrapper
{
public:
    CDescriptorHeapWrapper() { memset(this, 0, sizeof(*this)); }

    HRESULT Create(
        ID3D12Device* pDevice, 
        D3D12_DESCRIPTOR_HEAP_TYPE Type, 
        UINT NumDescriptors, 
        bool bShaderVisible = false)
    {
        D3D12_DESCRIPTOR_HEAP_DESC Desc;
        Desc.Type = Type;
        Desc.NumDescriptors = NumDescriptors;
        Desc.Flags = (bShaderVisible ? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE : D3D12_DESCRIPTOR_HEAP_FLAG_NONE);
       
        HRESULT hr = pDevice->CreateDescriptorHeap(&Desc, 
                               __uuidof(ID3D12DescriptorHeap), 
                               (void**)&pDH);
        if (FAILED(hr)) return hr;

        hCPUHeapStart = pDH->GetCPUDescriptorHandleForHeapStart();
        hGPUHeapStart = pDH->GetGPUDescriptorHandleForHeapStart();

        HandleIncrementSize = pDevice->GetDescriptorHandleIncrementSize(Desc.Type);
        return hr;
    }
    operator ID3D12DescriptorHeap*() { return pDH; }

    D3D12_CPU_DESCRIPTOR_HANDLE hCPU(UINT index)
    {
        return hCPUHeapStart.MakeOffsetted(index,HandleIncrementSize); 
    }
    D3D12_GPU_DESCRIPTOR_HANDLE hGPU(UINT index) 
    {
        assert(Desc.Flags&D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); 
        return hGPUHeapStart.MakeOffsetted(index,HandleIncrementSize); 
    }
    D3D12_DESCRIPTOR_HEAP_DESC Desc;
    CComPtr<ID3D12DescriptorHeap> pDH;
    D3D12_CPU_DESCRIPTOR_HANDLE hCPUHeapStart;
    D3D12_GPU_DESCRIPTOR_HANDLE hGPUHeapStart;
    UINT HandleIncrementSize;
};

설명자 힙