上传不同类型的资源

演示如何使用缓冲区将常量缓冲区数据和顶点缓冲区数据上载到 GPU,以及如何在缓冲区中正确地二次分配和放置数据。 使用单个缓冲区可提高内存使用灵活性,并为应用程序提供对内存使用情况的更严格控制。 还显示了 Direct3D 11 和 Direct3D 12 模型在上传不同类型的资源时的差异。

上传不同类型的资源

在 Direct3D 12 中,创建一个缓冲区来容纳用于上传的不同类型的资源数据,并针对不同的资源数据以类似的方式将资源数据复制到同一个缓冲区。 然后创建单个视图,以将这些资源数据绑定到 Direct3D 12 资源绑定模型中的图形管道。

在 Direct3D 11 中,为不同类型的资源数据创建单独的缓冲区 (记下以下 Direct3D 11 示例代码中使用的不同 BindFlags) ,将每个资源缓冲区显式绑定到图形管道,并基于不同的资源类型使用不同的方法更新资源数据。

在 Direct3D 12 和 Direct3D 11 中,应仅在 CPU 将写入数据一次且 GPU 读取数据时才使用上传资源。

在某些情况下,

  • GPU 将多次读取数据,或者
  • GPU 不会以线性方式读取数据,或者
  • 呈现已受到 GPU 的显著限制。

在这些情况下,更好的选择可能是使用 ID3D12GraphicsCommandList::CopyTextureRegionID3D12GraphicsCommandList::CopyBufferRegion 将上传缓冲区数据复制到默认资源。

默认资源可以驻留在离散 GPU 上的物理视频内存中。

代码示例:Direct3D 11

// Direct3D 11: Separate buffers for each resource type.

void main()
{
    // ...

    // Create a constant buffer.
    float constantBufferData[] = ...;

    D3D11_BUFFER_DESC constantBufferDesc = {0};  
    constantBufferDesc.ByteWidth = sizeof(constantBufferData);  
    constantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;  
    constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;  
    constantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;  

    ComPtr<ID3D11Buffer> constantBuffer;
    d3dDevice->CreateBuffer(  
        &constantBufferDesc,  
        NULL,
        &constantBuffer  
        );

    // Create a vertex buffer.
    float vertexBufferData[] = ...;

    D3D11_BUFFER_DESC vertexBufferDesc = { 0 };
    vertexBufferDesc.ByteWidth = sizeof(vertexBufferData);
    vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

    ComPtr<ID3D11Buffer> vertexBuffer;
    d3dDevice->CreateBuffer(
        &vertexBufferDesc,
        NULL,
        &vertexBuffer
        );

    // ...
}

void DrawFrame()
{
    // ...

    // Bind buffers to the graphics pipeline.
    d3dDeviceContext->VSSetConstantBuffers(0, 1, constantBuffer.Get());
    d3dDeviceContext->IASetVertexBuffers(0, 1, vertexBuffer.Get(), ...);

    // Update the constant buffer.
    D3D11_MAPPED_SUBRESOURCE mappedResource;  
    d3dDeviceContext->Map(
        constantBuffer.Get(),
        0, 
        D3D11_MAP_WRITE_DISCARD,
        0,
        &mappedResource
        );
    memcpy(mappedResource.pData, constantBufferData,
        sizeof(contatnBufferData));
    d3dDeviceContext->Unmap(constantBuffer.Get(), 0);  

    // Update the vertex buffer.
    d3dDeviceContext->UpdateSubresource(
        vertexBuffer.Get(),
        0,
        NULL,
        vertexBufferData,
        sizeof(vertexBufferData),
        0
    );

    // ...
}

代码示例:Direct3D 12

// Direct3D 12: One buffer to accommodate different types of resources

ComPtr<ID3D12Resource> m_spUploadBuffer;
UINT8* m_pDataBegin = nullptr;    // starting position of upload buffer
UINT8* m_pDataCur = nullptr;      // current position of upload buffer
UINT8* m_pDataEnd = nullptr;      // ending position of upload buffer

void main()
{
    //
    // Initialize an upload buffer
    //

    InitializeUploadBuffer(64 * 1024);

    // ...
}

void DrawFrame()
{
    // ...

    // Set vertices data to the upload buffer.

    float vertices[] = ...;
    UINT verticesOffset = 0;
    ThrowIfFailed(
        SetDataToUploadBuffer(
            vertices, sizeof(float), sizeof(vertices) / sizeof(float),
            sizeof(float), 
            verticesOffset
            ));

    // Set constant data to the upload buffer.

    float constants[] = ...;
    UINT constantsOffset = 0;
    ThrowIfFailed(
        SetDataToUploadBuffer(
            constants, sizeof(float), sizeof(constants) / sizeof(float), 
            D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT, 
            constantsOffset
            ));

    // Create vertex buffer views for the new binding model.

    D3D12_VERTEX_BUFFER_VIEW vertexBufferViewDesc = {
        m_spUploadBuffer->GetGPUVirtualAddress() + verticesOffset,
        sizeof(vertices), // size
        sizeof(float) * 4,  // stride
    };

    commandList->IASetVertexBuffers( 
        0,
        1,
        &vertexBufferViewDesc,
        ));

    // Create constant buffer views for the new binding model.

    D3D12_CONSTANT_BUFFER_VIEW_DESC constantBufferViewDesc = {
        m_spUploadBuffer->GetGPUVirtualAddress() + constantsOffset,
        sizeof(constants) // size
         };

    d3dDevice->CreateConstantBufferView(
        &constantBufferViewDesc,
        ...
        ));

    // Continue command list building and execution ...
}

//
// Create an upload buffer and keep it always mapped.
//

HRESULT InitializeUploadBuffer(SIZE_T uSize)
{
    HRESULT hr = d3dDevice->CreateCommittedResource(
         &CD3DX12_HEAP_PROPERTIES( D3D12_HEAP_TYPE_UPLOAD ),    
               D3D12_HEAP_FLAG_NONE, 
               &CD3DX12_RESOURCE_DESC::Buffer( uSize ), 
               D3D12_RESOURCE_STATE_GENERIC_READ, nullptr,  
               IID_PPV_ARGS( &m_spUploadBuffer ) );

    if (SUCCEEDED(hr))
    {
        void* pData;
        //
        // No CPU reads will be done from the resource.
        //
        CD3DX12_RANGE readRange(0, 0);
        m_spUploadBuffer->Map( 0, &readRange, &pData ); 
        m_pDataCur = m_pDataBegin = reinterpret_cast< UINT8* >( pData );
        m_pDataEnd = m_pDataBegin + uSize;
    }
    return hr;
}

//
// Sub-allocate from the buffer, with offset aligned.
//

HRESULT SuballocateFromBuffer(SIZE_T uSize, UINT uAlign)
{
    m_pDataCur = reinterpret_cast< UINT8* >(
        Align(reinterpret_cast< SIZE_T >(m_pDataCur), uAlign)
        );

    return (m_pDataCur + uSize > m_pDataEnd) ? E_INVALIDARG : S_OK;
}

//
// Place and copy data to the upload buffer.
//

HRESULT SetDataToUploadBuffer(
    const void* pData, 
    UINT bytesPerData, 
    UINT dataCount, 
    UINT alignment, 
    UINT& byteOffset
    )
{
    SIZE_T byteSize = bytesPerData * dataCount;
    HRESULT hr = SuballocateFromBuffer(byteSize, alignment);
    if (SUCCEEDED(hr))
    {
        byteOffset = UINT(m_pDataCur - m_pDataBegin);
        memcpy(m_pDataCur, pData, byteSize); 
        m_pDataCur += byteSize;
    }
    return hr;
}

//
// Align uLocation to the next multiple of uAlign.
//

UINT Align(UINT uLocation, UINT uAlign)
{
    if ( (0 == uAlign) || (uAlign & (uAlign-1)) )
    {
        ThrowException("non-pow2 alignment");
    }

    return ( (uLocation + (uAlign-1)) & ~(uAlign-1) );
}

请注意CD3DX12_HEAP_PROPERTIES和CD3DX12_RESOURCE_DESC使用帮助程序结构

常量

若要在上传或读回堆中设置常量、顶点和索引,请使用以下 API。

资源

资源是 Direct3D 概念,用于抽象 GPU 物理内存的使用。 资源需要 GPU 虚拟地址空间来访问物理内存。 资源创建是自由线程。

在 Direct3D 12 中,有三种类型的资源与虚拟地址创建和灵活性有关。

提交的资源

提交资源是 Direct3D 资源在代系中最常见的概念。 创建此类资源会分配虚拟地址范围(足以容纳整个资源的隐式堆),并将虚拟地址范围提交由堆封装的物理内存。 必须传递隐式堆属性才能将功能奇偶校验与以前的 Direct3D 版本匹配。 请参阅 ID3D12Device::CreateCommittedResource

预留的资源

保留资源等效于 Direct3D 11 平铺资源。 在创建虚拟地址时,只会分配虚拟地址范围,并且不会映射到任何堆。 应用程序稍后会将此类资源映射到堆。 此类资源的功能目前与 Direct3D 11 不同,因为它们可以使用 UpdateTileMappings 以 64KB 的磁贴粒度映射到堆。 请参阅 ID3D12Device::CreateReservedResource

放置的资源

Direct3D 12 新增功能,可以创建独立于资源的堆。 之后,可以在单个堆中查找多个资源。 无需创建平铺或保留资源即可执行此操作,从而启用应用程序可直接创建的所有资源类型的功能。 多个资源可能会重叠,必须使用 ID3D12GraphicsCommandList::ResourceBarrier 才能正确重新使用物理内存。 请参阅 ID3D12Device::CreatePlacedResource

资源大小反射

必须使用资源大小反射来了解堆中具有未知纹理布局的纹理需要多少空间。 还支持缓冲区,但主要是为了方便起见。

应注意主要对齐差异,以帮助更密集地打包资源。

例如,具有一字节缓冲区的单元素数组返回 Size 为 64KB, 对齐方式 为 64KB,因为缓冲区只能对齐 64KB。

此外,具有两个单纹素 64 KB 对齐纹理和单纹素 4 MB 对齐纹理的三元素数组根据数组顺序报告了不同的大小。 如果 4MB 对齐的纹理位于中间,则生成的 Size 为 12MB。 否则,生成的大小为 8 MB。 返回的对齐方式始终为 4MB,即资源数组中所有对齐方式的超集。

引用以下 API。

缓冲区对齐

缓冲区对齐限制与 Direct3D 11 没有变化,特别是:

  • 对于多重采样纹理,则为 4 MB。
  • 对于单采样纹理和缓冲区,则为 64 KB。