忍者ブログ

Memeplexes

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

C#でOpenCL入門 チュートリアルその5 バッファ

 前回までは初期化作業が中心でした。
GPUに対して命令するオブジェクトの生成をしたのです。

今回はGPU内にメモリ領域、バッファを生成し、そこに書き込みと読み込みをしてみます。
(GPGPUではそれに付け加えてバッファの値を使った計算もしなければいけませんが、それは次回以降で)


バッファの生成

バッファの生成にはclCreateBuffer関数を使います。
この関数は、デバイス(GPU)内にメモリの領域を確保します。
CPU側で言うとnew[]のようなものですね。
参考

cl_mem clCreateBuffer(
    cl_context context,
    cl_mem_flags flags,
    size_t size,
    void *host_ptr,
    cl_int *errcode_ret
)

contextはこのバッファを作るGPUに関連付けられたコンテキスト。
flagsは生成するバッファの性質を表したビットフィールドで、以下の値を組み合わせて使います。

cl_mem_flags 解説
CL_MEM_READ_WRITE 1 << 0 メモリがGPUによって読み込まれもするし書きこまれもすることを表します。これがデフォルトです。
CL_MEM_WRITE_ONLY 1 << 1 メモリがGPUによって書きこまれはするけれど読み込まれることはないということを表します。これを指定してデータを読み込もうとしたらどうなるかは未定義です。
CL_MEM_READ_ONLY 1 << 2 メモリが読み込み専用であることを表します。これを指定してデータを書きこもうとしたらどうなるかは未定義です。
CL_MEM_USE_HOST_PTR 1 << 3 このフラグはhost_ptrがNULL出ないときに限り有効です。もしこの値が指定されたら、アプリケーションはOpenCLの実装に対してhost_ptrで指定されたメモリをこのメモリオブジェクト(バッファ)のストレージとして使用して欲しいということを意味します。
CL_MEM_ALLOC_HOST_PTR 1 << 4 CPUからアクセス可能なメモリとしてメモリを生成してほしいことを表します。
この値とCL_MEM_USE_HOST_PTRは同時に指定できません。
CL_MEM_COPY_HOST_PTR 1 << 5 このフラグはhost_ptrがNULL出ないときに限って有効です。この値が指定されたら、host_ptrに書かれたデータをコピーしてメモリがGPUに確保されます。
この値とCL_MEM_USE_HOST_PTRは同時に指定できません。
この値はCL_MEM_ALLOC_HOST_PTRと一緒に使うことができます。

sizeは確保されるバッファーのサイズです(バイト単位)。
host_ptrはバッファの初期データです。ここで確保されているデータの大きさは、sizeと同じか、それよりも大きくなければいけません。
errcode_retはこの関数のエラーコードです。NULLでも構いません。

戻り値は生成されたバッファです。
型はcl_memとあります(cl_bufferというような名前ではありません!)。
これはバッファをテクスチャなど他のメモリオブジェクトと同じように扱うための型です。
clCreateImage2D()という2次元イメージを作る関数があるのですが、その戻り値もcl_memなのです。


バッファの破棄

使い終わったバッファは最後に破棄しなくてはいけません。
バッファの破棄にはclReleaseMemObject()という関数を使います。
名前に注意してください。
clReleaseBufferではありません。
この関数はデバイスが確保したメモリ一般を解放する関数なのです。
参考

cl_int clReleaseMemObject(cl_mem memobj)

memobjは破棄するメモリオブジェクトです(バッファやイメージ2Dなど)。


バッファからの読み込み

バッファへデータをセットするは初期化時に行えます。
後からデータを読み込むにはclEnqueueReadBuffer関数を使います。
この関数はバッファだけでなくコマンドキューも必要とします。
バッファから読み込むというより、コマンドキューに「バッファから読み込め」というコマンドを放り込むイメージです。
参考

cl_int clEnqueueReadBuffer(
    cl_command_queue command_queue,
    cl_mem buffer,
    cl_bool blocking_read,
    size_t offset,
    size_t cb,
    void *ptr,
    cl_uint num_events_in_wait_list,
    const cl_event *event_wait_list,
    cl_event *event
)

command_queueは読み込みを行うコマンドキュー。
bufferは読み込みを行うバッファ。
blocking_readはこの関数がブロックするかどうかを指定します。TRUEなら、読み込みが完了するまでこの関数は終わりません。FALSEなら、この関数は非同期に実行します。その場合ptrに入っているデータは読み込まれたものだとは限りません。読み込みが完了したかどうかを確かめるにはeventで帰ってくるイベントオブジェクトを使います。
offsetはバッファオブジェクトから読み込みを始めるオフセットです(バイト単位)。
cbは読み込むサイズです(バイト単位)。
ptrは読み込み先のメモリです。
event_wait_listnum_events_in_wait_listはこの命令を実行する前に行わなければいけない命令のリストです。NULLでも構いません。
eventはこの命令のイベントを返します(イベントは、ほかの命令が実行されるタイミングの調節に使えます)。



バッファを初期化してその内容を読み取る

では以上の3つの関数を使ったプログラムを書いてみましょう。

Program.cs
using System;

class Program
{
    static void Main()
    {
        IntPtr device = getDevices(getPlatforms()[0], DeviceType.Default)[0];
        Context context = new Context(device);
        CommandQueue commandQueue = new CommandQueue(context, device);

        Buffer buffer = Buffer.FromCopiedHostMemory(context, new float[] { 1, 2, 3 });
        
        float[] readBack = new float[3];
        commandQueue.ReadBuffer(buffer, readBack);

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

    private static IntPtr[] getDevices(IntPtr platform, DeviceType deviceType)
    {
        int deviceCount;
        OpenCLFunctions.clGetDeviceIDs(platform, deviceType, 0, null, out deviceCount);
       
        IntPtr[] result = new IntPtr[deviceCount];
        OpenCLFunctions.clGetDeviceIDs(platform, deviceType, deviceCount, result, out deviceCount);
        return result;
    }


    private static IntPtr[] getPlatforms()
    {
        int platformCount;
        OpenCLFunctions.clGetPlatformIDs(0, null, out platformCount);

        IntPtr[] result = new IntPtr[platformCount];
        OpenCLFunctions.clGetPlatformIDs(platformCount, result, out platformCount);
        return result;
    }
}
 


OpenCLWrappers.cs
using System;
using System.Runtime.InteropServices;

class Context
{
    public IntPtr InternalPointer { get; private set; }

    public Context(params IntPtr[] devices)
    {
        int error;
        InternalPointer = OpenCLFunctions.clCreateContext(
            null,
            devices.Length,
            devices,
            null,
            IntPtr.Zero,
            out error
            );
    }

    ~Context()
    {
        OpenCLFunctions.clReleaseContext(InternalPointer);
    }
}

class CommandQueue
{
    public IntPtr InternalPointer { get; private set; }

    public CommandQueue(Context context, IntPtr device)
    {
        int error;
        InternalPointer = OpenCLFunctions.clCreateCommandQueue(
            context.InternalPointer,
            device,
            0,
            out error
            );
    }

    ~CommandQueue()
    {
        OpenCLFunctions.clReleaseCommandQueue(InternalPointer);
    }

    public void ReadBuffer<T>(Buffer buffer, T[] systemBuffer) where T : struct
    {
        GCHandle handle = GCHandle.Alloc(systemBuffer, GCHandleType.Pinned);

        OpenCLFunctions.clEnqueueReadBuffer(
            InternalPointer,
            buffer.InternalPointer,
            true,
            0,
            Math.Min(buffer.SizeInBytes, Marshal.SizeOf(typeof(T)) * systemBuffer.Length),
            handle.AddrOfPinnedObject(),
            0,
            IntPtr.Zero,
            IntPtr.Zero
            );

        handle.Free();
    }
}

class Buffer
{
    public IntPtr InternalPointer { get; private set; }
    public int SizeInBytes { get; private set; }

    private Buffer() { }

    ~Buffer()
    {
        OpenCLFunctions.clReleaseMemObject(InternalPointer);
    }

    public static Buffer FromCopiedHostMemory<T>(Context context, T[] initialData) where T : struct
    {
        Buffer result = new Buffer();
        result.SizeInBytes = Marshal.SizeOf(typeof(T)) * initialData.Length;

        int errorCode;
        GCHandle handle = GCHandle.Alloc(initialData, GCHandleType.Pinned);

        result.InternalPointer = OpenCLFunctions.clCreateBuffer(
            context.InternalPointer,
            MemoryFlags.CopyHostMemory,
            result.SizeInBytes,
            handle.AddrOfPinnedObject(),
            out errorCode
            );

        handle.Free();
        return result;
    }
}


OpenCLFunctions.cs
using System;
using System.Runtime.InteropServices;

static class OpenCLFunctions
{
    [DllImport("OpenCL.dll")]
    public static extern int clGetPlatformIDs(int entryCount, IntPtr[] platforms, out int platformCount);

    [DllImport("OpenCL.dll")]
    public static extern int clGetDeviceIDs(
        IntPtr platform,
        DeviceType deviceType,
        int entryCount,
        IntPtr[] devices,
        out int deviceCount
        );

    [DllImport("OpenCL.dll")]
    public static extern IntPtr clCreateContext(
        IntPtr[] properties, 
        int deviceCount,
        IntPtr[] devices,
        NotifyCallback pfnNotify,
        IntPtr userData,
        out int errorCode
        );

    [DllImport("OpenCL.dll")]
    public static extern int clReleaseContext(IntPtr context);

    [DllImport("OpenCL.dll")]
    public static extern IntPtr clCreateCommandQueue(
        IntPtr context,
        IntPtr device,
        long properties,
        out int errorCodeReturn
        );

    [DllImport("OpenCL.dll")]
    public static extern int clReleaseCommandQueue(IntPtr commandQueue);

    [DllImport("OpenCL.dll")]
    public static extern IntPtr clCreateBuffer(
        IntPtr context,
        MemoryFlags allocationAndUsage,
        int sizeInBytes,
        IntPtr hostPtr,
        out int errorCodeReturn
        );

    [DllImport("OpenCL.dll")]
    public static extern int clReleaseMemObject(IntPtr memoryObject);

    [DllImport("OpenCL.dll")]
    public static extern int clEnqueueReadBuffer(
        IntPtr commandQueue,
        IntPtr buffer,
        bool isBlocking,
        int offset,
        int sizeInBytes,
        IntPtr result,
        int numberOfEventsInWaitList,
        IntPtr eventWaitList,
        IntPtr eventObjectOut
        );
}

delegate void NotifyCallback(string errorInfo, IntPtr privateInfoSize, int cb, IntPtr userData);

enum DeviceType : long
{
    Default = (1 << 0),
    Cpu = (1 << 1),
    Gpu = (1 << 2),
    Accelerator = (1 << 3),
    All = 0xFFFFFFFF
}

enum MemoryFlags : long
{
    ReadWrite = (1 << 0),
    WriteOnly = (1 << 1),
    ReadOnly = (1 << 2),
    UseHostMemory = (1 << 3),
    HostAccessible = (1 << 4),
    CopyHostMemory = (1 << 5)
}

このプログラムはGPU内に{1, 2, 3}という値を持ったバッファを作り、それを読み込み表示しています。
実行結果はこうなります。
 
1
2
3

















拍手[0回]

PR