日別アーカイブ: 2013年8月14日

OpenGL ES 3.0/OpenGL 4.x Uniform Block

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