今回はようやくOpenGLでのComputeShaderを扱ってみます。意外とOpenGLでのComputeShaderって話題になっていないようで、ワクワクしています。
手順
VertexShader, FragmentShader などを生成するように、ComputeShader を生成します。ComputeShaderもまたGLSLで記述するので、今までのシェーダーを生成するような感じでプログラムを記述します。ひとつ違うのは出力する箇所でしょうか、今まではピクセルに出力する感じで out 変数に入れていましたが、ComputeShader では imageXXX というサンプラーっぽい型のuniform変数に値を書き込んでいく感じになります。
#version 430 uniform float roll; writeonly uniform image2D destTex; layout(local_size_x=32, local_size_y=32) in; void main() { ivec2 storePos = ivec2( gl_GlobalInvocationID.xy ); float localCoef = length( vec2(256,256) - vec2(ivec2(gl_GlobalInvocationID.xy)) ); imageStore( destTex, storePos, vec4( 0.5+0.5*sin( localCoef*0.05 + roll ), 0.5,0.5,0) ); }
C++側のコードは以下のような感じになります。通常のシェーダーと同じようにGLSLをコンパイルして ShaderProgram を作成します。
cs = glCreateShader( GL_COMPUTE_SHADER ); // Compute Shader生成! program = glCreateProgram(); glAttachShader( program, cs ); ... glLinkProgram();
ComputeShaderからの結果を格納するためのテクスチャを用意しておきます。
このときいつも通りにテクスチャを用意した後、下記のAPIにて設定しておきます。これが Shader Image load store 拡張のAPIになります。これによりテクスチャをUAV化というか書き込み先として利用できるようになります。
glBindImageTexture( 0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F );
通常のメインループにおいては、以下のような感じでコンピュートシェーダーを実行し、結果をテクスチャとして利用して貼り付けています。コンピュートシェーダーの実行は glDispatchCompute で行います。
static int frame = 0; // アプリメインループ. glUseProgram( programCompute ); glUniform1f( glGetUniformLocation( programCompute, "roll" ), frame*0.1 ); glDispatchCompute( 512/32, 512/32, 1 ); glClearColor( 0.30f, 0.6f, 1.0f, 0.0f ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); glUseProgram( programDraw ); glBindVertexArray( vao ); glDrawArrays( GL_TRIANGLE_STRIP, 0, 4 ); frame++;
今回は gl_GlobalInvocationID を使って処理しているだけですが、各処理ブロックを識別するのに使える変数がもっとたくさんあります。これらの意味を理解して適切に使っていかないと折角の演算力を生かし切ることができません。
CUDAやDirectComputeのほうでもこれらのパラメータに相当するものがあるようなので、またそれらを試してみたときにどういう対応なのか、どの部分を指し示すのかをまとめてみたいと思います。