DirectX12の新しいBarrier

昨日、DirectX Agility SDK 1.700.10で追加された、ResourceBarrier()を刷新する新しいバリアAPI(Enhanced Barrier)が公開されました。
しかしSDKはあるもののドキュメントが見当たりません。
そこで、ヘッダの中身を見ながら考察してみました。

    ID3D12GraphicsCommandList7 : public ID3D12GraphicsCommandList6
    {
    public:
        virtual void STDMETHODCALLTYPE Barrier( 
            UINT32 NumBarrierGroups,
            _In_reads_(NumBarrierGroups)  const D3D12_BARRIER_GROUP *pBarrierGroups) = 0;
        
    };

Barrier()というAPIで指定するみたいです。
構造体を見てみましょう。

typedef struct D3D12_BARRIER_GROUP
    {
    D3D12_BARRIER_TYPE Type;
    UINT32 NumBarriers;
    union 
        {
        _In_reads_(NumBarriers)  const D3D12_GLOBAL_BARRIER *pGlobalBarriers;
        _In_reads_(NumBarriers)  const D3D12_TEXTURE_BARRIER *pTextureBarriers;
        _In_reads_(NumBarriers)  const D3D12_BUFFER_BARRIER *pBufferBarriers;
        _In_reads_(NumBarriers)  const D3D12_RESOURCE_STATE_BARRIER *pStateBarriers;
        } 	;
    } 	D3D12_BARRIER_GROUP;

4種類のバリアタイプGlobal, Texture, Buffer, Resource Stateがあるようです。
同一タイプのバリアは配列で指定できます。
各タイプの構造体を見てみましょう。

typedef struct D3D12_GLOBAL_BARRIER
    {
    D3D12_BARRIER_SYNC SyncBefore;
    D3D12_BARRIER_SYNC SyncAfter;
    D3D12_BARRIER_ACCESS AccessBefore;
    D3D12_BARRIER_ACCESS AccessAfter;
    } 	D3D12_GLOBAL_BARRIER;

まずはGlobal。SyncとAccessをペアとして、従来のResourceBarrier()と同じようにBeforeとAfterを指定するようです。
列挙子を見てみましょう。

typedef 
enum D3D12_BARRIER_SYNC
    {
        D3D12_BARRIER_SYNC_NONE	= 0,
        D3D12_BARRIER_SYNC_ALL	= 0x1,
        D3D12_BARRIER_SYNC_DRAW	= 0x2,
        D3D12_BARRIER_SYNC_INPUT_ASSEMBLER	= 0x4,
        D3D12_BARRIER_SYNC_VERTEX_SHADING	= 0x8,
        D3D12_BARRIER_SYNC_PIXEL_SHADING	= 0x10,
        D3D12_BARRIER_SYNC_DEPTH_STENCIL	= 0x20,
        D3D12_BARRIER_SYNC_RENDER_TARGET	= 0x40,
        D3D12_BARRIER_SYNC_COMPUTE_SHADING	= 0x80,
        D3D12_BARRIER_SYNC_RAYTRACING	= 0x100,
        D3D12_BARRIER_SYNC_COPY	= 0x200,
        D3D12_BARRIER_SYNC_RESOLVE	= 0x400,
        D3D12_BARRIER_SYNC_EXECUTE_INDIRECT	= 0x800,
        D3D12_BARRIER_SYNC_PREDICATION	= 0x800,
        D3D12_BARRIER_SYNC_ALL_SHADING	= 0x1000,
        D3D12_BARRIER_SYNC_NON_PIXEL_SHADING	= 0x2000,
        D3D12_BARRIER_SYNC_EMIT_RAYTRACING_ACCELERATION_STRUCTURE_POSTBUILD_INFO	= 0x4000,
        D3D12_BARRIER_SYNC_VIDEO_DECODE	= 0x100000,
        D3D12_BARRIER_SYNC_VIDEO_PROCESS	= 0x200000,
        D3D12_BARRIER_SYNC_VIDEO_ENCODE	= 0x400000,
        D3D12_BARRIER_SYNC_BUILD_RAYTRACING_ACCELERATION_STRUCTURE	= 0x800000,
        D3D12_BARRIER_SYNC_COPY_RAYTRACING_ACCELERATION_STRUCTURE	= 0x1000000,
        D3D12_BARRIER_SYNC_SPLIT	= 0x80000000
    } 	D3D12_BARRIER_SYNC;

typedef 
enum D3D12_BARRIER_ACCESS
    {
        D3D12_BARRIER_ACCESS_COMMON	= 0,
        D3D12_BARRIER_ACCESS_VERTEX_BUFFER	= 0x1,
        D3D12_BARRIER_ACCESS_CONSTANT_BUFFER	= 0x2,
        D3D12_BARRIER_ACCESS_INDEX_BUFFER	= 0x4,
        D3D12_BARRIER_ACCESS_RENDER_TARGET	= 0x8,
        D3D12_BARRIER_ACCESS_UNORDERED_ACCESS	= 0x10,
        D3D12_BARRIER_ACCESS_DEPTH_STENCIL_WRITE	= 0x20,
        D3D12_BARRIER_ACCESS_DEPTH_STENCIL_READ	= 0x40,
        D3D12_BARRIER_ACCESS_SHADER_RESOURCE	= 0x80,
        D3D12_BARRIER_ACCESS_STREAM_OUTPUT	= 0x100,
        D3D12_BARRIER_ACCESS_INDIRECT_ARGUMENT	= 0x200,
        D3D12_BARRIER_ACCESS_PREDICATION	= 0x200,
        D3D12_BARRIER_ACCESS_COPY_DEST	= 0x400,
        D3D12_BARRIER_ACCESS_COPY_SOURCE	= 0x800,
        D3D12_BARRIER_ACCESS_RESOLVE_DEST	= 0x1000,
        D3D12_BARRIER_ACCESS_RESOLVE_SOURCE	= 0x2000,
        D3D12_BARRIER_ACCESS_RAYTRACING_ACCELERATION_STRUCTURE_READ	= 0x4000,
        D3D12_BARRIER_ACCESS_RAYTRACING_ACCELERATION_STRUCTURE_WRITE	= 0x8000,
        D3D12_BARRIER_ACCESS_SHADING_RATE_SOURCE	= 0x10000,
        D3D12_BARRIER_ACCESS_VIDEO_DECODE_READ	= 0x20000,
        D3D12_BARRIER_ACCESS_VIDEO_DECODE_WRITE	= 0x40000,
        D3D12_BARRIER_ACCESS_VIDEO_PROCESS_READ	= 0x80000,
        D3D12_BARRIER_ACCESS_VIDEO_PROCESS_WRITE	= 0x100000,
        D3D12_BARRIER_ACCESS_VIDEO_ENCODE_READ	= 0x200000,
        D3D12_BARRIER_ACCESS_VIDEO_ENCODE_WRITE	= 0x400000,
        D3D12_BARRIER_ACCESS_NO_ACCESS	= 0x80000000
    } 	D3D12_BARRIER_ACCESS;

ドキュメントがないので推測ですが、Barrier Syncは同期待ちするパイプラインステージ、Barrier Accessはリソースの使用用途を指定するようです。
レガシーBarrierではSRV/UAV/CBVのステージ指定はありませんでした。
シェーダリソース系以外は、SyncとAccessで有効な組み合わせが限られそうで、なんだか冗長に感じます。
また、リソースの指定はないので、レガシーBarrierでいうnull UAVバリアやnull Aliasバリアの代わりなのかなと思います。

次にTextureバリアを見てみましょう。

typedef struct D3D12_TEXTURE_BARRIER
    {
    D3D12_BARRIER_SYNC SyncBefore;
    D3D12_BARRIER_SYNC SyncAfter;
    D3D12_BARRIER_ACCESS AccessBefore;
    D3D12_BARRIER_ACCESS AccessAfter;
    D3D12_BARRIER_LAYOUT LayoutBefore;
    D3D12_BARRIER_LAYOUT LayoutAfter;
    _In_  ID3D12Resource *pResource;
    D3D12_BARRIER_SUBRESOURCE_RANGE Subresources;
    D3D12_TEXTURE_BARRIER_FLAGS Flags;
    } 	D3D12_TEXTURE_BARRIER;

リソースとサブリソースの指定があります。
また、LayoutとTexture Barrier Flagsいう新しい列挙子も増えています。

typedef 
enum D3D12_BARRIER_LAYOUT
    {
        D3D12_BARRIER_LAYOUT_UNDEFINED	= 0xffffffff,
        D3D12_BARRIER_LAYOUT_COMMON	= 0,
        D3D12_BARRIER_LAYOUT_PRESENT	= 0,
        D3D12_BARRIER_LAYOUT_GENERIC_READ	= ( D3D12_BARRIER_LAYOUT_PRESENT + 1 ) ,
        D3D12_BARRIER_LAYOUT_RENDER_TARGET	= ( D3D12_BARRIER_LAYOUT_GENERIC_READ + 1 ) ,
        D3D12_BARRIER_LAYOUT_UNORDERED_ACCESS	= ( D3D12_BARRIER_LAYOUT_RENDER_TARGET + 1 ) ,
        D3D12_BARRIER_LAYOUT_DEPTH_STENCIL_WRITE	= ( D3D12_BARRIER_LAYOUT_UNORDERED_ACCESS + 1 ) ,
        D3D12_BARRIER_LAYOUT_DEPTH_STENCIL_READ	= ( D3D12_BARRIER_LAYOUT_DEPTH_STENCIL_WRITE + 1 ) ,
        D3D12_BARRIER_LAYOUT_SHADER_RESOURCE	= ( D3D12_BARRIER_LAYOUT_DEPTH_STENCIL_READ + 1 ) ,
        D3D12_BARRIER_LAYOUT_COPY_SOURCE	= ( D3D12_BARRIER_LAYOUT_SHADER_RESOURCE + 1 ) ,
        D3D12_BARRIER_LAYOUT_COPY_DEST	= ( D3D12_BARRIER_LAYOUT_COPY_SOURCE + 1 ) ,
        D3D12_BARRIER_LAYOUT_RESOLVE_SOURCE	= ( D3D12_BARRIER_LAYOUT_COPY_DEST + 1 ) ,
        D3D12_BARRIER_LAYOUT_RESOLVE_DEST	= ( D3D12_BARRIER_LAYOUT_RESOLVE_SOURCE + 1 ) ,
        D3D12_BARRIER_LAYOUT_SHADING_RATE_SOURCE	= ( D3D12_BARRIER_LAYOUT_RESOLVE_DEST + 1 ) ,
        D3D12_BARRIER_LAYOUT_VIDEO_DECODE_READ	= ( D3D12_BARRIER_LAYOUT_SHADING_RATE_SOURCE + 1 ) ,
        D3D12_BARRIER_LAYOUT_VIDEO_DECODE_WRITE	= ( D3D12_BARRIER_LAYOUT_VIDEO_DECODE_READ + 1 ) ,
        D3D12_BARRIER_LAYOUT_VIDEO_PROCESS_READ	= ( D3D12_BARRIER_LAYOUT_VIDEO_DECODE_WRITE + 1 ) ,
        D3D12_BARRIER_LAYOUT_VIDEO_PROCESS_WRITE	= ( D3D12_BARRIER_LAYOUT_VIDEO_PROCESS_READ + 1 ) ,
        D3D12_BARRIER_LAYOUT_VIDEO_ENCODE_READ	= ( D3D12_BARRIER_LAYOUT_VIDEO_PROCESS_WRITE + 1 ) ,
        D3D12_BARRIER_LAYOUT_VIDEO_ENCODE_WRITE	= ( D3D12_BARRIER_LAYOUT_VIDEO_ENCODE_READ + 1 ) ,
        D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_COMMON	= ( D3D12_BARRIER_LAYOUT_VIDEO_ENCODE_WRITE + 1 ) ,
        D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_GENERIC_READ	= ( D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_COMMON + 1 ) ,
        D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_UNORDERED_ACCESS	= ( D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_GENERIC_READ + 1 ) ,
        D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_SHADER_RESOURCE	= ( D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_UNORDERED_ACCESS + 1 ) ,
        D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_COPY_SOURCE	= ( D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_SHADER_RESOURCE + 1 ) ,
        D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_COPY_DEST	= ( D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_COPY_SOURCE + 1 ) ,
        D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COMMON	= ( D3D12_BARRIER_LAYOUT_DIRECT_QUEUE_COPY_DEST + 1 ) ,
        D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_GENERIC_READ	= ( D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COMMON + 1 ) ,
        D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_UNORDERED_ACCESS	= ( D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_GENERIC_READ + 1 ) ,
        D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_SHADER_RESOURCE	= ( D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_UNORDERED_ACCESS + 1 ) ,
        D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_SOURCE	= ( D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_SHADER_RESOURCE + 1 ) ,
        D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_DEST	= ( D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_SOURCE + 1 ) ,
        D3D12_BARRIER_LAYOUT_VIDEO_QUEUE_COMMON	= ( D3D12_BARRIER_LAYOUT_COMPUTE_QUEUE_COPY_DEST + 1 ) 
    } 	D3D12_BARRIER_LAYOUT;

typedef 
enum D3D12_TEXTURE_BARRIER_FLAGS
    {
        D3D12_TEXTURE_BARRIER_FLAG_NONE	= 0,
        D3D12_TEXTURE_BARRIER_FLAG_DISCARD	= 0x1
    } 	D3D12_TEXTURE_BARRIER_FLAGS;

typedef struct D3D12_BARRIER_SUBRESOURCE_RANGE
    {
    UINT IndexOrFirstMipLevel;
    UINT NumMipLevels;
    UINT FirstArraySlice;
    UINT NumArraySlices;
    UINT FirstPlane;
    UINT NumPlanes;
    } 	D3D12_BARRIER_SUBRESOURCE_RANGE;

LayoutがAccessと似ていて何が違うのかよくわかりませんが、どうもVulkanのVkImageMemoryBarrierを手本にしているように見えます。
Layoutはビットマスクではないので、指定できるのは1つだけです。
Readonly DepthとSRVを同時に使う場合はどうしたら良いのでしょうか?

また、明示的にDirect QueueかCompute Queueかを指定できるようになっています。
レガシーBarrierでは、Direct QueueでCopy系のステートに遷移すると、DMA転送を可能にするために高負荷なキャッシュフラッシュが実行されることがありました。
新しいBarrierではその問題を回避できそうです。

新しいBarrierではDiscardフラグを付与できます。
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARDの代わりでしょうか?
DirectX12に従来からあるRender Passの立ち位置が良く分からなくなってきました。

サブリソースでは範囲を指定できるようになっています。
レガシーBarrierは、1つだけか全てかのどちらかしかなく、例えばミップマップのダウンサンプルを1パスでやろうとすると、ミップマップの数だけBarrierを書く必要がありましたが、これからは1つにまとめられます。

次にBufferを見てみましょう。

typedef struct D3D12_BUFFER_BARRIER
    {
    D3D12_BARRIER_SYNC SyncBefore;
    D3D12_BARRIER_SYNC SyncAfter;
    D3D12_BARRIER_ACCESS AccessBefore;
    D3D12_BARRIER_ACCESS AccessAfter;
    _In_  ID3D12Resource *pResource;
    UINT64 Offset;
    UINT64 Size;
    } 	D3D12_BUFFER_BARRIER;

リソースの指定とオフセット、サイズが増えています。

ついに1つのバッファの部分的な読み書きが可能になりそうです。
レガシーBarrierでは、バリアがサブリソース単位になっているため、バッファは全体で単一のステートにしか遷移できず、同時に読み書きすることはできませんでした。

最後にResource Stateです。

typedef struct D3D12_RESOURCE_STATE_BARRIER
    {
    D3D12_RESOURCE_STATES State;
    _In_  ID3D12Resource *pResource;
    UINT Subresource;
    D3D12_BARRIER_SYNC Sync;
    D3D12_BARRIER_ACCESS Access;
    D3D12_BARRIER_LAYOUT Layout;
    } 	D3D12_RESOURCE_STATE_BARRIER;

他のバリアと違って、BeforeとAfterの指定がありません。
また、D3D12_RESOURCE_STATEはレガシーBarrierの列挙子です。
特殊な用途でしょうか?
いまひとつ目的が分かりません。

新しいBarrierの説明はここまでです。

最後にデバッグ関係です。

    ID3D12Debug6 : public ID3D12Debug5
    {
    public:
        virtual void STDMETHODCALLTYPE SetForceLegacyBarrierValidation( 
            BOOL Enable) = 0;
        
    };

DirectX Developer Blogsによると、レガシーBarrierと新しいBarrierは併用できるので、GPU Based Validationがどちらを使っているか判定できないので、新しいBarrierが使える環境では新しいBarrierを使っているものとしてバリデーションするそうです。
おそらくこのAPIを使うことで、どの環境でもレガシーBarrierが使われるものとしてバリデーションできるのだと思います。

取り急ぎ新しいAPIを見てみました。
何か分かったら追記するかもしれません。

コメントを残す