Direct3D12 ROV (Rasterizer Order View) を試してみる

DirectX12 / DirectX11.3 の新機能である ROV (Rasterizer Order View) を試してみました。対応している GPU は下記の通り。新 Maxwell (GTX900) と HD Graphics 系です。ただし Intel HD Graphics は Typed UAV がありません。両方対応しているのは Maxwell 2 だけとなっています。

                          ROV   TypedUAV
----------------------------------------------------
GeForce  Maxwell 2         Y       Y      (GTX900)
GeForce  Maxwell 1         N       Y      (GTX750)
GeForce  Kepler            N       N
RADEON GCN 1.1             N       Y
RADEON GCN 1.0             N       Y
Intel HD Graphics Gen8     Y       N      (Broadwell)
Intel HD Graphics Gen7.5   Y       N      (Haswell)

より詳しい機能表は下記のページに掲載しています。

Direct3D 12 (DirectX 12) Windows 詳細

Shader は複数の Unit で同時に走るので、実行順番は保証されずバッファアクセスは競合します。しかしながら Pixel の出力自体は順序が保証されており結果は一定です。Blend や Depth/Stencil Test では同一の Draw Call で重ね書きしても正しい値で評価されます。
シェーダー内で任意に読み書きできる UAV はこのルールが適用されませんでした。ROV を使うと UAV の読み書きの整合性が取れるようになります。

(1) が通常の RenderTarget への描画です。
(2) は RenderTarget への書き込みと全く同じものを UAV にも書き込んでいます。擬似 MRT

// (1) hlsl
Texture2D       ColorMap0   : register( t0 );
SamplerState    Sampler0    : register( s0 );

struct PS_OUT {
    float4  Color   : SV_Target;
};

PS_OUT pmain( VS_OUT pin )
{
    PS_OUT  pout;
    float4  color_map= ColorMap0.Sample( Sampler0, pin.Texcoord );
    pout.Color= color_map;
    return  pout;
}
// (2) hlsl
Texture2D           ColorMap0   : register( t0 );
SamplerState        Sampler0    : register( s0 );
RWTexture2D UAVBuffer0  : register( u0 );

PS_OUT pmain( VS_OUT pin, float4 pos : SV_Position )
{
    PS_OUT  pout;
    float4  color_map= ColorMap0.Sample( Sampler0, pin.Texcoord );
    pout.Color= color_map;

    UAVBuffer0[ uint2(pos.xy) ]= color_map;
    return  pout;
}

dx12_rov_01.png

↑(2) の結果を 2つ並べて表示したのがこちらです。左が RTV (RenderTarget), 右が UAV です。使用したのは GeForce GTX960 (Maxwell2)。

↑右の UAV では重ね合わせに問題が生じています。並列度が上がるためプリミティブが小さい方が発生しやすいです。
これを ROV に変更したのが (3) です。

// (3) hlsl
Texture2D           ColorMap0   : register( t0 );
SamplerState        Sampler0    : register( s0 );
RasterizerOrderedTexture2D    UAVBuffer0  : register( u0 );

PS_OUT pmain( VS_OUT pin, float4 pos : SV_Position )
{
    PS_OUT  pout;
    float4  color_map= ColorMap0.Sample( Sampler0, pin.Texcoord );
    pout.Color= color_map;

    UAVBuffer0[ uint2(pos.xy) ]= color_map;
    return  pout;
}

dx12_rov_02.png

↑ (3) の結果です。左が RTV (RenderTarget), 右が ROV。
ROV では重ね合わせが正しく行われ RTV と見た目が同一になりました。

さらに ROV では、RenderTarget と違って値をシェーダーで読むことができます。
(4) は Shader 内で Blend したもの。

// (4) hlsl
Texture2D           ColorMap0   : register( t0 );
SamplerState        Sampler0    : register( s0 );
RasterizerOrderedTexture2D   UAVBuffer0  : register( u0 );

PS_OUT pmain( VS_OUT pin, float4 pos : SV_Position )
{
    PS_OUT  pout;
    float4  color_map= ColorMap0.Sample( Sampler0, pin.Texcoord );
    pout.Color= color_map;

    float4  prev_color= UAVBuffer0[uint2(pos.xy)];
    UAVBuffer0[uint2(pos.xy)]= prev_color * 0.7f + map * 0.7f;
    return  pout;
}

dx12_rov_03.png

↑(4) の結果。左が RTV, 右が ROV の半透明合成

ROV はかなり自由度が高く MRT の制限がほぼ無くなったような印象です。パフォーマンス等は調べてませんがかなり応用が効きそうです。

なお Intel HD Graphics では Typed UAV が使えないので、今まで使用してきた下記のコードが動きません。

RWTexture2D UAVBuffer0  : register( u0 );
RasterizerOrderedTexture2D    UAVBuffer0  : register( u0 );

一応対応してみました。UAV を R32_UINT として読み込みます。

// cpp HD Graphics
#define USE_TYPED_UAV 0
    D3D12_RESOURCE_DESC res_desc;
    flatlib::memory::MemClear( res_desc );
    res_desc.Dimension= D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    res_desc.Alignment= 0;
    res_desc.Width= width;
    res_desc.Height= height;
    res_desc.DepthOrArraySize= 1;
    res_desc.MipLevels= 1;
#if USE_TYPED_UAV
    res_desc.Format= DXGI_FORMAT_R8G8B8A8_UNORM;
#else
    res_desc.Format= DXGI_FORMAT_R8G8B8A8_TYPELESS;
#endif
    res_desc.SampleDesc.Count= 1;
    res_desc.Layout= D3D12_TEXTURE_LAYOUT_UNKNOWN;
    res_desc.Flags= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS|D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;

    D3D12_HEAP_PROPERTIES   heap;
    flatlib::memory::MemClear( heap );
    heap.Type= D3D12_HEAP_TYPE_DEFAULT;
    heap.CPUPageProperty= D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
    heap.MemoryPoolPreference= D3D12_MEMORY_POOL_UNKNOWN;

    D3D12_CLEAR_VALUE   clear_value;
    flatlib::memory::MemClear( clear_value );
    clear_value.Format= DXGI_FORMAT_R8G8B8A8_UNORM;

    iD3DDevice->CreateCommittedResource(
            &heap,
            D3D12_HEAP_FLAG_NONE,
            &res_desc,
            D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
            &clear_value, IID_PPV_ARGS(&iUAVTexture0) );

    D3D12_UNORDERED_ACCESS_VIEW_DESC    uav_desc;
    flatlib::memory::MemClear( uav_desc );
#if USE_TYPED_UAV
    uav_desc.Format= DXGI_FORMAT_R8G8B8A8_UNORM;
#else
    uav_desc.Format= DXGI_FORMAT_R32_UINT;
#endif
    uav_desc.ViewDimension= D3D12_UAV_DIMENSION_TEXTURE2D;

    UINT    uav_descriptor_size= iD3DDevice->GetDescriptorHandleIncrementSize( D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV );
    D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle= iHeapUAV->GetCPUDescriptorHandleForHeapStart();
    cpu_handle.ptr+= uav_descriptor_size * (UAV_DYNAMIC_COUNT);
    iD3DDevice->CreateUnorderedAccessView( iUAVTexture0, nullptr, &uav_desc, cpu_handle );

シェーダーは下記の通り。

// hlsl (HD Graphics)
//RWTexture2D    UAVBuffer0    : register( u0 );
RasterizerOrderedTexture2D   UAVBuffer0  : register( u0 );

PS_OUT  pmain( VS_OUT pin, float4 pos : SV_Position )
{
    PS_OUT  pout;
    float4  color_map= TextureMap0.Sample( Sampler0, pin.Texcoord );
    pout.Color= color_map;

    uint    fetch= UAVBuffer0[ uint2(pos.xy) ];
    float4  prev_color;
    prev_color.x= ((fetch>> 0) & 255) * (1.0f/255.0f);
    prev_color.y= ((fetch>> 8) & 255) * (1.0f/255.0f);
    prev_color.z= ((fetch>>16) & 255) * (1.0f/255.0f);
    prev_color.w= ((fetch>>24) & 255) * (1.0f/255.0f);
    float4  color= saturate( prev_color * 0.7f + color_map * 0.7f );
    UAVBuffer0[ uint2(pos.xy) ]= ((uint)(color.x * 255.0f) << 0)
                                |((uint)(color.y * 255.0f) << 8)
                                |((uint)(color.z * 255.0f) <<16)
                                |((uint)(color.w * 255.0f) <<24);
    return  pout;
}

これで Intel HD Graphics でも同等の結果になりました。
RADEON は残念ながら ROV 非対応です。下記は RADEON GCN 1.1 (UAV) の結果で、こちらも GeForce 同様ブロック状のノイズが生じています。

dx12_rov_04rad.png

関連エントリ
3D 低レベル API の違い Direct3D 12/Metal
Direct3D 12 GeForce GTX970 は FeatureLevel 12_1 対応、Resource Bind/Heap Tier は低い
3D 低レベル API の現状 Direct3D 12/Metal
Direct3D 12 (DirectX12) GPU と API の対応表
DirectX 12 (Direct3D 12) と GPU の対応