Uniform Block は Direct3D の Constant Buffer に相当します。
外部で確保したメモリを、Shader からアクセス可能な Uniform 変数として
割り付けられるようになります。
トータルで Uniform として使えるメモリが増えますし、Shader 間での
データ共有も可能です。
OpenGL の Shader Program は D3D の FX に近い機能を持っていました。
シンボルは Shader 同士リンクされた状態となり、uniform の割り当てや
管理も自動的に行なってくれるため大変扱いやすくなっています。
その反面 GL 3.0 以前は uniform 値も Program Object 内で閉じており、
Camera や Light 等のシーンで共通なパラメータも、シェーダーそれぞれに
コピーする必要がありました。
OpenGL 3.1 と OpenGL ES 3.0 以降は予め Uniform Buffer を作って
おくことができます。
描画時は各シェーダーにバッファを bind するだけです。
その代わり、バッファ内の変数配置を自分で管理する必要があります。
// GLSL vsh layout(std140) uniform Scene { mat4 Projection; mat4 ViewMatrix; }; uniform mat4 World; in vec3 POSITION; void main() { mat4 pvw= World * ViewMatrix * Projection; gl_Position.xyzw= vec4( POSITION.xyz, 1.0 ) * pvw; }
↑シェーダー内では構造体のような書式で uniform block を宣言します。
HLSL の cbuffer と全く同じ仕様で、インスタンス名を宣言しなければ
シンボル名はトップレベルのスコープに含まれます。
main() の中でそのまま ViewMatrix や Projection を参照しています。
// C Init struct SceneData { math::Matrix4 Projection; math::Matrix4 ViewMatrix; }; GLuint scene_uniform= 0; glGenBuffers( 1, &scene_uniform ); glBindBuffer( GL_UNIFORM_BUFFER, scene_uniform ); glBufferData( GL_UNIFORM_BUFFER, sizeof(SceneData), &scene_data, GL_STATIC_DRAW ); glBindBuffer( GL_UNIFORM_BUFFER, 0 ); GLuint scene_block_index= glGetUniformBlockIndex( program, "Scene" ); glUniformBlockBinding( program, scene_block_index, 5 );
↑Uniform Block の作成は通常の Buffer と同じです。
操作対象を glBindBuffer() を使って bind します。
管理しやすいように GLSL 側の block と同じ構造体を宣言しています。
// C Render glUseProgram( program ); glBindVertexArray( input_layout ); // shader local glUniformMatrix4fv( world_location, 1, GL_FALSE, matrix ); // global glBindBufferBase( GL_UNIFORM_BUFFER, 5, scene_uniform ); glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0 );
↑描画時の bind 命令は特殊です。
Uniform Block の API は、Texture Object や Sampler Object の
仕組みに似ています。
API 内部に Bind Point の Table が存在しており、Object はこの Table に登録します。
この場合の番号は任意で、利用可能な範囲内 (GL_MAX_UNIFORM_BUFFER_BINDINGS)
なら好きなものを選んで構いません。
glBindBufferBase( GL_UNIFORM_BUFFER, 5, scene_uniform );
シェーダーにはこの Table の番号を登録します。
glUniformBlockBinding( program, scene_block_index, 5 );
Texture は glUniform1i() を使いましたが Uniform Block は専用の
命令 glUniformBlockBinding() があります。
Texture の場合と命令を比較すると下記の通り
● Texture の場合 // shader 側の Unit 指定 glUseProgram( program ); GLint loc= glGetUniformLocation( "SamplerName" ); glUniform1i( loc, 5 ); // glGetUniformLocation() は 存在しない場合 -1 を返す // object のバインド glActiveTexture( GL_TEXTURE0 + 5 ); glBindTexture( GL_TEXTURE_2D, texture_object );
● Uniform Block の場合 // shader 側の Unit 指定 GLuint bindex= glGetUniformBlockIndex( program, "BlockName" ); glUniformBlockBinding( program, bindex, 5 ); // glGetUniformBlockIndex() は 存在しない場合 GL_INVALID_INDEX を返す // object のバインド glBindBufferBase( GL_UNIFORM_BUFFER, 5, scene_uniform );
Texture Object では、Object 操作用の Bind と
描画用の Texture Image Unit 指定の Bind が同一でした。
複数の Unit を切り替えるためには別命令 glActiveTexture() を併用します。
Uniform Block の場合は、Object 操作用の Bind と描画用の BInd Point が
分かれています。
Object 操作は Vertex Buffer や Index Buffer などと同じ glBindBuffer() です。
描画用の Bind 登録には glBindBufferBase() か glBindBufferRange() を使います。
これらの命令は Uniform 以外の Buffer でも用います。
glBindBufferBase() や glBindBufferRange() を実行すると、描画用の
Bind Point だけでなく操作用の Bind Point の両方が置き換わるので注意が必要です。
glBindBuffer( GL_UNIFORM_BUFFER, 0 ); DumpUniformBinding(); // Buf: Uni=0000 通常の glBindBuffer() の値 (0クリア) glBindBufferBase( GL_UNIFORM_BUFFER, 0, scene_uniform ); glBindBufferBase( GL_UNIFORM_BUFFER, 4, scene_uniform ); DumpUniformBinding(); // Buf: Uni=000e 通常の glBindBuffer() の値 (ここも設定される) // [ 0]: Bind=000e (0 - 0) glBindBufferBase( 0 ) の値 // [ 4]: Bind=000e (0 - 0) glBindBufferBase( 4 ) の値 glBindBuffer( GL_UNIFORM_BUFFER, 0 ); DumpUniformBinding(); // Buf: Uni=0000 通常の glBindBuffer() の値 (ここだけ 0) // [ 0]: Bind=000e (0 - 0) glBindBufferBase( 0 ) の値 // [ 4]: Bind=000e (0 - 0) glBindBufferBase( 4 ) の値
上記のように glBindBufferBase() は通常の glBindBuffer() の bind point も書き換えます。
ですが glBindBuffer() を実行しても、描画用の Bind Point には影響が出ません。
よって下記のように glDrawElements() の前に
glBindBuffer( GL_UNIFORM_BUFFER, 0 ) を入れても問題ありません。
glUseProgram( program ); glBindVertexArray( input_layout ); glUniformMatrix4fv( world_location, 1, GL_FALSE, matrix ); glBindBufferBase( GL_UNIFORM_BUFFER, 5, scene_uniform ); glBindBuffer( GL_UNIFORM_BUFFER, 0 ); // 問題なし glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0 );
なお glBindBufferBase() でバッファを登録しても GL_UNIFORM_BUFFER_SIZE
は 0 のままでした。RADEON だけ値が入っています。
GPU GL_UNIFORM_BUFFER_SIZE --------------------------------------------- Intel HD 4000 4.0 0 GeForce GTX 650 4.4 0 RADEON HD 6750M 4.3 128 Mali-T604 OpenGL ES 3.0 0
下記は Uniform Block の利用可能なサイズを調べた結果です。
Intel GeForce RADEON RADEON Mali-T604 HD4000 GTX650M HD6750M HD7750(GCN) ------------------------------------------------------------ Vertex: Components 1024 4096 4096 16384 1024 Blocks 12 12 14 15 15 Combined 50176 53248 233472 262144 246784 Fragment: Components 4096 4096 2048 16384 1024 Blocks 12 12 14 15 15 Combined 53248 53248 231424 262144 246784 Hull (tcsh): Components 4096 2048 16384 16384 Blocks 12 14 15 15 Combined 53248 231424 262144 246784 Domain (tesh): Components 4096 2048 16384 16384 Blocks 12 14 15 15 Combined 53248 231424 262144 246784 Geometry: Components 4096 2048 16384 16384 Blocks 12 14 15 15 Combined 53248 231424 262144 246784 Compute: Components 2048 1024 1024 Blocks 14 16 16 Combined 231424 32768 1024 Bindings 36 60 84 75 75 BlockSize 16384 16384 65536 65536 65536 CombindBlocks 24 60 84 75 75
Direct3D API では shader 毎に 4096 elements の block を 14 slot まで
利用できます。(OpenGL の components と blocks に相当)
GL API では上記のように GPU 毎に異なる値となっています。
関連エントリ
・OpenGL の各バージョンと GLSL の互換性
・OpenGL のエラー判定と OpenGL 4.3 Debug Output
・OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
・OpenGL ES 3.0 と Vertex Array Object