さまざまな種類のリソースのアップロード

1 つのバッファーを使用して定数バッファー データと頂点バッファー データの両方を GPU にアップロードする方法と、バッファー内にデータを適切にサブ割り当ておよび配置する方法を説明します。 単一のバッファを使用すると、メモリ使用の柔軟性が向上し、アプリケーションはメモリ使用量をより厳密に制御できるようになります。 また、さまざまな種類のリソースをアップロードするための Direct3D 11 モデルと Direct3D 12 モデルの違いも示します。

さまざまな種類のリソースをアップロードする

Direct3D 12 では、アップロード用のさまざまな種類のリソース データに対応するために 1 つのバッファーを作成し、異なるリソース データに対して同様の方法で同じバッファーにリソース データをコピーします。 次に、それらのリソース データを Direct3D 12 リソース バインディング モデルのグラフィックス パイプラインにバインドするための個別のビューが作成されます。

Direct3D 11 では、異なる種類のリソース データごとに個別のバッファーを作成し (以下の Direct3D 11 サンプル コードで使用されている異なる BindFlags に注意してください)、各リソース バッファーをグラフィックス パイプラインに明示的にバインドし、異なるリソースの種類に基づいて異なる方法でリソース データを更新します。

Direct3D 12 と Direct3D 11 の両方で、CPU がデータを 1 回書き込み、GPU がそれを 1 回読み取るアップロード リソースのみを使用する必要があります。

ある場合には、

  • GPUはデータを複数回読み取るか、
  • GPUはデータを線形に読み取らない、または
  • レンダリングはすでに GPU によって大幅に制限されています。

このような場合、 ID3D12GraphicsCommandList::CopyTextureRegion または ID3D12GraphicsCommandList::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_PROPERTIESCD3DX12_RESOURCE_DESC の使用に注意してください。

定数

アップロード ヒープまたはリードバック ヒープ内の定数、頂点、インデックスを設定するには、次の API を使用します。

リソース

リソースは、GPU 物理メモリの使用を抽象化する Direct3D の概念です。 リソースには、物理メモリにアクセスするための GPU 仮想アドレス空間が必要です。 リソース作成はフリー スレッドです。

Direct3D 12 には、仮想アドレスの作成と柔軟性に関して 3 種類のリソースがあります。

コミット済みリソース

コミットされたリソースは、世代を超えて Direct3D リソースの最も一般的な概念です。 このようなリソースを作成すると、仮想アドレス範囲 (リソース全体が収まる大きさの暗黙的ヒープ) が割り当てられ、このヒープによってカプセル化される物理メモリに仮想アドレス範囲がコミットされます。 以前の Direct3D バージョンと機能的に同等にするには、暗黙的なヒープ プロパティを渡す必要があります。 「ID3D12Device::CreateCommittedResource」を参照してください。

予約済みリソース

予約済みリソースは、Direct3D 11 のタイル リソースと同等です。 作成時には仮想アドレス範囲のみが割り当てられ、ヒープにはマップされません。 そのようなリソースは、後でアプリケーションによってヒープにマップされます。 このようなリソースの機能は、 UpdateTileMappingsを使用して 64 KB のタイルの粒度でヒープ領域にマップできるため、現在 Direct3D 11 から変更されていません。 ID3D12Device::CreateReservedResourceを参照してください。

配置済みリソース

Direct3D 12 の新機能として、リソースとは別にヒープを作成できます。 その後、単一のヒープ内に複数のリソースを配置できます。 タイル化されたリソースや予約されたリソースを作成せずにこれを行うことができ、すべてのリソース タイプの機能をアプリケーションで直接作成できるようになります。 複数のリソースが重複する可能性があるため、物理メモリを正しく再利用するには、 ID3D12GraphicsCommandList::ResourceBarrier を使用する必要があります。 ID3D12Device::CreatePlacedResourceを参照してください。

リソース サイズのリフレクション

テクスチャ レイアウトが不明なテクスチャがヒープ内でどれだけのスペースを必要とするかを理解するには、リソース サイズの反映を使用する必要があります。 バッファーもサポートされますが、それは主に便宜上の理由です。

リソースをより高密度に詰め込むために、主要な配置の不一致に注意する必要があります。

たとえば、1 バイトのバッファを持つ単一要素配列は、バッファが 64 KB に揃えられるだけなので、 Size として 64 KB、 Alignment として 64 KB を返します。

また、64 KB にアラインされた 2 つの単一テクセル テクスチャと 4 MB にアラインされた 1 つの単一テクセル テクスチャを含む 3 要素の配列は、配列の順序によって異なるサイズを報告します。 4MB の整列テクスチャが中央にある場合、結果の サイズ は 12MB になります。 それ以外の場合はサイズが 8 MB になります。 返されるアライメントは常に 4MB となり、リソース配列内のすべてのアライメントのスーパーセットになります。

以下の API を参照してください。

バッファー アラインメント

バッファ アラインメントの制限は Direct3D 11 から変更されていません。特に次の点が異なります。

  • マルチサンプル テクスチャの場合は 4 MB。
  • シングルサンプル テクスチャおよびバッファーの場合は 64 KB。