忍者ブログ

Memeplexes

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

C#でGPGPUチュートリアル その4 複数のリソースを計算に使う

ベクトルA + ベクトルB = 

 前回行った計算は
ひとつのバッファの数値を2倍して
もとのバッファに格納するというものでした
計算にはひとつのバッファしか使用していませんでした。

しかし現実の世界では、ひとつだけでなく
色々なデータを使って計算をするのが普通だと思います。

というわけで今回は、複数のバッファを使って計算してみましょう。

howBasicCompute11Works.jpg


3つのバッファを用意し、
2つのバッファを足した結果を
3つ目に格納します。

やっている計算はDirectX SDKのサンプル、
BasicCompute11とほぼ同じです。


複数のUnorderedAccessViewを演算シェーダにセットする : SetUnorderedAccessView()メソッド

今回は複数のバッファを使いますが、
それらすべてをUnorderedAccessViewとします。

本当は、DirectX SDKのサンプルを見ると
計算結果を格納するバッファ以外は
UnorderdAccessViewでは無くShaderResourceViewとしていて、
全部で2種類のViewを使っています。
が、ここではシンプルさを優先してUnorderedAccessViewだけを使って計算しましょう。

なお、DirectX SDKのサンプルで、
UnorderedAccessViewを必要最低限なところにしか使っていない理由は、
これが希少なものだから、というのがあるのだと思います(鵜呑みにしないでください!)。
実は、UnorderedAccessViewは"cs_4_0"では1つしか使えません
3つのバッファが登場する今回のケースでは本当はまずいのです。
今回のケースでは、3つのバッファをUnorderedAccessViewとして使うために
コンパイル時のシェーダープロファイルを"cs_5_0"にします。
"cs_5_0"の場合のUnorderedAccessViewの数の上限はなので、
今回の場合(3つ使う)も十分大丈夫です。

前回使ったComputeShader.SetUnorderedAccessView()メソッドでは
ひとつしかバッファ(のView)をセット出来ません。
今回は3つのViewをセットしなければいけないのですが、
それには前述のメソッド名の最後にsがついただけの、
ComputeShader.SetUnorderedAccessViews()メソッドを使います。

public void SetUnorderedAccessViews(
    UnorderedAccessView[] unorderedAccessViews, 
    int startSlot, 
    int count
    );

前述のメソッドとは違い引数は複数をセット出来るよう配列です。
unorderedAccessViewsはセットするUnorderedAccessViewの配列。
startSlotはセットを開始するデバイスのインデックス。
countは配列の中からセットする数です。

registerキーワード(HLSL)

さて、上のメソッドが想定していることは、
HLSL側にUnorderedAccessViewの配列のようなものがある、
ということですが、その順番はどうやって決まるのでしょうか?
それは基本的にはHLSLファイルの中で変数を宣言した順番です。
たとえば
 
RWStructuredBuffer<int> BufferSum;
RWStructuredBuffer<int> Buffer0;
RWStructuredBuffer<int> Buffer1;
とあるとき、
デバイスには3つの長さのUnorderedAccessViewの配列があり、
0番目はBufferSumで
1番目はBuffer0で
2番目はBuffer1です。

しかしこれはいかがなものでしょうか?
このままだと、たとえば変数宣言の位置を変えただけでプログラムが動かなくなってしまいます。
計算の入力に使用するバッファが計算結果の出力に使用されるかもしれません。

そういった事を防ぐには、変数の後にregister()キーワードを使います。

: register( [shader_profile], Type#[subcomponent])
shader_profileはつけるつけないは自由のオプションですが、"ps_5_0"等のシェーダープロファイルです。
Type#[subcomponent]は変数を関連付けるレジスターです。
Typeはレジスターのタイプで、subcomponentはレジスターの番号です。

以下にMSDNより表を載せます:
Type 対応するリソース
b 定数バッファ
t テクスチャとテクスチャバッファ
c Buffer offset
s Sampler
u Unordered Access View


これを使うと前述のコードはこうなります。
RWStructuredBuffer<int> BufferSum : register(u0);
RWStructuredBuffer<int> Buffer0 : register(u1);
RWStructuredBuffer<int> Buffer1 : register(u2);

これで3つの変数をUnorderedAccessViewのそれぞれ0番目、1番目、2番目として使います。
こうすると、3つの変数の配列中の位置は固定されます。
たとえ並び替えても

RWStructuredBuffer<int> Buffer0 : register(u1);
RWStructuredBuffer<int> Buffer1 : register(u2);
RWStructuredBuffer<int> BufferSum : register(u0);

これでも以前と同じように動作してくれます。


コード

コードはこのようになります。
なお、CPUからアクセスできるバッファの内部を初期化することに
意味はもはや無いので(実は前回からすでに意味はなかったのですが)
その部分のコードを削除しています。

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;

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

        bufferSum = createStructuredBuffer(device, Enumerable.Range(0, 10).ToArray());
        buffer0 = createStructuredBuffer(device, Enumerable.Range(0, 10).ToArray());
        buffer1 = createStructuredBuffer(device, Enumerable.Range(0, 10).ToArray());

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

    static void initComputeShader(Device device)
    {
        device.ImmediateContext.ComputeShader.SetUnorderedAccessViews(
            new[]
                {
                    new UnorderedAccessView(device, bufferSum),
                    new UnorderedAccessView(device, buffer0),
                    new UnorderedAccessView(device, buffer1),
                },
            0,
            3
            );

        ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "MyShader.fx",
            "MyComputeShader",
            "cs_5_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)
    {
        DataStream initialDataStream = new DataStream(initialData, true, true);
        return new Buffer(
            device,
            initialDataStream,
            new BufferDescription
            {
                SizeInBytes = (int)initialDataStream.Length,
                BindFlags = BindFlags.UnorderedAccess,
                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;
    }
}



HLSL側のファイルはこうです。
変数として宣言しているバッファが増え
演算シェーダーの中でそれを使うように変わっています。

MyShader.fx
RWStructuredBuffer<int> BufferSum : register(u0);
RWStructuredBuffer<int> Buffer0 : register(u1);
RWStructuredBuffer<int> Buffer1 : register(u2);

[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

今回やっていることはこうです:
まず「0, 1, 2, 3, 4, 5, 6, 7, 8, 9」というバッファを3つ用意します。
 
そして2つのバッファの合計
「0, 1, 2, 3, 4, 5, 6, 7, 8, 9」
                        +
「0, 1, 2, 3, 4, 5, 6, 7, 8, 9」 
を残りの1つに書き込むのです。
すると残りの一つは
「0, 2, 4, 6, 8, 10, 12, 14, 16, 18」になります。
 
それをSystem.Console.WriteLine()で書きだしています。
ですから、このような2の倍数な値が表示されているわけですね



拍手[1回]

PR