다음을 통해 공유


설명자 힙 생성하기

설명자 힙을 만들고 구성하려면 설명자 힙 유형을 선택하고, 포함된 설명자 수를 결정하고, 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_DESC 구조에 설정되며, 이 구조는 D3D12_DESCRIPTOR_HEAP_TYPE 열거형과 D3D12_DESCRIPTOR_HEAP_FLAGS 열거형을 참조합니다.

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에 전달할 수 있습니다. 참조 수 계산이 없으므로 기본 디스크립터 힙이 삭제된 후에는 애플리케이션이 핸들을 사용하지 않도록 해야 합니다.

애플리케이션은 지정된 설명자 힙 유형에 대한 설명자의 증분 크기를 파악하여, 기본 핸들부터 시작하여 설명자 힙 내의 어느 위치든지 수동으로 핸들을 생성할 수 있습니다. 애플리케이션은 증분 크기를 하드 코드하지 않아야 하며 항상 지정된 디바이스 인스턴스에 대해 쿼리해야 합니다. 그렇지 않으면 동작이 정의되지 않습니다. 또한 애플리케이션은 증분 크기 및 핸들을 사용하여 설명자 힙 데이터를 직접 검사하거나 조작해서는 안 됩니다. 그렇게 할 경우, 결과는 정의되지 않습니다. 핸들은 실제로 포인터로 사용되지 않고 포인터의 프록시로 사용하여 우발적인 역참조를 방지할 수 있습니다.

메모

헤더 d3dx12.h에 정의된 CD3DX12_GPU_DESCRIPTOR_HANDLE 도우미 구조체가 있습니다. 이 구조체는 D3D12_GPU_DESCRIPTOR_HANDLE 구조를 상속하고 초기화 및 기타 유용한 작업을 제공합니다. 마찬가지로 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;
};

설명자 힙