TOP > PROGRAM

GPU Upload Heaps

1 はじめに

来週からGDC 2024ですね。
今回は,Agility SDK 1.613.0 から正式機能になった GPU Upload Heaps について取り扱ってみます。
DirectX Developer Blogによると,動作させるためには Windows 11 Insider Preview Build 26080 以降が必要となるので注意してください。Windows 10 ではサポートされていないようなので予め留意してください。

GPU Upload Heaps
図 1: GPU Upload Heaps

2 GPU Upload Heapsとは?

従来までは,GPUのVRAMはCPUからアクセスできないので,PCIバス経由で大量のデータをGPUにコピーする必要がありました。アプリ開発者の視点で見ると,GPUにコピーするためだけに余分なメモリを確保しなくてはならず,またコマンドリストを介してコピー命令を発行して,コピーする必要があるため,D3D11では隠蔽されていたものが,D3D12では自前で管理せざるを得ず,そのための仕組みなども用意する必要があるので,非常に面倒くさいものでした。また,コピーするためのID3D12Resourceの生成処理や,アップロードバッファにいったんコピーするため,CPU時間も喰われるため,処理負荷の面でも問題になることがあります。
 現在のGPUにはResizable BAR(Base Address Register)というPCI Expressの機能があります。これはベースアドレスレジスタをサイズ変更可能にする機能で,これによってCPUからグラフィックスカード上のビデオメモリーの全領域にアクセスすることが可能になります。つまり,今まではいったんコピー用のリソースにCPUから書き込みして,それをGPUコマンドでGPU上のメモリーにコピーするという2Stepの処理が,1Stepで済むということを意味します。これによりアップロードリソースが不要になりますし,そのための無駄なメモリコピーも不要になり,アップロード管理機構などの仕組みも不要になります。また,不要な処理を省くことができるという意味でアプリのパフォーマンス改善にもつながります。ただ残念な点は前述したのようにDirect3D 12 を使う場合,Windows 11でしかサポートされていないという点です。Windows 10のサポート期限は 2025/10/14 までですので,あと1年ちょっとすればWindows 11に移らざるをえない状況になりますから,この問題は時間が解決してくれるはずです。
 さて,そんな便利な機能があるなら早速使いたいではありませんか!
…というわけで,次のセクションで使い方を説明していきたいと思います。

3 使い方

3.1 事前準備

まず,使うに当たってPCに刺さっているグラフィックスカードなどのハードウェアが,Resizeable BARに対応している必要があります。また,場合によってBIOSをアップデートしてResizable BARを有効にするなどの設定を行う必要があります。これについては,お使いのハードウェアを作っているメーカーのサイト等で調べて設定してください。
 つづいて,Windows 11 Insider Preview Build 26080 以降をPCにインストール必要があります。設定ウィンドウを開いて,Windows Update > Windows Insider Program からInsider Programに参加して, Preview Buildなどをインストールしてください。自分の場合は,Dev 設定にして,26080.1201のビルドをインストールしました。
 あとは,Agilith SDK 1.613.0 を使えるように設定しておいてください。導入の仕方は,Direct3D 12 ゲームグラフィックス実践ガイドのAppendix Bにも記載していますし,DirectX Developer BlogのGetting Started with the Agility SDK”を参考にすればよいかと思います。

3.2 実装

続いてD3D12のプログラム側の説明に入っていきます。
DirectX-Specsの”D3D12 GPU Upload Heaps”に書いてある手順に従って実装を行っていきますが,1個ハマりポイントがあるので後述します。

使い方 ※図は,DirectX-Specsより引用
図 2: 使い方 ※図は,DirectX-Specsより引用

まずは,D3D12_FEATURE_DATA_D3D12_OPTIONS16のGPUUploadHeapSupportedの値をチェックして,実際にGPU Upload Heapがプログラム上で使用可能かどうかを問い合わせます。実装コードは下記のような感じです。

    // GPU Upload Heap のサポートチェック.
    m_GpuUploadHeapSupported = false;
    D3D12_FEATURE_DATA_D3D12_OPTIONS16 options16 = {};
    hr = m_pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS16, &options16, sizeof(options16));
    if (SUCCEEDED(hr))
    { m_GpuUploadHeapSupported = options16.GPUUploadHeapSupported; }

    ILOG("GPU Upload Heap Support : %s", m_GpuUploadHeapSupported ? "OK" : "NG");

次に,GPU上で使用するリソースを生成します。サンプルコードでは次のようにしました。

     // ヒープタイプを選択.
    auto heapType  = (m_GpuUploadHeapSupported)
        ? D3D12_HEAP_TYPE_GPU_UPLOAD
        : D3D12_HEAP_TYPE_DEFAULT;

    // ヒーププロパティの設定.
    D3D12_HEAP_PROPERTIES prop = {};
    prop.Type                 = heapType;
    prop.CPUPageProperty      = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
    prop.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
    prop.CreationNodeMask     = 1;
    prop.VisibleNodeMask      = 1;

    // リソースの設定.
    D3D12_RESOURCE_DESC desc = {};
    desc.Dimension          = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    desc.Width              = tga.GetWidth();
    desc.Height             = tga.GetHeight();
    desc.DepthOrArraySize   = 1;
    desc.MipLevels          = 1;
    desc.Format             = DXGI_FORMAT_R8G8B8A8_UNORM;
    desc.SampleDesc.Count   = 1;
    desc.SampleDesc.Quality = 0;
    desc.Flags              = D3D12_RESOURCE_FLAG_NONE;
    desc.Layout             = D3D12_TEXTURE_LAYOUT_UNKNOWN;

    // リソースを生成.
    hr = m_pDevice->CreateCommittedResource(
        &prop,
        D3D12_HEAP_FLAG_NONE,
        &desc,
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(m_pTexture.GetAddress()) );
    if ( FAILED( hr ) )
    {
        ELOG( "Error : ID3D12Device::CreateCommittedResource() Failed." );
        return false;
    }

GPU Upload Heapが使用可能であれば,D3D12_HEAP_TYPE_GPU_UPLOADとD3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN を指定して,リソースを生成します。カスタムヒープを使いたい場合の設定は,DirectX-Specsに詳細が載っていますので,そちらを参考にしてください。
 いよいよ作成したリソースにCPUから書き込みます。DirectX-SpecsではMapしろ的なことが書いてありますが,Mapを使うと以下のようなD3D12 Errorが表示されました。

D3D12 ERROR: ID3D12Resource2::ID3D12Resource::Map: A pointer to resource data cannot be obtained the resource has an opaque memory layout or opaque strides. ppData must be NULL in order to use WriteToSubresource and ReadFromSubresource. The resource layout is D3D12_TEXTURE_LAYOUT_UNKNOWN, which is opaque [EXECUTION ERROR #832: MAP_INVALIDDATAPOINTER]

…というわけで,エラーメッセージに従いMap()ではなく,WriteToSubresource()を使って,テクセルデータ等を書き込みます。ここがハマりポイントなので注意してください。実装コードは次のようになります。

    if (m_GpuUploadHeapSupported)
    {
        D3D12_BOX dstBox = {};
        dstBox.left     = 0;
        dstBox.right    = tga.GetWidth();
        dstBox.top      = 0;
        dstBox.bottom   = tga.GetHeight();
        dstBox.front    = 0;
        dstBox.back     = 1;

        uint32_t    srcRowPitch   = tga.GetWidth () * (tga.GetBitPerPixel() / 8);
        uint32_t    srcDepthPitch = tga.GetHeight() * srcRowPitch;
        const void* srcPtr        = tga.GetPixels();

        hr = m_pTexture->WriteToSubresource(0, &dstBox, srcPtr, srcRowPitch, srcDepthPitch);
        if (FAILED(hr))
        {
            ELOG("Error : ID3D12Resource::WriteToSubresource() Failed. errcode = 0x%x", hr);
            return false;
        }
    }
    else
    {
        // サブリソースデータ設定.
        D3D12_SUBRESOURCE_DATA srcData = {};
        srcData.pData      = tga.GetPixels();
        srcData.RowPitch   = LONG_PTR(tga.GetWidth() * tga.GetBitPerPixel() / 8);
        srcData.SlicePitch = LONG_PTR(srcData.RowPitch * tga.GetHeight());

        // サブリソースの更新.
        auto ret = UpdateSubresources(
            m_pCmdList.GetPtr(),
            m_pCmdQueue.GetPtr(),
            m_pFence.GetPtr(),
            m_FenceEvent,
            m_pTexture.GetPtr(),
            0,
            1,
            &srcData);
        if ( !ret )
        {
            ELOG( "Error : UdpateSubresources() Failed." );
            return false;
        }
    }

これで,もうテクスチャデータが使える状態になっているので,あとはいつも通りShaderResourceViewなどを作って,描画してあげれば良いです。描画部分はいつも通りです。

4 おわりに

今回は,GPU Upload Heapsの機能紹介を行いました。Windows 10でも使えるようにしてくれれば,便利だから一斉にみんな使うと思うんですけどね。Microsoftの卑しい所です。

5 参考文献

6 サンプルコード

本ソースコードおよびプログラムはMIT Licenseに準じます。
プログラムの作成には,Microsoft Visual Studio Community 2022, DirectX Agility SDK 1.6130.0 を使用しています。
動作確認環境は,Windows 11 Home Insider Preview Build 26080.1201, NVIDIA GeForce RTX 3050 Laptop GPU, Driver Version 551.76 です。