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


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

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

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

Тип кучи определяется одним членом перечисления 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 и предоставляет инициализацию, а также другие полезные операции. Аналогичным образом вспомогательная структура 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 возвращает дескриптор ЦП для видимой кучи дескрипторов ЦП. Он возвращает NULL-указатель (и слой отладки сообщает об ошибке), если куча дескрипторов не имеет видимость для ЦП.

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

Например, создание целевых представлений рендеринга для отображения текста 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;
};

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