2012年12月4日火曜日

Hello Oldschool GPGPU World

お久しぶりです。@jirohclです。
特に書くことも思い浮かばず日が過ぎ、気がつけばまたAdvent Calenderの季節になりました。

今回、私が参加するのはこちら、GPGPU Advent Calender@3日目となります。
この記事は温故知新と聞こえの良い事を言いつつ、ネタが思い浮かばなかった苦肉の策、
タイトルの通り古臭いGPGPUをやってみようと思います。

古臭いGPGPUとは何かと言うと、プログラマブルシェーダ、特にピクセルシェーダを用いて汎用計算をやってみましょうというアレです。
これを知ることでCUDA/OpenCL等は裏側で一体何をしているのか押し知ることが出来、また効率的な実装の方法も見えてくるはず。(建前)

まず、GPGPUをするために何が必要かというとご存知の通り
1.デバイスへの入力
2.処理関数
3.ホストへの出力
これら3つ。

まず、1の入力はテクスチャを使うのがポピュラーな方法です。
次に2の処理関数に関してはフラグメントシェーダーで頑張ります。
3はglReadPixels等のレンダリング後の画像を読み込む関数を使うことで結果を取り出します。

ではまずデバイス側のコード(行列積)を見てみましょう。
//vs
uniform mat4 mvp;

in vec3 in_pos;
in vec2 in_tex;

out vec2 out_tex;

void main(void)
{
gl_Position = mvp * vec4(gl_Vertex.xyz,0.f);
out_tex = in_tex;
}

//fs
uniform int mat_size;

uniform sampler2D mat_1;
uniform sampler2D mat_2;

in vec2 texcoord;

void main (void)
{
float result = 0.f;
for(int i = 0;i<mat_size;++i)
{
result += texture2D( mat_1,vec2(i/(float)mat_size,texcoord.y) )
* texture2D( mat_2,vec2(texcoord.y,i/(float)mat_size) );
}
gl_FragColor = vec4(result,1.f);
}

バーテクスシェーダ自体は何も特別なことは行わず、フラグメントシェーダで積を演算しています。
ここでフラグメントシェーダでの演算ですが実は一つ大きな問題があり、
フラグメントシェーダー内での処理には時間制限があるために行列のサイズが大きくなると計算が正常に終了しなくなります。OpenCLを使ったことがある方はきっと身に覚えがあるかと思います。アレです。

では次にホストコードを、と思いましたが全部羅列すると酷いことになるので一部
//データ作成
GLuint mat_lhs;
::glGenTextures( 1, &mat_lhs );
::glBindTexture( GL_TEXTURE_2D, mat_lhs );
::glTexImage2D(GL_TEXTURE_2D,0,GL_LUMINANCE,mat_size,mat_size,0,GL_LUMINANCE,GL_FLOAT,data);
::glBindTexture( GL_TEXTURE_2D, 0 );

//実行
::glEnableClientState(GL_VERTEX_ARRAY);
::glEnableClientState(GL_TEXTURE_COORD_ARRAY);
::glBindBuffer(GL_ARRAY_BUFFER, texed_quad);
::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
::glDrawElements(GL_TRIANGLES,buffer_size/sizeof(GLuint),GL_UNSIGNED_INT,0);
::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
::glBindBuffer(GL_ARRAY_BUFFER, 0);
::glDisableClientState(GL_VERTEX_ARRAY);
::glDisableClientState(GL_TEXTURE_COORD_ARRAY);
::glFlush();
::glReadPixels(0,0,mat_size,mat_size,GL_RGBA,GL_FLOAT,result);

デバイスへのデータ転送、カーネルの実行する”だけ”でもこれだけの決まりごとの命令が必要になります。
また、この他にもシェーダの初期化、ユニフォーム、イン/アウトへの関連付け、等々面倒くさい作業が目白押しとなっております。

CUDA/OpenCLが難しそうと思った方、古くさいGPGPUを動かすためのコードを見ると、現在の環境は大分めぐまれてると思いませんか?
思った方は是非GPGPU Advent Calenderに登録して記事を書いてみましょう!

0 件のコメント:

コメントを投稿