1. サイトトップ
  2. ブログ
  3. C++
  4. 【C++】DirectX12でAliasingBarrierを活用してみたい

【C++】DirectX12でAliasingBarrierを活用してみたい

こんにちは!
情熱開発部プログラム課の柄本です。

今回はDirectX12のResourceBarrierの中のAliasingについてご紹介します。

他2つのTransitionとUAVに比べると使う機会が少ない印象が私の中ではあり、いまいち使用方法も理解できていませんでした。
参考になるサイトも少なかったので、自分で記事を書いてみました!

Aliasing Barrier とは

冒頭でも記述しましたが、DirectX12のResourceBarrierのうちの1つです。

同じメモリ領域にマッピングされた複数のリソースに対して、使用するリソースを切り替える際に使用します。

同時に使用しないことが担保されているリソースのメモリを節約することができます。特にテクスチャなどの大きいリソースに関してはかなりの効果を発揮します。

他2種のResourceBarrierも含めた詳細な説明は以下の公式ページをご覧ください。

https://docs.microsoft.com/ja-jp/windows/win32/direct3d12/using-resource-barriers-to-synchronize-resource-states-in-direct3d-12

活用

DirectX12でリソースを作成する際、メモリ管理を意識しない場合ID3D12Device::CreateCommittedResourceを使用しますが、今回は同じヒープにリソースを作成するため使用できません。

そこで以下2つのメソッドを使用します。

前者は任意のヒープを確保するもので、後者は指定のヒープにリソースを作成するものです。

この2つを使用してまずは同じヒープにリソースを作成します。
今回は解像度を変更したRenderTargetを作成して、任意に解像度を変更できるような仕組みを目指します。

なお、今回のソースはDirectX12の公式サンプルである GitHub/Microsoft/DirectX-Graphics-Samples の”D3D12HelloWorld”サンプルを改造して実行しました。

// 一番大きなテクスチャの情報を作成
D3D12_RESOURCE_DESC renderTexDesc{};
renderTexDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
renderTexDesc.Width = m_width;
renderTexDesc.Height = m_height;
renderTexDesc.MipLevels = 1;
renderTexDesc.DepthOrArraySize = 1;
renderTexDesc.SampleDesc.Count = 1;
renderTexDesc.SampleDesc.Quality = 0;
renderTexDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
renderTexDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;

// ヒープを作成
D3D12_RESOURCE_ALLOCATION_INFO info = m_device->GetResourceAllocationInfo(0, 1, &renderTexDesc);
CD3DX12_HEAP_DESC heapDesc(info, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES);
ThrowIfFailed(m_device->CreateHeap(&heapDesc, IID_PPV_ARGS(&m_shareTargetHeap)));

// レンダーターゲットを作成
for(UINT level = 0; level < LevelCount; level++)
{
    int width = m_width / (1 << level);
    int height = m_height / (1 << level);

    renderTexDesc.Width = width;
    renderTexDesc.Height = height;

    ThrowIfFailed(m_device->CreatePlacedResource(
        m_shareTargetHeap.Get(),
        0,
        &renderTexDesc,
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(&m_shareRenderTargets[level])));

}

// 最初に使用するリソースはエイリアシングバリアをかけておく
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Aliasing(nullptr, m_shareRenderTargets[0].Get()));

これでヒープの作成及びリソースの作成は完了です。

今回はテクスチャ分のヒープしか確保しないため、D3D12_RESOURCE_DESCで定められたリソース情報から、ID3D12Device::GetResourceAllocationInfo()を使用して必要な領域サイズとアライメントを計算しています。
こちらの方法ではなく任意にサイズとアライメントを指定してヒープを確保することもできます。

そして、ID3D12Device::CreatePlacedResource()でリソースを配置しています。
同じヒープ領域にリソースを配置しているので、メモリ使用量は今回作成したテクスチャのうち最大の大きさのもの一つで済んでいます。これを全て別のメモリに作ろうとするとテクスチャの数にもよりますが、かなり多くのメモリを使用しなくてはなりません。

また、注意すべき点として、ヒープが重複しているリソースのうち最初に使用するものはAliasingBarrierをかけておく必要があります。

続いて、作成したリソースを実際に利用する際のソースが以下です。

// 使用するリソースが以前と異なる場合AliasingBarrierをかける
ID3D12Resource* shareRT = m_shareRenderTargets[m_levelIndex].Get();
if(m_prevLevel != m_levelIndex)
{
    ID3D12Resource* prevResource = m_shareRenderTargets[m_prevLevel].Get();
    m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Aliasing(prevResource, shareRT));
}

// ここからは通常通りのリソースの取り扱いと同じ
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(shareRT, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_RENDER_TARGET));

CD3DX12_CPU_DESCRIPTOR_HANDLE shareRtvHandle(m_blitRtvHeap->GetCPUDescriptorHandleForHeapStart(), m_levelIndex, m_rtvDescriptorSize);
m_commandList->OMSetRenderTargets(1, &shareRtvHandle, FALSE, nullptr);

m_commandList->ClearRenderTargetView(shareRtvHandle, clearColor, 0, nullptr);
m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
m_commandList->DrawInstanced(3, 1, 0, 0);
...

基本的には通常のリソースと扱いは変わりません。

リソースの切り替えが起きるときのみ、Aliasing Barrierをかけて使用するリソースが変わることを明示的にあらわす必要があります。

以下が今回のソースを実行したものです。
解像度を変えることで三角形が荒くなっているのが確認できるかと思います。
この三角形を描画しているRenderTargetを今回は同じヒープ領域に作成していた形です。

画像の下に今回の例におけるメモリの削減結果を記載しておきます。

  • 解像度は8段階、1段階で幅と高さを2分の1する
  • 最大サイズは720×720
  • FormatはR8G8B8A8_UNORM
CreateCommittedResource(全て新規生成)21.1MB
CreatePlacedResource(ヒープ共有)15.8MB

まとめ

今回はAliasing Barrierの活用方法を説明させていただきました。

メモリを気にする場面に巡り合わないと活用するようなことにはならないですが、重ねるリソースが増えるほどかなりの効果を発揮する機能となっています。

私もこのような機会に調査してみて、その有用性に気づかされました。
拙い文章でしたが、皆さんのご参考になれば幸いです。

参考文献

以下のサイトを参考にさせていただきました。ありがとうございます。


【免責事項】

本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。