記述子ヒープの作成

記述子ヒープを作成して構成するには、記述子ヒープの種類を選択し、格納する記述子の数を決定するとともに、CPU に認識されるかシェーダーに認識されるか (またはその両方) を指定するフラグを設定する必要があります。

記述子ヒープの種類

ヒープの種類は、 D3D12_DESCRIPTOR_HEAP_TYPE 列挙型の 1 つのメンバーによって決まります。

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_HANDLEおよびD3D12_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;
};

記述子ヒープ