忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

C#でGPGPUチュートリアル その5 入力にShaderResourceViewを使う

 ShaderResourceViewを使う意味は?

前回はGPGPUの計算に3つのバッファを使用しました。
2つのバッファを入力にして、1つのバッファに書き込んだのです。
この時に3つのバッファを全てUnorderedAccessViewにしました。

basicCompute11WithAllBuffersUnorderedAccessView.png

ところが、同じ計算をやっているDirectX SDKのサンプルでは、
入力の2つはShaderResourceViewにしています。
(出力はUnorderedAccessViewですが)

basicCompute11WithInputsShaderResourceView.png

なぜShaderResourceViewを使うのでしょうか?
あるいは言い換えるなら、
なぜUnorderedAccessViewを避けるのでしょうか?
前回も言った気がしますが、これはおそらくUnorderedAccessViewが希少な存在だからです(たぶん)。
あるいはもしかしたらパフォーマンス的な面もあるかもしれません。
たとえばUnorderedAccessViewよりもShaderResourceViewのほうが、アクセスが早いというような。

UnorderedAccessViewが希少というのは、
"cs_4_0"では1つしかUnroderedAccessViewを使えないということです。
"cs_4_0"でコンパイルしようとすると例外をスローしてしまいます。
1つしか使えないのでは、前回のように入力2つに出力ひとつという、合計3つ必要とする計算はできません。
ですから前回は"cs_5_0"を使いました。
こちらはUnorderedAccessViewを8個まで使えます
それでも8個制限のうち3つも使ってしまうというのはあまりいい気はしません。

一方、ShaderResourceViewはというと、
限度はそれほどないようです。
少なくとも16個変数を作ってコンパイルした程度ではどうということはありません。
ただしShadereResourceViewは読み込み専用で、書込み先には使えないようです。
(msdnにはShader-Resourceビューはどのように読み込まれるかを指定する、というようなことが書いてあります。)

ですから、入力はShaderResourceView、出力はUnorderedAccessView
というふうに分けて使うといいのでしょう。

ShaderResourceViewを使う(C #側)

バッファをShaderResourceViewとして使うには、
Bufferのコンストラクタ引数の中で
BindFlags.ShaderResourceを指定しておく必要があります。

そうして作ったバッファのインスタンスを引数として、
SlimDX.Direct3D11.ShaderResourceViewクラスのインスタンスを作ります。

public class ShaderResourceView : ResourceView

public ShaderResourceView(Device device, Resource resource);

deviceはこのShaderResourceViewをつくるデバイス。
resourceはこのビューを関連付けるリソースです。今回はこれはバッファです。

こうして作ったビューは、ComputeShaderWrapper.SetShaderResources()メソッドを使って
デバイスにセットします。
public void SetShaderResources(
    ShaderResourceView[] resourceViews, 
    int startSlot, 
    int count
    );

resourceViewsはセットするビューの入った配列。
startSlotはGPU側のセットする最初のインデックス。
countはresourceViewsから何個セットするかを表す個数です。


ShaderResourceViewを使う(HLSL側)

C#側でShaderResourceViewを使うと決めた場合、
HLSL側ではStructuredBuffer<T>を使うことになります。
(前回まで使っていたのはRWStructuredBuffer<T>でしたが、RWがなくなっています。
どういう事かというと、読み込みしかできなくなった、ということです。
ですから計算結果を格納するバッファは前回までのようにRWの付いた方を使います。
入力バッファをStructuredBuffer<T>にするのです。)

StructuredBuffer<T>は読み取り専門ですが、
やはり演算子[]を使うことができます。
T Operator[](in uint indexPosition);

indexPositionは[]の中のインデックスです。

また、StructuredBuffer<T>の変数には: register(t0)をつけて、
シェーダーリソースとして扱ってやります。
(実はregister(t0)などと付けてやらなくてもシェーダーリソースとして扱われるのですが)
tはテクスチャを意味します。


コード

Program.cs

using System.Collections.Generic;
using System.Linq;

using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.D3DCompiler;

class Program
{
    static Buffer bufferSum;
    static Buffer buffer0;
    static Buffer buffer1;
    const int ElementCount = 20;

    static void Main(string[] args)
    {
        Device device = new Device(DriverType.Hardware);

        bufferSum = createStructuredBuffer(
            device,
            Enumerable.Range(0, ElementCount).ToArray(),
            BindFlags.UnorderedAccess
            );
        buffer0 = createStructuredBuffer(
            device,
            Enumerable.Range(0, ElementCount).ToArray(),
            BindFlags.ShaderResource
            );
        buffer1 = createStructuredBuffer(
            device,
            Enumerable.Range(0, ElementCount).ToArray(),
            BindFlags.ShaderResource
            );

        initComputeShader(device);
        device.ImmediateContext.Dispatch(ElementCount, 1, 1);
        writeBuffer(device, bufferSum);
    }

    static void initComputeShader(Device device)
    {
        device.ImmediateContext.ComputeShader.SetUnorderedAccessView(
            new UnorderedAccessView(device, bufferSum), 0
            );
        device.ImmediateContext.ComputeShader.SetShaderResources(
            new[]
            {
                new ShaderResourceView(device, buffer0),
                new ShaderResourceView(device, buffer1),
            },
            0,
            2
            );

        ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "MyShader.fx",
            "MyComputeShader",
            "cs_4_0",
            ShaderFlags.None,
            EffectFlags.None
            );
        device.ImmediateContext.ComputeShader.Set(
            new ComputeShader(device, shaderBytecode)
            );
    }

    static void writeBuffer(Device device, Buffer buffer)
    {
        Buffer cpuAccessibleBuffer = createCpuAccessibleBuffer(device, buffer.Description.SizeInBytes);
        device.ImmediateContext.CopyResource(buffer, cpuAccessibleBuffer);
        int[] readBack = readBackFromGpu(cpuAccessibleBuffer);

        foreach (var number in readBack)
        {
            System.Console.WriteLine(number);
        }
    }

    static Buffer createStructuredBuffer(Device device, int[] initialData, BindFlags bindFlags)
    {
        DataStream initialDataStream = new DataStream(initialData, true, true);
        return new Buffer(
            device,
            initialDataStream,
            new BufferDescription
            {
                SizeInBytes = (int)initialDataStream.Length,
                BindFlags = bindFlags,
                OptionFlags = ResourceOptionFlags.StructuredBuffer,
                StructureByteStride = sizeof(int)
            }
            );
    }

    static Buffer createCpuAccessibleBuffer(Device device, int sizeInBytes)
    {
        return new Buffer(
            device,
            new BufferDescription
            {
                SizeInBytes = sizeInBytes,
                CpuAccessFlags = CpuAccessFlags.Read,
                Usage = ResourceUsage.Staging
            }
            );
    }

    static int[] readBackFromGpu(Buffer from)
    {
        DataBox data = from.Device.ImmediateContext.MapSubresource(
            from,
            MapMode.Read,
            MapFlags.None
            );
        return getArrayInt32(data.Data);
    }

    static int[] getArrayInt32(DataStream stream)
    {
        int[] buffer = new int[stream.Length / sizeof(int)];
        stream.ReadRange(buffer, 0, buffer.Length);
        return buffer;
    }
}




MyShader.fx
RWStructuredBuffer<int> BufferSum : register(u0);
StructuredBuffer<int> Buffer0 : register(t0);
StructuredBuffer<int> Buffer1 : register(t1);

[numthreads(1, 1, 1)]
void MyComputeShader(uint3 threadID : SV_DispatchThreadID )
{
    BufferSum[threadID.x] = Buffer0[threadID.x] + Buffer1[threadID.x];
}

実行するとこうなります。

0
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38


シェーダーリソースにするだけでは物足りないので
表示する数の数を2倍にしてみました。

このプログラムがやっていることは、上に書いたとおり、
2つのシェーダーリソースを作り、その中に入っている数を足しあわせ
1つのアンオーダードアクセスなバッファに書き込んでいるというものです。

basicCompute11WithInputsShaderResourceView.png



この図では0~7までですが、このプログラムでは0~19まで足しています。
なので出力は0~38になるというわけですね。


















拍手[0回]

PR