マルチコア対応のプログラミング
「CPUの数を知る」でCPUの数を見てからマルチスレッドプログラムでどうにかしようとか考えていたが、どうやら.NET4.0ではParallelクラスという便利な並列処理クラスが提供されているようだ。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace test static void Main(string[] args) { int count = 1000000; int[] arr = new int[count]; Parallel.For(0, count, i => { // 何か処理 arr[i] = i; } ); Console.WriteLine("処理完了"); Console.ReadLine(); } }
こんな感じに書ける。
InvokeしたりForEachしたりもできる。
ただし、同じメモリに書き込む可能性があるときは排他処理をわすれないように。
ゲーム内でのポリゴンをGPUベースで描画する2
「ゲーム内でのポリゴンをGPUベースで描画する」のつづき
下準備が終わったところで、具体的な手順をメモしておく
まずは頂点情報バッファをVRAM上に確保する
public static int CreateVertexBuffer(int VertexNum, int VertexType)
VertexNumは頂点情報の数、VertexTypeは頂点情報の形でDX.DX_VERTEX_TYPE_NORMAL_3Dを指定すればDX.VERTEX3Dが使える。戻り値は成功ならばハンドルが、失敗ならば-1が返る。
次に、実際にVRAMにデータを転送する
public static int SetVertexBufferData(int SetIndex, out VERTEX3D Vertex, int VertexNum, int VertexBufHandle)
SetIndexは開始位置、Vertexは頂点データ配列の先頭、VertexNumは配列の長さ、VertexBufHandleは確保した頂点バッファのハンドル、慣例に従えば戻り値が-1であれば失敗、それ以外で成功となる。
さらにインデックスバッファも同じように確保する
public static int CreateIndexBuffer(int IndexNum, int IndexType)
IndexNumはインデックスバッファの大きさ、IndexTypeはインデックスのタイプでDX.DX_INDEX_TYPE_16BITを指定できる。戻り値は-1であれば失敗、それ以外の場合は確保されたインデックスバッファのハンドルになる。
インデックスバッファも同じようにVRAMに転送する必要がある
public static int SetIndexBufferData(int SetIndex, out ushort IndexData, int IndexNum, int IndexBufHandle)
SetIndexは開始位置、IndexDataはインデックスバッファ配列の先頭、IndexNumは配列の数、IndexBufHandleに確保したインデックスバッファのハンドルを指定する。戻り値はおそらくいつものとおりだろう。
ここまでくれば、描画をおこなうことができる
public static int DrawPolygonIndexed3D_UseVertexBuffer(int VertexBufHandle, int IndexBufHandle, int GrHandle, int TransFlag)
VertexBufHandleは頂点バッファのハンドル、IndexBufHandleはインデックスバッファのハンドル、GrHandleはテクスチャのハンドル、TransFlagは透過フラグになる。戻り値は(以下略)
実際に描画するに当たって、移動や回転、変形を加えたい場合は
public static int SetTransformToWorld(out MATRIX Matrix)
を使う。Matrixには行列を指定する。
実際に使うときは
DX.SetTransformToWorld(out Matrix);
DX.DrawPolygonIndexed3D_UseVertexBuffer(VertexBufHandle, IndexBufHandle, GrHandle, DX.TRUE);
の様にして書くことになる。
VRAMに確保したバッファを開放するには
public static int DeleteVertexBuffer(int VertexBufHandle) public static int DeleteIndexBuffer(int IndexBufHandle)
を使うことになる、引数に開放するバッファのハンドルを指定すればよい。
一括で全て開放するには
public static int InitVertexBuffer() public static int InitIndexBuffer()
が使えるようだ、
ゲーム内でのポリゴンをGPUベースで描画する
DrawPolygonIndexed3D_UseVertexBufferという関数がある。この関数をつかうとGPUベースでのポリゴンの描画が可能だ。DrawPolygonIndexed3DではCPUベースなのでGUPベースより処理速度が劣る感がある。実際には頂点シェーダ2.0に対応したグラボが必要なようで、未対応だったりするとCPUベースの計算になるということらしい。
で、使い方だが、C#で使う場合そのままでは、いくつかのコードがunsafe指定されているので使いにくい。なので先に通常の関数に直しておくことにする。
DxDLL.csにある関数の
[DllImport("DxLib.dll", EntryPoint="dx_SetVertexBufferData")] extern unsafe static int dx_SetVertexBufferData( int SetIndex, void * VertexData, int VertexNum, int VertexBufHandle); public unsafe static int SetVertexBufferData( int SetIndex, void * VertexData, int VertexNum, int VertexBufHandle) { return dx_SetVertexBufferData( SetIndex , VertexData , VertexNum , VertexBufHandle ); }
[DllImport("DxLib.dll", EntryPoint="dx_SetIndexBufferData")] extern unsafe static int dx_SetIndexBufferData( int SetIndex, void * IndexData, int IndexNum, int IndexBufHandle); public unsafe static int SetIndexBufferData( int SetIndex, void * IndexData, int IndexNum, int IndexBufHandle) { return dx_SetIndexBufferData( SetIndex , IndexData , IndexNum , IndexBufHandle ); }
この2つが今回使う関数になる。ここで指定されている「void*」は複数の型が指定できる配列の先頭アドレスを表している。この場合は利用方法を限定することでunsafeをはずすことができる
[DllImport("DxLib.dll", EntryPoint = "dx_SetVertexBufferData")] extern static int dx_SetVertexBufferData(int SetIndex, out VERTEX3D Vertex, int VertexNum, int VertexBufHandle); public static int SetVertexBufferData(int SetIndex, out VERTEX3D Vertex, int VertexNum, int VertexBufHandle) { return dx_SetVertexBufferData(SetIndex, out Vertex, VertexNum, VertexBufHandle); }
[DllImport("DxLib.dll", EntryPoint = "dx_SetIndexBufferData")] extern static int dx_SetIndexBufferData(int SetIndex, out ushort IndexData, int IndexNum, int IndexBufHandle); public static int SetIndexBufferData(int SetIndex, out ushort IndexData, int IndexNum, int IndexBufHandle) { return dx_SetIndexBufferData(SetIndex, out IndexData, IndexNum, IndexBufHandle); }
ここでvoid*の代わりに指定されたout VERTEX3Dやout ushortはそれぞれVERTEX3Dとushortの先頭アドレスとしてDXライブラリに引渡しをされる。
日本語でもいけるらしい
namespace 日本語テスト { class テストクラス { public static void 書く(string 文字列) { Console.WriteLine(文字列); } } }
このコードが通るので、英語名に困ったら日本語でもいける。
入力補間がうまく働かなかったりするので使わないと思うけど一応メモしておく
アンセーフコード
C#ではポインタを扱う際には、このコードが危険であることを明示することが必要なようだ。C#単体ではまず使わないが、CやC++で書かれたDLL等を使うときには必要になる処理だ。
unsafe { int i = 0; int* p = &i; }
という具合に書いたりする。
特にvoid型のポインタを指定してきた場合などには使うのではないだろうか?
DXライブラリを利用していていくつかこのような部分を見つけることがある。
これを使うためにはプロジェクトのプロパティ-ビルドから「アンセーフコードの許可」を有効にする必要があるが、DXライブラリの.net版にはDxDLL.csというファイルがありここで、各関数の挙動を定義している。
なので、ここでvoid型のポインタを要求している箇所を特定の配列の先頭を指定することでアンセーフ指定しなくても良い常態にすることができる。
具体的には
#if DX_USE_UNSAFE [DllImport("DxLib.dll", EntryPoint="dx_SetVertexBufferData")] extern unsafe static int dx_SetVertexBufferData( int SetIndex, void * VertexData, int VertexNum, int VertexBufHandle); public unsafe static int SetVertexBufferData( int SetIndex, void * VertexData, int VertexNum, int VertexBufHandle) { return dx_SetVertexBufferData( SetIndex , VertexData , VertexNum , VertexBufHandle ); } #endif [DllImport("DxLib.dll", EntryPoint = "dx_SetVertexBufferData")] extern static int dx_SetVertexBufferData(int SetIndex, out VERTEX3D Vertex, int VertexNum, int VertexBufHandle); public static int SetVertexBufferData(int SetIndex, out VERTEX3D Vertex, int VertexNum, int VertexBufHandle) { return dx_SetVertexBufferData(SetIndex, out Vertex, VertexNum, VertexBufHandle); }
という感じに書けばよい
CPUの数を知る
どうやらマルチスレッドで処理をおこなうと、あいてるCPUに処理を振り分けてくれるようなので、一部の処理をマルチスレッド化しようかと考える。
そこで、CPUの数がどうしても知りたくなる。CPUが1つしかないのにスレッドを沢山作っても意味が無いからだ。
.netのコードでは
int cpu = Environment.ProcessorCount;
という具合に書いておけばOSから見てのCPUの数がわかるので必要に応じて処理を分けるといい。