忍者ブログ

Memeplexes

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

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


C#でOpenCL入門 チュートリアルその2 デバイス

 OpenCLでは並列計算をしてくれるハードウェアのことをデバイスといいます。
例外はあるもののこれはまずGPUのことだと考えて間違い有りません(実はCPUでもいいのですが)。
デバイス=GPUです。

GPUで並列計算するためには、下準備としてPCに付いているGPUを列挙する必要があります。
今回はGPU、OpenCLの用語で言うとデバイスを列挙、情報を表示する方法をメモします。


プラットフォームからデバイスを取得

前回出てきたプラットフォームは、GPUのメーカーを表すようなものでした。
今回扱うデバイスは、そのメーカーの作ったGPUのことです。
プラットフォームから、デバイスを取得できるのです(生産者から製品を)。

それにはclGetDeviceIDs関数を使います。
参考

cl_int clGetDeviceIDs(
    cl_platform_id platform,
    cl_device_type device_type,
    cl_uint num_entries,
    cl_device_id *devices,
    cl_uint *num_devices
)

platformは取得するデバイスのプラットフォームです。
device_typeは取得するデバイスの種類。具体的には以下の5つから選びます。

cl_device_type 解説
CL_DEVICE_TYPE_CPU (1 << 1) CPUです。
CL_DEVICE_TYPE_GPU (1 << 2) GPUです。
CL_DEVICE_TYPE_ACCELERATOR (1 << 3) IBM CELL(PS3に使われている)などです。
CL_DEVICE_TYPE_DEFAULT (1 << 0) デフォルトのデバイスです。
CL_DEVICE_TYPE_ALL 0xFFFFFFFF 使用可能な全てのOpenCLデバイスです。


num_entriesは結果を格納する引数devicesの配列の長さです。もしdevicesがNULLでなかったら、この引数は0より大きくなければいけません。
devicesはこの関数の結果を格納します。もしこの値がNULLなら、この引数は無視されます。
num_devicesは使用可能なデバイスの数を返します。この値がNULLならば、この引数は無視されます。

戻り値
名前 解説
CL_SUCCESS 0 関数が成功しました。
CL_INVALID_PLATFORM -32 platformが無効な値です。
CL_INVALID_DEVICE_TYPE -31 device_typeが無効な値です。
CL_INVALID_VALUE -30 num_entriesが0なのにdevicesがNULLではありません。あるいは、num_devicesとdevicesが両方共NULLです。
CL_DEVICE_NOT_FOUND -1 device_typeで指定されたタイプのデバイスが存在しません。
CL_OUT_OF_RESOURCES -5 デバイスでリソースを確保するのに失敗しました。
CL_OUT_OF_HOST_MEMORY -6 メモリが足りません。


デバイスの列挙

では早速clGetDeviceIDs()を使ってみましょう。
システムにインストールされたGPU(正確にはデバイス)を列挙します。

Program.cs
using System;

class Program
{
    static void Main()
    {
        foreach (var platform in getPlatforms())
        {
            foreach (var device in getDevices(platform, DeviceType.Default))
            {
                Console.WriteLine(device);
            }
        }
    }

    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;
    }
}
 

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
        );
}

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

実行結果は、私の環境ではこうなりました:

82646520
108450576

うーん意味不明ですね。
これが意味するのはシステムにGPUが2つ付いているということです。
他の環境では数字は一つしか表示されないかもしれません。

しかし数字が表示されるだけというのはなんとも無機質です。
GPUの名前が表示されたらいいのですが…


デバイスの情報

デバイスのIDからその名前や各種情報を取得する関数があります。
それがclGetDeviceInfo()関数です。
参考

cl_int clGetDeviceInfo(
    cl_device_id device,
    cl_device_info param_name,
    size_t param_value_size,
    void *param_value,
    size_t *param_value_size_ret
)

deviceは情報を取得するデバイスです。clGetDeviceIDs関数によって返された値を使います。
param_nameは情報のタイプを表す列挙型です。膨大な種類があります。詳細は以下の表に。
param_value_sizeはparam_valueにセットするメモリのサイズです。
param_valueはこの関数の結果を格納するメモリです。
param_value_size_retは情報の実際のサイズです。NULLならば無視されます。

cl_device_info  値  
 CL_DEVICE_ADDRESS_BITS cl_uint  0x100D  
 CL_DEVICE_AVAILABLE cl_bool  0x1027  
 CL_DEVICE_COMPILER_AVAILABLE cl_bool  0x1028  
 CL_DEVICE_DOUBLE_FP_CONFIG cl_device_fp_config  0x1032  
 CL_DEVICE_ENDIAN_LITTLE cl_bool  0x1026  
 CL_DEVICE_ERROR_CORRECTION_SUPPORT cl_bool  0x1024  
 CL_DEVICE_EXECUTION_CAPABILITIES cl_device_exec_capabilities  0x1029  
 CL_DEVICE_EXTENSIONS char[]  0x1030  
 CL_DEVICE_GLOBAL_MEM_CACHE_SIZE cl_ulong  0x101E  
 CL_DEVICE_GLOBAL_MEM_CACHE_TYPE cl_device_mem_cache_type  0x101C  
 CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE cl_uint  0x101D  
 CL_DEVICE_GLOBAL_MEM_SIZE cl_ulong  0x101F  
 CL_DEVICE_HALF_FP_CONFIG cl_device_fp_config  0x1033  
 CL_DEVICE_HOST_UNIFIED_MEMORY cl_bool  0x1035  
 CL_DEVICE_IMAGE_SUPPORT cl_bool  0x1016  
 CL_DEVICE_IMAGE2D_MAX_HEIGHT size_t  0x1012  
 CL_DEVICE_IMAGE2D_MAX_WIDTH size_t  0x1011  
 CL_DEVICE_IMAGE3D_MAX_DEPTH size_t  0x1015  
 CL_DEVICE_IMAGE3D_MAX_HEIGHT size_t  0x1014  
 CL_DEVICE_IMAGE3D_MAX_WIDTH size_t  0x1013  
 CL_DEVICE_LOCAL_MEM_SIZE cl_ulong  0x1023  
 CL_DEVICE_LOCAL_MEM_TYPE cl_device_local_mem_type  0x1022  
 CL_DEVICE_MAX_CLOCK_FREQUENCY cl_uint  0x100C  
 CL_DEVICE_MAX_COMPUTE_UNITS cl_uint  0x1002  
 CL_DEVICE_MAX_CONSTANT_ARGS cl_uint  0x1021  
 CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE cl_ulong  0x1020  
 CL_DEVICE_MAX_MEM_ALLOC_SIZE cl_ulong  0x1010  
 CL_DEVICE_MAX_PARAMETER_SIZE size_t  0x1017  
 CL_DEVICE_MAX_READ_IMAGE_ARGS cl_uint  0x100E  
 CL_DEVICE_MAX_SAMPLERS cl_uint  0x1018  
 CL_DEVICE_MAX_WORK_GROUP_SIZE size_t  0x1004  
 CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS cl_uint  0x1003  
 CL_DEVICE_MAX_WORK_ITEM_SIZES size_t[]  0x1005  
 CL_DEVICE_MAX_WRITE_IMAGE_ARGS cl_uint  0x100F  
 CL_DEVICE_MEM_BASE_ADDR_ALIGN cl_uint  0x1019  
 CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE cl_uint  0x101A  
 CL_DEVICE_NAME char[]  0x102B  
 CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR cl_uint  0x1036  
 CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT cl_uint  0x1037  
 CL_DEVICE_NATIVE_VECTOR_WIDTH_INT cl_uint  0x1038  
 CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG cl_uint  0x1039  
 CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT cl_uint  0x103A  
 CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE cl_uint  0x103B  
 CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF cl_uint  0x103C  
 CL_DEVICE_OPENCL_C_VERSION char[]  0x103D  
 CL_DEVICE_PLATFORM  cl_platform_id  0x1031  
 CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR  cl_uint  0x1006  
 CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT  cl_uint  0x1007  
 CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT  cl_uint  0x1008  
 CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG  cl_uint  0x1009  
 CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT  cl_uint  0x100A  
 CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE  cl_uint  0x100B  
 CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF cl_uint  0x1034  
 CL_DEVICE_PROFILE char[]   0x102E  
 CL_DEVICE_PROFILING_TIMER_RESOLUTION size_t   0x1025  
 CL_DEVICE_QUEUE_PROPERTIES cl_command_queue_properties   0x102A  
 CL_DEVICE_SINGLE_FP_CONFIG cl_device_fp_config   0x101B  
 CL_DEVICE_TYPE cl_device_type   0x1000  
 CL_DEVICE_VENDOR  char[]  0x102C  
 CL_DEVICE_VENDOR_ID  cl_uint  0x1001  
 CL_DEVICE_VERSION  char[]  0x102F  
 CL_DRIVER_VERSION  char[]  0x102D  

戻り値
名前 解説
CL_SUCCESS 0 関数は成功しました
CL_INVALID_DEVICE -33 deviceが無効な値です。
CL_INVALID_VALUE -30 param_nameが無効な値であるか、param_value_sizeが小さすぎ、param_valueがNULLではありません。
CL_OUT_OF_RESOURCES -5 デバイスでリソースを確保するのに失敗しました。
CL_OUT_OF_HOST_MEMORY -6 メモリが足りません。




デバイス名の列挙

先程はデバイスのIDを列挙しました。
ここではデバイスの名前を列挙します。

Program.cs
using System;

class Program
{
    static void Main()
    {
        foreach (var platform in getPlatforms())
        {
            foreach (var device in getDevices(platform, DeviceType.Default))
            {
                Console.WriteLine(getDeviceInfo(device, DeviceInfoString.Name));
            }
        }
    }

    private static string getDeviceInfo(IntPtr device, DeviceInfoString deviceInfoName)
    {
        int valueSize;
        OpenCLFunctions.clGetDeviceInfo(device, deviceInfoName, 0, null, out valueSize);
        System.Text.StringBuilder result = new System.Text.StringBuilder(valueSize);
        OpenCLFunctions.clGetDeviceInfo(device, deviceInfoName, valueSize, result, out valueSize);
        return result.ToString();
    }

    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;
    }
}
 


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 int clGetDeviceInfo(
        IntPtr device, 
        DeviceInfoString paramName, 
        int paramValueSize,
        System.Text.StringBuilder paramValue,
        out int paramValueSizeReturn
        );
    
}

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


enum DeviceInfoString
{
    Name = 0x102B,
    Vendor = 0x102C,
    DriverVersion = 0x102D,
    Profile = 0x102E,
    Version = 0x102F,
    Extensions = 0x1030
}


このプログラムはPCに付いているGPUの名前を列挙します。
環境によって実行結果は異なります。
私の環境ではこうなりました:
 
GeForce GTX 260
Juniper
 
GeForce GTX 260というのはNVIDIA社製のGPUの名前です。
JuniperというのはAMDのGPU、Radeon HD 5700シリーズの通称です。


拍手[2回]

PR

C#でOpenCL入門 チュートリアルその1 プラットフォーム

 ググッてみたところOpenCLの入門というかサンプルは数が少ないようなので
自分で書いたコードをメモしておきます。

OpenCLはGPUを使って並列計算をしようというフレームワークです。
今まで3DCGを描くことくらいにしか使っていなかったハードウェアを、並列計算にも応用してやろうというあれです。
類似の技術にMSのコンピュートシェーダーやATI Stream、NVIDIA CUDAがあります。
このうちコンピュートシェーダーはこのブログでも以前取り上げましたね。

OpenCLはクロノスグループという団体が関わっているようです。
C言語やC++でのインターフェースが決められています。
MSではないので、当然のごとくC#からは簡単には使えません。
P/Invokeを通して使う必要があります。
せっかくなのでここではC#から使ってみます。

インストール

いくつかのメーカーからのSDKがあるとおもうのですが、ここでは例としてAMDのものを紹介します。
amdPage.JPG

Overviewの下の方にdownload hereというのがあるのでそこをクリック

amdDownloadPage.JPG
ダウンロードのリンクが出てきます。
ダウンロードした後はインストーラを起動します。


プラットフォーム

今回はプラットフォームをプログラムから操作する方法をメモします。
GPUを動かして計算するのはまた次回以降で。

OpenCLを使うには、GPUが必要です。
正確に言うとCPUでもいいのですが、普通はGPUです。
GPUのメーカーがOpenCLに対応した環境を用意しています。
それがOpenCLで言うプラットフォームです。
次のようなものがあります。

NVIDIA CUDA
AMD Accelerated Parallel Processing


NVIDIAのGPUを使っている人は「NVIDIA CUDA」というプラットフォームがあるでしょう(インストールすれば)。
AMDのGPUを使っている人は「AMD Accelerated Parallel Processing」というプラットフォームがあるでしょう(インストールすれば)。
私は一つのパソコンに2つ両方を使っているので(これは本当はいけないことなのかもしれませんが)、上の2つが両方共ありました。

どういうプラットフォームがインストールされているのか確かめる方法ですが、それにはプログラムから調べます。
(もっと簡単な方法もありそうな気もしますが)

インストールされたプラットフォームは、clGetPlatformIDs関数を使って取得します。
参考

cl_int clGetPlatformIDs(
    cl_uint num_entries,
    cl_platform_id *platforms,
    cl_uint *num_platforms
    );

cl_XXXというのはOpenCLが定義している型です。
cl_は無視してXXXだけ見ると意味が取れます。
たとえばcl_intは実はただのintです。

引数は3つあります。
num_entriesはplatforms配列の長さ。platformsがNULLでないならば、この値は0より大でなければいけません。
platformsは、取得するプラットフォームを格納する配列。NULLならば無視されます。必要な配列の長さはnum_platformsで取得できます。
num_platformsは、存在するプラットフォームの数を返します。NULLならば無視されます。

戻り値は3つのうちの一つです。

名前 数値 説明
CL_SUCCESS 0 関数が成功しました。
CL_INVALID_VALUE -30 関数が失敗しました。num_entriesが0で、platformsがNULLではありません。あるいはnum_platformsとplatformsの両方がNULLです。
CL_OUT_OF_HOST_MEMORY -6 メモリが足りません。



プラットフォームの個数を取得する

まずはclGetPlatformIDsの簡単な使い方から見てみましょう。
次のようにすると、パソコンにインストールされているOpenCLプラットフォームの個数を取得できます。

Program.cs
using System;

class Program
{
    static void Main()
    {
        int platformCount = 0;
        OpenCLFunctions.clGetPlatformIDs(0, null, out platformCount);
        Console.WriteLine(platformCount);
    }
}
 

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);
}
 

実行結果は環境によって異なります。
普通は「1」でしょうが、私の環境では「2」になりました。
NVIDIAとAMDのGPU両方がついており、両方の環境がインストールされているからです。
(「1」が普通です。「2」にならないからと言って心配しなくても構いません)


プラットフォームを取得する

プラットフォーム(のID)そのものを取得するには次のようにします。
上で取得した個数を元に、その長さの配列を用意し、それを引数として渡すのです。

Program.cs
using System;

class Program
{
    static void Main()
    {
        foreach (var platform in getPlatforms())
        {
            Console.WriteLine(platform);
        }
    }

    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;
    }
}
 

実行結果は

111875472
125673668

でした。
意味がわかりませんね。
IntPtrの値をそのまま出力しているからです。

この値はプラットフォームのIDです。
このIDを通して、他の関数を使ってプラットフォームの意味のある情報を取得することが出来ます。

くどいようですが、普通の環境では数字は1つしか表示されないはずです。
プラットフォームは普通一つしかインストールしないからです(たぶん)。


プラットフォームの情報

上の例が意味不明なのは、IDをそのまま表示しているからです。
意味があるものにするには、たとえばプラットフォームの名前が表示されると嬉しいですね。
プラットフォームの情報を取得する必要があります。
そのための関数が、clGetPlatformInfo関数です。
参考

cl_int clGetPlatformInfo(
    cl_platform_id platform,
    cl_platform_info param_name,
    size_t param_value_size,
    void *param_value,
    size_t *param_value_size_ret
)

platformはプラットフォームのIDです。clGetPlatformIDsを使って取得したIDです。この値はNULLでもいいようですが、NULLの場合の振る舞いは実装に依存します。
param_nameは列挙型の値で、この関数で取得するプラットフォームの情報のタイプを表します。どんな値を取れるかは下のテーブルでに書いておきます。
param_value_sizeはparam_valueで指されているメモリのサイズ(バイト単位)を表します。
param_valueはプラットフォームの情報を返します。この値はNULLならば無視されます。
param_value_size_retは、プラットフォームの情報のサイズを返します(param_valueのメモリのサイズです。)。NULLならば無視されます。

cl_platform_info 解説
CL_PLATFORM_PROFILE 0x0900  OpenCLのプロファイル文字列です。実装によってサポートされているプロファイル名を表します。この値は次のうちのどれか一つになります。

FULL_PROFILEはOpenCLの仕様を実装が満たしていることを意味します。

EMBEDDED_PROFILEは、実装がOpenCLのサブセットを満たしているという意味です。
CL_PLATFORM_VERSION 0x0901  OpenCLのバージョンです。
CL_PLATFORM_NAME 0x0902  プラットフォームの名前です。
CL_PLATFORM_VENDOR 0x0903  ベンダー名です。
CL_PLATFORM_EXTENSIONS 0x0904  拡張名のリストを返します。それぞれの拡張はスペースで区切られています。

戻り値
名前 数値 解説
CL_SUCCESS 0 関数が成功しました。
CL_INVALID_PLATFORM -32 platformが有効な値ではありません。
CL_INVALID_VALUE -30 param_nameが有効な値でないか、あるいはparam_value_sizeが戻り値の型のサイズより小さくparam_valueがNULLではありません。
CL_OUT_OF_HOST_MEMORY -6 メモリが足りません。

プラットフォームの名前を列挙

ではclGetPlatformInfo関数を使ってプラットフォームの名前を列挙してみましょう。
次のようなプログラムになります。

Program.cs
using System;

class Program
{
    static void Main()
    {
        foreach (var platform in getPlatforms())
        {
            Console.WriteLine(getPlatformInfo(platform, PlatformInfo.Name));
        }
    }

    private static string getPlatformInfo(IntPtr platform, PlatformInfo platformInfo)
    {
        int parameterValueSize;
        OpenCLFunctions.clGetPlatformInfo(
            platform,
            platformInfo,
            0,
            null,
            out parameterValueSize
            );

        System.Text.StringBuilder result = new System.Text.StringBuilder(parameterValueSize);
        OpenCLFunctions.clGetPlatformInfo(
            platform,
            platformInfo,
            parameterValueSize,
            result,
            out parameterValueSize
            );
        return result.ToString();
    }

    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;
    }
}
 

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 clGetPlatformInfo(
        IntPtr platform,
        PlatformInfo parameterName,
        int parameterValueSize,
        System.Text.StringBuilder parameterValue,
        out int parameterValueSizeReturn
        );
}

enum PlatformInfo
{
    Profile = 0x0900,
    Version = 0x0901,
    Name = 0x0902,
    Vendor = 0x0903,
    Extensions = 0x0904
}
 

このプログラムはPCにインストールされているOpenCLのプラットフォームを列挙していきます。
私の環境では実行結果はこうなりました:

NVIDIA CUDA
AMD Accelerated Parallel Processing

くどいようですが、普通はこの2つのうちのどちらか片方だけが表示されるのではないかと思います。
2つの違うメーカーのGPUをPCに付けているときにはこのように2つ表示されますが、そうでないときには1つだけでしょう(たぶん)。

拍手[8回]


C#でDirectX11入門一覧 (SlimDX入門)

 まとめ

Direct3D 11入門

01.メッセージループ
02.デバイスの初期化
03.背景のクリア
04.三角形の表示(色なし)クラス紹介編
05.色つき三角形
06.動く三角形
07.カメラ(遠近感+回転)
08.平行移動
09.深度バッファ
10.ライト
11.テクスチャ
12.ワイヤーフレーム
13.ジオメトリシェーダー
14.定数バッファー

コンピュートシェーダー

01.GPUからCPU側へのデータ転送
02.バッファのコピー
03.コンピュートシェーダー
04.複数のリソースを計算に使う
05.ShaderResourceViewを使う
06.BarrierWithGroupSync()



感想

Direct3D11入門は、XNAやDirectXに関する知識がほとんど全くない人向けに書きました。
が、しかし効果の程は怪しいですね。
三角形を描くだけでかなりの設定が必要で、XNAなどで予備知識がないと厳しい気がしなくもありません。
はじめに大きな乗り越えなければいけない壁がDirect3D9世代よりもぐんと高くなっている気がします。

最大の難関は04.三角形の表示(色なし)です。
白い三角形を描くだけなのに200行とちょっと必要というのは初学者の心をぽっきり折るのに十分だと思います。
XNAのありがたみがひしひしと感じられます。










拍手[3回]


C#でDirectX11 SlimDXチュートリアルその13 ジオメトリシェーダー

 今回のテーマはジオメトリシェーダーです。
ジオメトリシェーダーはポリゴンを分割します。

d4d15310.jpg



Tutorial13GeometryShader.jpg


上はジオメトリシェーダーなし、下はジオメトリシェーダー有りです。
上と下の頂点バッファは同じです。
違うのはHLSLのエフェクトファイルだけ。
頂点バッファの中には三角形1つ分の頂点しか入っていないのですが、ジオメトリシェーダーで分割されて2つになるのです。

こう言うことをして何が嬉しいかというと、例えば荒い3Dモデルを面の細やかなモデルに変換することが出来ます。
だったら最初から細かいモデルを用意しておけという感じですが、実際には遠くから見たときに細かいモデルを用意するのは無駄です。
近くにあるときは細かいモデル、遠くにあるときには荒いモデル、その中間ならそこそこのモデルが欲しいのです。
これら全部を用意するという方法もありますが(面倒な上容量をけっこう食いそうです)、一度に全部の距離に柔軟に対応できればそれに越したことはありません。

そういうニーズにジオメトリシェーダーは答えることが出来ます。
最初に荒いモデルを用意しておいて、必要に応じて適切な細かさのモデルを作ればいいのです。
ジオメトリシェーダーでポリゴンを分割すればそれだけ細かいモデルになります。
もちろんただ分割するだけでは意味が無いので、予めテクスチャに描いた細かい凹凸情報でポリゴンの高低を変えればいいというわけです。


ジオメトリシェーダー(HLSL)

今回ジオメトリシェーダーを使う上で必要なのはHLSL側の変更だけです。
C#側は前回のワイヤーフレームを描くコードから変える必要はありません。
今回、HLSLのジオメトリシェーダーは次のような構文となります:

[maxvertexcount(VertexCount)]
void ShaderName(
    triangle DataType vertices[3],
    inout TriangleStream<DataType> resultStream
    )


maxvertexcountはC#の属性のようなものですね。
ジオメトリシェーダーによって造られる頂点の最大数を指定します。
作られるというと語弊がありそうです。
3の頂点から6つの頂点を作るとき(1つの三角形を分割して2つの三角形にするとき)、VertexCountは6です。

 DataTypeは頂点の構造体です。

TriangleStream<T>はジオメトリシェーダーで作成される三角形を格納します。
今回は三角形なのでTriangleStreamなのですが、他にもPointStreamやLineStreamなどの仲間もあります
TriangleStreamには、ジオメトリシェーダーで作成する三角形を格納するためのメソッドが付いています。

class TriangleStream<T>
{
      void Append(T vertex);
      void RestartStrip();
}

TriangleStream<T>.Append()メソッドは頂点を追加します。
TriangleStream<T>.RestartStrip()メソッドは、Appendで3つの頂点を追加した後に呼びます。


コード

Program.cs
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.D3DCompiler;
 
class Program
{
    static void Main()
    {
        using (Game game = new MyGame())
        {
            game.Run();
        }
    }
}
 
class MyGame : Game
{
    Effect effect;
    InputLayout vertexLayout;
    Buffer vertexBuffer;
 
    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0, 0, 1)
            );
 
        initTriangleInputAssembler();
        drawTriangle();
        
        SwapChain.Present(0, PresentFlags.None);
    }
 
    private void drawTriangle()
    {
        effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
        GraphicsDevice.ImmediateContext.Draw(3, 0);
    }
 
    private void initTriangleInputAssembler()
    {
        GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
        GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
            0,
            new VertexBufferBinding(vertexBuffer, sizeof(float) * 3, 0)
            );
        GraphicsDevice.ImmediateContext.InputAssembler.PrimitiveTopology
            = PrimitiveTopology.TriangleList;
    }
 
    protected override void LoadContent()
    {
        initEffect();
        initVertexLayout();
        initVertexBuffer();
        initWireframeRasterizerState();
    }
 
    private void initEffect()
    {
        using (ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "myEffect.fx", "fx_5_0",
            ShaderFlags.None,
            EffectFlags.None
            ))
        {
            effect = new Effect(GraphicsDevice, shaderBytecode);
        }
    }
 
    private void initVertexLayout()
    {
        vertexLayout = new InputLayout(
            GraphicsDevice,
            effect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
            new[] { 
                    new InputElement
                    {
                        SemanticName = "SV_Position",
                        Format = Format.R32G32B32_Float
                    }
                }
            );
    }
 
    private void initVertexBuffer()
    {
        vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
            GraphicsDevice,
            new[] {
                new SlimDX.Vector3(0, 0.5f, 0),
                new SlimDX.Vector3(0.5f, 0, 0),
                new SlimDX.Vector3(-0.5f, 0, 0),
            });
    }
 
    private void initWireframeRasterizerState()
    {
        var desc = new RasterizerStateDescription
        {
            CullMode = CullMode.Back,
            FillMode = FillMode.Wireframe,
        };
 
        GraphicsDevice.ImmediateContext.Rasterizer.State
            = RasterizerState.FromDescription(
                GraphicsDevice,
                desc
            );
    }
 
    protected override void UnloadContent()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.State.Dispose();
        effect.Dispose();
        vertexLayout.Dispose();
        vertexBuffer.Dispose();
    }
}
 
class Game : System.Windows.Forms.Form
{
    public SlimDX.Direct3D11.Device GraphicsDevice;
    public SwapChain SwapChain;
    public RenderTargetView RenderTarget;
 
 
    public void Run()
    {
        initDevice();
        SlimDX.Windows.MessagePump.Run(this, Draw);
        disposeDevice();
    }
 
    private void initDevice()
    {
        MyDirectXHelper.CreateDeviceAndSwapChain(
            this, out GraphicsDevice, out SwapChain
            );
 
        initRenderTarget();
        initViewport();
 
        LoadContent();
    }
 
    private void initRenderTarget()
    {
        using (Texture2D backBuffer
            = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
            )
        {
            RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
            GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(RenderTarget);
        }
    }
 
    private void initViewport()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.SetViewports(
            new Viewport
            {
                Width = ClientSize.Width,
                Height = ClientSize.Height,
            }
            );
    }
 
    private void disposeDevice()
    {
        UnloadContent();
        RenderTarget.Dispose();
        GraphicsDevice.Dispose();
        SwapChain.Dispose();
    }
 
    protected virtual void Draw() { }
    protected virtual void LoadContent() { }
    protected virtual void UnloadContent() { }
}
 
class MyDirectXHelper
{
    public static void CreateDeviceAndSwapChain(
        System.Windows.Forms.Form form,
        out SlimDX.Direct3D11.Device device,
        out SlimDX.DXGI.SwapChain swapChain
        )
    {
        SlimDX.Direct3D11.Device.CreateWithSwapChain(
            DriverType.Hardware,
            DeviceCreationFlags.None,
            new SwapChainDescription
            {
                BufferCount = 1,
                OutputHandle = form.Handle,
                IsWindowed = true,
                SampleDescription = new SampleDescription
                {
                    Count = 1,
                    Quality = 0
                },
                ModeDescription = new ModeDescription
                {
                    Width = form.ClientSize.Width,
                    Height = form.ClientSize.Height,
                    RefreshRate = new SlimDX.Rational(60, 1),
                    Format = Format.R8G8B8A8_UNorm
                },
                Usage = Usage.RenderTargetOutput
            },
            out device,
            out swapChain
            );
    }
 
    public static Buffer CreateVertexBuffer(
        SlimDX.Direct3D11.Device graphicsDevice,
        System.Array vertices
        )
    {
        using (SlimDX.DataStream vertexStream 
            = new SlimDX.DataStream(vertices, true, true))
        {
            return new Buffer(
                graphicsDevice,
                vertexStream,
                new BufferDescription
                {
                    SizeInBytes= (int)vertexStream.Length,
                    BindFlags = BindFlags.VertexBuffer,
                }
                );
        }
    }
}
 
 

myEffect.fx
struct VertexPosition
{
float4 Position : SV_Position;
};

VertexPosition MyVertexShader(VertexPosition position)
{
    return position;
}

VertexPosition Average(VertexPosition a, VertexPosition b)
{
VertexPosition result = { (a.Position + b.Position) / 2};
return result;
}

[maxvertexcount(6)]
void MyGeometryShader(
triangle VertexPosition vertices[3],
inout TriangleStream<VertexPosition> resultStream
)
{
resultStream.Append(vertices[0]);
resultStream.Append(vertices[1]);
resultStream.Append(Average(vertices[1], vertices[2]));
resultStream.RestartStrip();

resultStream.Append(vertices[0]);
resultStream.Append(Average(vertices[1], vertices[2]));
resultStream.Append(vertices[2]);
resultStream.RestartStrip();
}

float4 MyPixelShader() : SV_Target
{
    return float4(1, 1, 1, 1);
}

technique10 MyTechnique
{
pass MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetGeometryShader(CompileShader(gs_5_0, MyGeometryShader()));
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}

実行結果はこうなります。

Tutorial13GeometryShader.jpg

このプログラムはまず三角形を一つだけ用意しています。
 
Tutorial13HowTriangleIsDivided.jpg

それを真ん中で2等分しているのです。
 
Tutorial13HowTriangleIsDivided2.jpg

今回は単純化のため縦に2等分しましたが、実際には
 △
△▽△
というような分割方法を使うべきでしょう。




























拍手[2回]


C#でDirectX11 SlimDXチュートリアルその12 ワイヤーフレーム

 今回はワイヤーフレームです。
ワイヤーフレームとは何かというと、
こんな三角形を

Tutorial03WhiteTriangle.jpg

こう表示することです:

Tutorial12WireFrame.jpg


図形の骨組みだけ表示されるのです。
上の三角形で言うと、3つの辺だけが描かれ、その内部は描かれません。

これは一体何に使うのか?
ということですが、ポリゴンの様子がよくわかるということで、デバッグ用といいますか、DirectXのポリゴンの分割サンプルにはよく使われているようです。

SubD11WithoutWireframe.jpg

SubD11WithWireframe.jpg

上は普通のモデルだけ描画、下は普通のモデル+そのワイヤーフレームを描画しています。
こうしてみるとポリゴンがどんな様子なのか一目瞭然です。(このサンプルはポリゴンの分割のデモなので、ポリゴンが見えると嬉しいのです)

あるいはレトロSF風味でカッコイイということもあるかもしれませんね。

ラスタライザーステート

ワイヤーフレームを描くには、デバイスをワイヤーフレームがオンになった状態にしてやります。
その状態管理を行うのが、RasterizerWrapper.Stateプロパティです。
GraphicsDevice.ImmediateContext.Rasterizer.State = ~~というふうに設定します。

public RasterizerState State { get; set; }

SlimDX.Direct3D11.RasterizerStateクラスはデバイスの描画設定をつかさどります。
今回のようにワイヤーフレームをオンにしたい場合は、そういう設定をしたこのクラスのインスタンスをデバイスにセットします。
初期化にはRasterizerState.FromDescriptionメソッドを使います。

public class RasterizerState : DeviceChild
{
    public static RasterizerState FromDescription(
        Device device, 
        RasterizerStateDescription description
        );
...
}

deviceはRasterizerStateを作るデバイス。
descriptionはRasterizerStateが設定するデバイスの描画設定です。つまり今回の設定はdescriptionに対してセットします。

SlimDX.Direct3D11.RasterizerStateDescription構造体のメンバのうち、今回セットする必要があるのはCullModeFillModeプロパティです。

public struct RasterizerStateDescription
{
    public CullMode CullMode { get; set; }
    public FillMode FillMode { get; set; }
...
}

CullModeは三角形が描かれなくする方向です。
三角形ポリゴンには方向があります。
FrontとBackです(CullMode.FrontCullMode.Back)。
この方向はIsFrontCounterclockwiseプロパティ(今回は使いませんが)によって時計回りか反時計回りかがセットされるのです。
CullModeプロパティによってセットされた方向では三角形が見えなくなります。
これはより近くのポリゴンによって隠れてしまうポリゴンはどうせ描かれないので最初っから描かないことにしようというものです。

例えば地球を描く事を考えます。
日本が表を向いた地球を描くとき、地球の反対側のチリ共和国を構成しているポリゴンはどうあがいたって描かれません。
なら最初から地球の裏側は描かないことにすれば描画コストを削減できるというわけです。
地球の裏側にあるかどうかというのを、三角形の頂点が時計回りに見えるかどうかというのを基準に考えるのです。


FillModeは図形内部を色塗りする方法です。
FillMode.WireframeFillMode.Solidのどちらかをセットします。
前回までやってきた三角形内部を塗りつぶすのはSolidで、今回のワイヤーフレームはWireframeです。

本当は今回のワイヤーフレームを使うのにCullModeは関係ありません。
しかし、デフォルトの設定ではAPIがエラーを投げるのです。
これはしかたがありません。
とりあえず今回はCullModeにはBackをセットしておきます。

コード

Program.cs
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.D3DCompiler;
 
class Program
{
    static void Main()
    {
        using (Game game = new MyGame())
        {
            game.Run();
        }
    }
}
 
class MyGame : Game
{
    Effect effect;
    InputLayout vertexLayout;
    Buffer vertexBuffer;
 
    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0, 0, 1)
            );
 
        initTriangleInputAssembler();
        drawTriangle();
        
        SwapChain.Present(0, PresentFlags.None);
    }
 
    private void drawTriangle()
    {
        effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
        GraphicsDevice.ImmediateContext.Draw(3, 0);
    }
 
    private void initTriangleInputAssembler()
    {
        GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
        GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
            0,
            new VertexBufferBinding(vertexBuffer, sizeof(float) * 3, 0)
            );
        GraphicsDevice.ImmediateContext.InputAssembler.PrimitiveTopology
            = PrimitiveTopology.TriangleList;
    }
 
    protected override void LoadContent()
    {
        initEffect();
        initVertexLayout();
        initVertexBuffer();
        initWireframeRasterizerState();
    }
 
    private void initEffect()
    {
        using (ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "myEffect.fx", "fx_5_0",
            ShaderFlags.None,
            EffectFlags.None
            ))
        {
            effect = new Effect(GraphicsDevice, shaderBytecode);
        }
    }
 
    private void initVertexLayout()
    {
        vertexLayout = new InputLayout(
            GraphicsDevice,
            effect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
            new[] { 
                    new InputElement
                    {
                        SemanticName = "SV_Position",
                        Format = Format.R32G32B32_Float
                    }
                }
            );
    }
 
    private void initVertexBuffer()
    {
        vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
            GraphicsDevice,
            new[] {
                new SlimDX.Vector3(0, 0.5f, 0),
                new SlimDX.Vector3(0.5f, 0, 0),
                new SlimDX.Vector3(-0.5f, 0, 0),
            });
    }
 
    private void initWireframeRasterizerState()
    {
        var desc = new RasterizerStateDescription
        {
            CullMode = CullMode.Back,
            FillMode = FillMode.Wireframe,
        };
 
        GraphicsDevice.ImmediateContext.Rasterizer.State
            = RasterizerState.FromDescription(
                GraphicsDevice,
                desc
            );
    }
 
    protected override void UnloadContent()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.State.Dispose();
        effect.Dispose();
        vertexLayout.Dispose();
        vertexBuffer.Dispose();
    }
}
 
class Game : System.Windows.Forms.Form
{
    public SlimDX.Direct3D11.Device GraphicsDevice;
    public SwapChain SwapChain;
    public RenderTargetView RenderTarget;
 
 
    public void Run()
    {
        initDevice();
        SlimDX.Windows.MessagePump.Run(this, Draw);
        disposeDevice();
    }
 
    private void initDevice()
    {
        MyDirectXHelper.CreateDeviceAndSwapChain(
            this, out GraphicsDevice, out SwapChain
            );
 
        initRenderTarget();
        initViewport();
 
        LoadContent();
    }
 
    private void initRenderTarget()
    {
        using (Texture2D backBuffer
            = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
            )
        {
            RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
            GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(RenderTarget);
        }
    }
 
    private void initViewport()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.SetViewports(
            new Viewport
            {
                Width = ClientSize.Width,
                Height = ClientSize.Height,
            }
            );
    }
 
    private void disposeDevice()
    {
        UnloadContent();
        RenderTarget.Dispose();
        GraphicsDevice.Dispose();
        SwapChain.Dispose();
    }
 
    protected virtual void Draw() { }
    protected virtual void LoadContent() { }
    protected virtual void UnloadContent() { }
}
 
class MyDirectXHelper
{
    public static void CreateDeviceAndSwapChain(
        System.Windows.Forms.Form form,
        out SlimDX.Direct3D11.Device device,
        out SlimDX.DXGI.SwapChain swapChain
        )
    {
        SlimDX.Direct3D11.Device.CreateWithSwapChain(
            DriverType.Hardware,
            DeviceCreationFlags.None,
            new SwapChainDescription
            {
                BufferCount = 1,
                OutputHandle = form.Handle,
                IsWindowed = true,
                SampleDescription = new SampleDescription
                {
                    Count = 1,
                    Quality = 0
                },
                ModeDescription = new ModeDescription
                {
                    Width = form.ClientSize.Width,
                    Height = form.ClientSize.Height,
                    RefreshRate = new SlimDX.Rational(60, 1),
                    Format = Format.R8G8B8A8_UNorm
                },
                Usage = Usage.RenderTargetOutput
            },
            out device,
            out swapChain
            );
    }
 
    public static Buffer CreateVertexBuffer(
        SlimDX.Direct3D11.Device graphicsDevice,
        System.Array vertices
        )
    {
        using (SlimDX.DataStream vertexStream 
            = new SlimDX.DataStream(vertices, true, true))
        {
            return new Buffer(
                graphicsDevice,
                vertexStream,
                new BufferDescription
                {
                    SizeInBytes= (int)vertexStream.Length,
                    BindFlags = BindFlags.VertexBuffer,
                }
                );
        }
    }
}
 
 

myEffect.fx
float4 MyVertexShader(float4 position : SV_Position) : SV_Position
{
    return position;
}

float4 MyPixelShader() : SV_Target
{
    return float4(1, 1, 1, 1);
}

technique10 MyTechnique
{
pass MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}

このプログラムはワイヤーフレームな三角形を表示します。

d4d15310.jpg

デバイスが初期化するときに、ラスタライザーステートをデバイスにセットするのです。
そのラスタライザーステートには、ワイヤーフレームで表示せよ、ということが書いてあります。
結果として、前回まで中身がきちんと描かれていた三角形は、周りの辺だけが描かれることになったのです。





























拍手[0回]