OpenGL のユニフォームブロックについて

はじめに

OpenGL のシェーダにデータを受け渡す方法の一つにユニフォームがありますが、 ユニフォームを少し拡張したような機能でユニフォームブロックというものもあってこれについて調べたことについて書きます。

仕様

ユニフォームブロックにはユニフォームバッファオブジェクトを使うことでまとめてユニフォームを受け渡すことができます。

ライトやカメラの情報など複数のシェーダで使う情報を受け渡すときに便利なのではないかと思います。

しかしバッファにユニフォームのデータを書き込むときにはユニフォームブロックのメモリレイアウトについて考慮する必要があって、この部分が少し難しく間違いやすい気がします。

メモリレイアウトの種類として shared, packed, std140 があります。デフォルトは shared のようです。std140 を使うことが多いと思うのでこれのみ調べています。

OpenGL 仕様の 7.6.2.2 Standard Uniform Block Layout のあたりにかかれていました。

https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf

std140 のメモリレイアウト仕様の引用

  1. If the member is a scalar consuming N basic machine units, the base alignment is N.
  2. If the member is a two- or four-component vector with components consuming N basic machine units, the base alignment is 2N or 4N, respectively.
  3. If the member is a three-component vector with components consuming N basic machine units, the base alignment is 4N.
  4. If the member is an array of scalars or vectors, the base alignment and array stride are set to match the base alignment of a single array element, according to rules (1), (2), and (3), and rounded up to the base alignment of a vec4.
  5. If the member is a column-major matrix with C columns and R rows, the matrix is stored identically to an array of C column vectors with R components each, according to rule (4).
  6. If the member is an array of S column-major matrices with C columns and R rows, the matrix is stored identically to a row of S × C column vectors with R components each, according to rule (4).
  7. If the member is a row-major matrix with C columns and R rows, the matrix is stored identically to an array of R row vectors with C components each, according to rule (4).
  8. If the member is an array of S row-major matrices with C columns and R rows, the matrix is stored identically to a row of S × R row vectors with C components each, according to rule (4).
  9. If the member is a structure, the base alignment of the structure is N, where N is the largest base alignment value of any of its members, and rounded up to the base alignment of a vec4.
    The individual members of this substructure are then assigned offsets by applying this set of rules recursively, where the base offset of the first member of the sub-structure is equal to the aligned offset of the structure.
    The structure may have padding at the end; the base offset of the member following the sub-structure is rounded up to the next multiple of the base alignment of the structure.
  10. If the member is an array of S structures, the S elements of the array are laid out in order, according to rule (9).

訳してみたもの

  1. スカラーでNマシンユニットを消費する場合アラインメントはN。
  2. 2または4要素のベクトルで要素がNマシンユニットを消費する場合、アラインメントはそれぞれ2N、4N。
  3. 3要素ベクトルで要素がNマシンユニットを消費するときのアラインメントは4N。
  4. 配列のアラインメントは要素から計算される。ただしvec4で切り上げられる。
  5. 列優先C列R行行列はルール4からR要素ベクトルのサイズCの配列と同じ。
  6. 列優先C列R行行列のS要素配列の場合、R要素ベクトルを要素とするサイズS x Cの配列と同じ。
  7. 行優先C列R行行列の場合、C要素ベクトルのサイズRの配列と同じ。
  8. 行優先C列R行の行列S要素配列の場合、C要素ベクトルを要素とするサイズS x Rの配列と同じ。
  9. 構造体の場合、アラインメントは構造体のメンバーのうち一番大きいもののアラインメントになる。ただしvec4アラインメントに切り上げられる。
    構造体の個々のメンバーはこのルールを再帰的に適用され決まったオフセットの位置に配置される。構造体の最初のメンバーの配置オフセットは構造体自身のオフセットに一致する。
    構造体は末尾にパディングを持つ場合がある。構造体に続くメンバーのベースオフセットは、構造体のベースアライメントの次の倍数に切り上げられる。
  10. 構造体を要素とするサイズSの配列の場合、ルール9によって順番に配置される。

スカラー型は bool, float, int, uint, double があります。

スカラー型のアラインメントとバイトサイズは下のようになっています。

uniform type alignment byte size
bool 4 4
float 4 4
int 4 4
uint 4 4
double 8 8

それから行列のメモリレイアウトの列優先 (column-major)、行優先 (row-major) は glsl 側で指定できて column_major または row_major で指定します。 デフォルトは列優先です。

https://www.khronos.org/opengl/wiki/Interface_Block_(GLSL)#Matrix_storage_order

調べ方

仕様を読んだり google で検索したりしてたんですがはっきりした情報が見つからずコードを書いて実際にどうなるかみてみることにしました。

https://github.com/tkaaad97/uniformblock-experiment

使ったコードはここのリポジトリに置いています。

適当なユニフォームブロックが入ったシェーダーを書いてこれを OpenGLコンパイルし、ユニフォームの情報を取得して調べるという方法で試しました。

下のような感じでブロック一つ目のフィールドをアラインメントが一番小さい float にして二つ目のフィールドを調べたい型にし、三つ目を float にしてオフセットを取得することでアラインメントとサイズが多分わかると思います。

layout (std140) uniform ublock {
    float uniform1;
    #{uniformType} uniform2;
    float uniform3;
} ublock;

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glGetActiveUniformsiv.xhtml

ユニフォームのパラメーターは glGetActiveUniformsiv で取得できます。

パラメーターの種類は GL_UNIFORM_SIZE, GL_UNIFORM_NAME_LENGTH, GL_UNIFORM_BLOCK_INDEX, GL_UNIFORM_OFFSET, GL_UNIFORM_ARRAY_STRIDE, GL_UNIFORM_MATRIX_STRIDE, GL_UNIFORM_IS_ROW_MAJOR GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX です。

また構造体についてはパターンが色々あって網羅できないためこの方法では調べていません。 他の部分が分かれば構造体については仕様のルールから理解できるはずと思います。

結果

下のような結果になりました。 仕様の文章からわかる結果だと思いますが、確証が持てなかったのではっきりしてよかったです。

uniform type alignment byte size array stride matrix stride matrix order
bvec2 8 8
bvec3 16 12
bvec4 16 16
vec2 8 8
vec3 16 12
vec4 16 16
ivec2 8 8
ivec3 16 12
ivec4 16 16
uvec2 8 8
uvec3 16 12
uvec4 16 16
dvec2 16 16
dvec3 32 24
dvec4 32 32
float[4] 16 64 16
vec2[4] 16 64 16
vec3[4] 16 64 16
vec4[4] 16 64 16
dvec2[4] 16 64 16
dvec3[4] 32 128 32
dvec4[4] 32 128 32
mat2 16 32 16 row
mat2x3 16 48 16 row
mat2x4 16 64 16 row
mat3 16 48 16 row
mat3x2 16 32 16 row
mat3x4 16 64 16 row
mat4 16 64 16 row
mat4x2 16 32 16 row
mat4x3 16 48 16 row
dmat2 16 32 16 row
dmat2x3 16 48 16 row
dmat2x4 16 64 16 row
dmat3 32 96 32 row
dmat3x2 32 64 32 row
dmat3x4 32 128 32 row
dmat4 32 128 32 row
dmat4x2 32 64 32 row
dmat4x3 32 96 32 row
mat2[4] 16 128 32 16 row
mat2x3[4] 16 192 48 16 row
mat2x4[4] 16 256 64 16 row
mat3[4] 16 192 48 16 row
mat3x2[4] 16 128 32 16 row
mat3x4[4] 16 256 64 16 row
mat4[4] 16 256 64 16 row
mat4x2[4] 16 128 32 16 row
mat4x3[4] 16 192 48 16 row
dmat2[4] 16 128 32 16 row
dmat2x3[4] 16 192 48 16 row
dmat2x4[4] 16 256 64 16 row
dmat3[4] 32 384 96 32 row
dmat3x2[4] 32 256 64 32 row
dmat3x4[4] 32 512 128 32 row
dmat4[4] 32 512 128 32 row
dmat4x2[4] 32 256 64 32 row
dmat4x3[4] 32 384 96 32 row
mat2 16 32 16 column
mat2x3 16 32 16 column
mat2x4 16 32 16 column
mat3 16 48 16 column
mat3x2 16 48 16 column
mat3x4 16 48 16 column
mat4 16 64 16 column
mat4x2 16 64 16 column
mat4x3 16 64 16 column
dmat2 16 32 16 column
dmat2x3 32 64 32 column
dmat2x4 32 64 32 column
dmat3 32 96 32 column
dmat3x2 16 48 16 column
dmat3x4 32 96 32 column
dmat4 32 128 32 column
dmat4x2 16 64 16 column
dmat4x3 32 128 32 column
mat2[4] 16 128 32 16 column
mat2x3[4] 16 128 32 16 column
mat2x4[4] 16 128 32 16 column
mat3[4] 16 192 48 16 column
mat3x2[4] 16 192 48 16 column
mat3x4[4] 16 192 48 16 column
mat4[4] 16 256 64 16 column
mat4x2[4] 16 256 64 16 column
mat4x3[4] 16 256 64 16 column
dmat2[4] 16 128 32 16 column
dmat2x3[4] 32 256 64 32 column
dmat2x4[4] 32 256 64 32 column
dmat3[4] 32 384 96 32 column
dmat3x2[4] 16 192 48 16 column
dmat3x4[4] 32 384 96 32 column
dmat4[4] 32 512 128 32 column
dmat4x2[4] 16 256 64 16 column
dmat4x3[4] 32 512 128 32 column