Поделиться через


Создание куч дескрипторов

Чтобы создать и настроить кучу дескрипторов, необходимо выбрать тип кучи дескриптора, определить, сколько дескрипторов в ней содержится, и установить флаги, указывающие, является ли он видимым для ЦП и (или) видимым шейдером.

Типы кучи дескриптора

Тип кучи определяется одним членом перечисления 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 при необходимости можно задать в куче дескриптора, чтобы указать, что он привязан к списку команд для справки шейдерами. Кучи дескрипторов, созданные без этого флага, позволяют приложениям выполнять дескрипторы в памяти ЦП перед их копированием в кучу видимого дескриптора шейдера. Но приложения также прекрасно создают дескрипторы в виде кучи видимых дескрипторов шейдера без необходимости создавать что-либо на ЦП.

Этот флаг применяется только к 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, которые работают с дескриптором или используют дескрипторы. Количество ссылок отсутствует, поэтому приложение должно убедиться, что оно не использует дескриптор после удаления базовой кучи дескриптора.

Приложения могут узнать размер приращения дескрипторов для заданного типа кучи дескриптора, чтобы они могли создавать дескрипторы в любом расположении кучи дескриптора вручную, начиная от дескриптора до базового. Приложения никогда не должны обрабатывать дескриптор жесткого кода приращение размеров и всегда запрашивать их для конкретного экземпляра устройства; В противном случае поведение не определено. Приложения также не должны использовать приращение размеров и дескрипторов для собственного изучения или обработки данных кучи дескриптора, так как результаты этого действия не определены. Дескрипторы на самом деле могут использоваться не как указатели, а как прокси-серверы для указателей, чтобы избежать случайного разыменовки.

Примечание

Существует вспомогающая структура, CD3DX12_GPU_DESCRIPTOR_HANDLE, определенная в заголовке d3dx12.h, которая наследует структуру D3D12_GPU_DESCRIPTOR_HANDLE и обеспечивает инициализацию и другие полезные операции. Аналогичным образом для структуры D3D12_CPU_DESCRIPTOR_HANDLE определяется вспомогательная структура CD3DX12_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 возвращает дескриптор ЦП для кучи видимых дескрипторов ЦП. Он возвращает дескриптор NULL (и уровень отладки сообщит об ошибке), если куча дескриптора не видна для ЦП.

GetGPUDescriptorHandleForHeapStart возвращает дескриптор GPU для кучи видимых дескрипторов шейдера. Он возвращает дескриптор NULL (и уровень отладки сообщит об ошибке), если куча дескриптора не видна.

Например, создание целевых представлений отрисовки для отображения D2D-текста с помощью устройства 11on12.

    // 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;
};

Кучи дескрипторов