戻る

OpenGL3.3の仕様を読む

Sep 1,2020

OpenGLの最新バージョンは4以上だが, OpenGL3.3では既に現代的なプログラマシェーダーによる方法を進めており, かつての固定シェーダーによる方法ではない. 3と4の違いは機能がいくつか追加されたことだけなので3.3の仕様が読めれば十分に役に立つ.

OpenGLはステートマシーンである, というのが重要である. frame bufferは描画面, contextは描画面の状態であるがこれらはOS側が設定する物であり, GL側にはframe bufferやcontextを初期化する, という操作はない. GLがやることは命令に応じてframe bufferをある状態からある状態へ変える, ということだけだ. frame bufferはdefaultのものが用意されていることが多いが, defaultのものは使わず, frame buffer objectを生成して使うことが多い.

仕様書では gl(Cライブラリ)のように定数や型にGL_やGLをつけることなくそのままの名前で説明している.

7ページから12ページまではfloating pointでの計算の仕様を決めている. これはGPUベンダー側が考える話なので必要ない.

2.2 GL State

GL server stateとGL client stateに分けられる. ほとんどは前者, gl contextはserver state

2.3 GL Command Syntax

GLの命令には命名規則があり, これによって引数や返り値を考えることができる. 4という数字は引数の数, fはflaot型というように. vはベクトル値であり, つまりポインター型を指すことになる.


void Uniform4f(int location, float v0, float v1, float v2, float v3);
void Uniform1i(int location, int value);
void GetFloatv(enum value, float *data)

コマンドの流れ, rastelization, fragment shader, ...

2.5 GL Errors

GetError()で最初に起きたエラーを取得できる. NO_ERRORなら正常. INVALID_ENUM, INVALID_VALUE, INVALID_OPERATION, INVALID_FRAMEBUFFER_OPERATION, OUT_OF_MEMORY


enum GetError();

2.6 Primitives and Vertices

primitiveとは点, 線分, 多角形のこと. DrawArraysなどの命令で生成される. Primitive Typeとして, POINTS, LINE_STRIP, LINE_LOOP, LINESがある. LINE_STRIPは線をどんどんつなげていくもので, i個の点ならi-1個の辺ができる. LINE_LOOPは最後の点と最初の点もつなげるので閉じる. LINESは1,2番目を線で結び, 3,4番目を線で結び, ...を繰り返す. 2n個の頂点に対しn個の辺ができる. 奇数個の場合は最後の頂点は無視される.

TRIANGLE_STRIP はi-2,i-1番目の頂点をi番目の頂点に結ぶ, ということを繰り返す. 2番目を1番目に結ぶ(-1番目はないので一つだけしか結ばない) 3番目を1,2番目に結ぶ, 4番目を2,3番目に結ぶ,...

TRIANGLE_FANはTRIANGLE_STRIPの説明のi-2を1に変えたものであり, 1が中心の扇型のような図形となる.

TRIANGLESはLINESの場合の三角形バージョンで, 3n個の頂点に対して3k-2,3k-1,3k番目の頂点で三角形を為す.

LINES_ADJACENCY, LINE_STRIP_ADJACENCY, TRIANGLES_ADJACENCY, TRIANGLE_STRIP_ADJACENCY

2.7 Vertex Specification

頂点はindexによって識別され, 座標によって識別するわけではない. 頂点に対して, 座標(x,y,z,w)を与えることができる. 4つの要素を全て与えなくても, 例えばVertexAttrib2ならx, yを指定した値, z=0,w=1となる.


void VertexAttrib{1234}{sfd}(uint index, T values)
void VertexAttribP{1234}ui(uint index, enum type, boolean normalized, uint value)

packed dataについては後で解説.

2.8 Vertex Arrays

次の命令で頂点配列に対する属性を設定. sizeは1,2,3,4のいずれか, typeはBYTE,SHORT,INT,FLOAT,HALF_FLOAT,DOUBLEのいずれか. UNSIGNED_BYTE,UNSIgNED_SHORT,UNSIGNED_INT 使える. INT_2_10_10_10_-REV, UNSIGNED_INT_10_100_10_0_REV, 4signed, 4 unsigned elements packed


void VertexAttribPointer(uint index, int size, enum type, boolean normalized, sizei stride, const void* pointer);
void VertexAttribIPointer(uint index, int size, enum type, sizei stride, const void *pointer);


void EnableVertexAttribArray(uint index);
void DisableVertexAttribArray(uint index);

DrawArrays, DrawElements, Draw*命令によって array element iはGLに送られる. void Enable(enum target); void Disable(enum target);のtargetをPRIMITIVE_RESTARTにすることでprimitiveの再起動を有効, 無効にする. void PrimitiveRestartIndex(uint index); を使うことで

pack dataのINT_2_10_10_10_REVはw,x,y,zのそれぞれのbit数.

void DrawArraysOneInstance(enum mode, int first, sizei count, int instance); 命令はGLにはないが, primitiveの列を生成する. firstからfirst + count -1まで. modeでPRIMITIVE type指定. void DrawArrays(enum mode, int first, sizei count); はDrawArraysOneInstance(mode, first, count, 0);に等しい. instanceはvertex shaderでgl_InstanceIDとして読み込まれる. void DrawArraysInstanced(enum mode, int first, sizei count, sizei primcount)


void DrawArraysInstanced(enum mode, int first, sizei count, sizei primcount) {
  if (mode or count is invalid)
    generate appropriate error
  else {
    for (i = 0; i < primcount; i++) {
        instanceID = i;
        DrawArraysOneInstance(mode, first, count, i)
      }
  }
}


void MultiDrawArrays(enum mode, const int *first, const sizei *count, sizei primcount) {
  if (mode is invalid)
    generate appropriate error
  else {
    for (i = 0; i < primcount; i++) {
      if (count[i] > 0)
        DrawArraysOneInstance(mode, first[i], count[i], 0);
    }
  }
}

void DrawElementsOneInstance(enum mode, sizei count, enum type, const void *indices, uint instance); はGLにない命令だが, バインドされているarray bufferのcount長さのindicesに含まれるindexに対して描画. DrawElements(mode, count, type, indices);はDrawElementsOneInstance(mode, count, type, indices, 0)に等しい


void DrawElementsInstanced(enum mode, sizei count, enum type, const void *indices, sizei primcount) {
  if (mode, count, or type is invalid)
    generate appropriate error
  else {
    for (int i = 0; i < primcount; i++) {
      instanceID = i;
      DrawElementsOneInstance(mode, count, type, indices, i);
  }
  instanceID = 0;
  }
}


void MultiDrawElements( enum mode, const sizei *count, enum type, const void **indices, sizei primcount ) {
  if (mode, count, or type is invalid)
    generate appropriate error
  else {
    for (int i = 0; i < primcount; i++)
      DrawElementsOneInstance(mode, count[i], type, indices[i], 0);
  }
}

2.9 Buffer Objects

vertex array dataはGLのbuffer object上で操作される. void GenBuffers(sizei n, uint *buffers); でn個のbuffer idを*buffersに格納する. buffer objectはuintのidで識別されるのであった. 同様に, void DeleteBuffers(sizei n, const uint *buffers)で消去.

void BindBuffer(enum target, uint buffer); によって初めてbuffer objectが生成される. GenBuffersではidを発行しただけで実際にstateを持つオブジェクトではない. buffer objectがdeleteされると, そのbuffer objectとバインドしているターゲット全ては0にバインドされる. 一つのbuffer objectはいくつものtargetにバインドできる.

void BindBufferRange(enum target, uint index, uint buffer, intptr offset, sizeiptr size); void BindBufferBase(enum target, uint index, uint buffer); targetはTRANSFORM_FEEDBACK_BUFFER or UNIFORM_BUFFER. offsetはbufferのoffset, sizeはoffsetからbindする領域のsize. BindBufferBaseはoffset=0, size = bufferのサイズとして適用したもの.

Target name Purpose
ARRAY_BUFFERS Vertex attributes
COPY_READ_BUFFER Buffer copy source
COPY_WRITE_BUFFER Buffer copy destination
ELEMENT_ARRAY_BUFFER Vertex array indices
PIXEL_PACK_BUFFER Pixel read target
PIXEL_UNPACK_BUFFER Texture data source
TEXTURE_BUFFER Texture data buffer
TRANSFORM_FEEDBACK_BUFFER Transform feedback buffer
UNIFORM_BUFFER Uniform block storage
Name Type Initial Value Legal Values
BUFFER_SIZE int64 0 any non-negative integer
BUFFER_USAGE enum STATICK_DRAW STREAM_DRAW,STREAM_READ,STREAM_COPY,STATIC_DRAW,STATIC_READ,STATIC_COPY,DYNMIC_DRAW,DYNAMIC_READ,DYNAMIC_COPY
BUFFER_ACCESS enum READ_SRITE READ_ONLY,WRITE_ONLY,READ_WRITE
BUFFER_MAPPED boolean FALSE TRUE,FALSE
BUFFER_MAP_POINTER void* NULL address
BUFFER_MAP_OFFSET int64 0 any non-negative integer
BUFFER_MAP_LENGTH int64 0 any non-negative integer
BUFFER_SIZE int64 0 any non-negative integer

bindしただけではまだ領域が確保されない. BufferDataを呼び出して, targetにbindされたbuffer objectの領域を確保する. void BufferData(enum target, sizeiptr size, const void *data, enum usage); sizeは領域のサイズ, *dataはクライエント側のデータであり, それをbufferにコピーする. usageは以下の説明となる:

STREAM_DRAW: クライエント側のバッファを一度指定することで, GLはそこからデータを引き出し続ける.

STREAM_READ: GLからクライエントへ

STREAM_COPY: GLのデータからGLのデータへ

STATIC_DRAW: データ元が不変である時に使う. パフォーマンスが良い気がする.

STATIC_READ:

STATIC_COPY:

DYNAMIC_DRAW:

DYNAMIC_READ:

DYNAMIC_COPOY:

BufferDataを呼び出すと既にあるデータは消去される. 生成できない場合OUT_OF_MEMORYエラーとなる. void BufferSubData(enum target, intptr offset, sizeiptr size, const void *data); によって, データを置き換える. BUFFER_SIZEを超えた領域まで置き換えることはできない.

実は, GLのbufferが持つデータにクライエント側からアクセスできる. void* MapBufferRange(enum target, intptr offset, sizeiptr length, bitfield access); エラーが起きなければ, bufferの指定した領域へのポインターが返される.

accessとしては次のものがある:

MAP_READ_BIT: クライエント側がreadする.

MAP_WRITE_BIT: クライエント側がデータを書き込む.

MAP_INVALIDATE_RANGE_BIT: bufferの領域の全てのrangeに書き込まなくてもエラーを出さない. writeのみで使う

MAP_INVALIDATE_BUFFER_BIT: buffer全体にかき込まなくてもエラーを出さない. writeのみで使う.

MAP_FLUSH_EXPLICIT_BIT:

MAP_UNSYNCHRONIZED_BIT: Bufferのrangeに対して命令でアクセス待ちになっていてもすぐ返す.

MapBufferRangeによってbuffer objectのstateは変化する: 失敗時はnullが返される.

Name Value
BUFFER_ACCESS Depends on access
BUFFER_ACCESS_FLAGS accessのビットフィールドに対応
BUFFER_MAPPED TRUE
BUFFER_MAP_POINTER pointer to the data store
BUFFER_MAP_OFFSET offset
BUFFER_MAP_LENGTH length

既にmapされているbufferに対してMapBufferRangeは実行できない. MAP_READ_BIT, MAP_WRITE_BITの少なくとも一方はsetする.

void* MapBuffer(enum target, enum mbaccess); はbufferの全体に対してmapする. mbaccessはenumであることに注意する. enumとして, READ_ONLY, WRITE_ONLY, READ_WRITEがある.

flushを使う場合, void FlushMappedBufferRange(enum target, intptr offset, sizeiptr length); で変更をflushする. この範囲指定は, mapに対応するrangeを基準としていることに注意.

マッピング後はboolean UnmapBuffer(enum target);を呼び出す.

void* CopyBufferSubData(enum readtarget, enum writetarget, intptr readoffset, intptr writeoffset, sizeiptr size); ではbufferのdataを別のbufferのdataへコピーする. targetとしてCOPY_READ_BUFFER, COPY_WRITE_BUFFERがよく使われる. どちらのtargetも同じbuffer objectとbindしているとINVALID_VALUEエラーとなる. あとrangeを超えてもINVALID_VALUE. どちらかのtargetが0 bindだったり, buffer objectがmapされているとINVALID_OPERTIONとなる.

VertexAttribPointerはARRAY_BUFFER_BINDINGを指定されたindexのクライエントステートのVERTEX_ATTRIB_ARRAY_BUFFER_BINDINGにコピーする.

BindBufferによってbuffer objectはELEMENT_ARRAY_BUFFERにbindできる.

2.10 Vertex Array Objects

buffer objectは集約してvertex array objectをなす. void GenVertexArrays(sizei n, uint *arrays); でn個のvertex array objectを発行し*arraysに渡す. void DeleteVertexArrays(sizei n, const uint *arrays)でn個消す. 消去されたvertex array objectにbindしていたものは0にbindする.

void BindVertexArray(uint array); によってvertex array objectを初期化.

2.11 Vertex Shaders

vertex shaderとはstring配列でありGLSLソースコードである. まず, shader objectにvertex shaderをロードし, コンパイルすることで使える. このようにしてshader objectにvertex shaderを組み込み, shader objectをいくつか集めてprogram objectにattachする. そしてlinkすることで, プログラムが完成する. このプログラムをcurrent program objectで使うことで, 頂点を処理できる.

vertex shaderに加え, geometry shadersとfragment shadersも同様な過程でプログラムを作る. gemetry shaderはprimitiveの生成, fragment shaderはラスターで使う. 1つのprogram objectにvertex shader, geometry shader, fragment shaderを組み込むことができる.

vertex shaderはいくつかの変数を持つ. Vertex attributesは各頂点の値であり, Uniformsはプログラムごとの変数でプログラム実行時は定数. SamplersはaUniformsの中でもテクスチャに使う.

uint CreateShader(enum type); でshader object生成. typeはVERTEX_SHADERなど.

void ShaderSource(uint shader, sizei count, const char **string, const int *length); でshader objectにGLSLソースコードを組み込む. *lengthはそれぞれのstringの長さだが, nullを入れると全てのstringはnull terminateとみなし終端まで読み取る. 既にshader objectにGLSLが組み込まれているなら, 元のGLSLは破棄される.

ロードしたら, void CompileShader(uint shader); でshader をコンパイルする. shader objectはCOMPILE_STATUSを持つ. GetShaderivで取得できる. TRUEなら成功, FALSEなら失敗. ShaderInfoLogでコンパイル情報取得. シェーダーオブジェクトはvoid DeleteShader(uint shader)で消去. プログラムにattachされていなければすぐ消えるが, そうでなければまずDELETE_STATUSがtrueとなる. このstatusもGetShaderivで取得できる.

uint CreateProgram(void); でprogram object生成. 失敗なら0.

void AttachShader(uint program, uint shader); でshader objectをattachする. これはGLSLのロード前やコンパイル前にもして良い. 既にattachされているshaderならINVALID_OPERATIONとなる. 同じshaderをまた別のprogramにattachできる. void DetachShader(uint program, uint shader); でdetachする. detachされたshader objectのDELETE_STATUSがtrueのとき, shader objectがどこのprogramにもattachされていない状態になって初めて消去される.

void LinkProgram(uint program); でshader objectをリンクして実行プログラム完成. program objectはLINK_STATUSを持ち, GetProgramivで取得できる. TRUEなら成功, FALSEなら失敗. GetProgramInfoLogでリンク情報取得. void UseProgram(uint program); で使用できる. UseProgramを呼び出した後は, attachされているshader objectをコンパイルしようがdetachしようが, 新たなshader objectをattachしようがどうでもいい. しかし, さらにprogram objectがLinkProgramを呼び出した時は新たな実行プログラムがレンダリングで使われる. 失敗した時は依然として同じ実行プログラムが使われる. void DeleteProgram(uint program); でどのcontextでも使われていない時にprogram object消去. そうでない場合はdelete flagがsetされ消去待ちとなる. program objectが消去されるとattachしているshader objectはdetachされる.

vertex shaderではattribute variableを定義でき, VertexAttrib*にbindされた変数はクライエントから変数名でクエリすることでアクセスできる. これはリンク前に行う必要がある. 変数がfloat, vec2, vec3, vec4ならattributeのindex iでアクセスできる. 行列の場合は, 4x2行列ならi, i+1のindexで各列にアクセスできる. attribute variableはプログラムで実際に使われているならばactiveとみなされる. void GetActiveAttrib(uint program, uint index, sizei bufSize, sizei *length, int *size, enum *type, char *name); でactive attribute variableを取得できる. indexに0で最初のattribute variable, ACTIVE_ATTRIBUTES-1で最後のactive attribute variableを取得できる. ACCTIVE_ATTRIBUTESはGetProgramivで取得できる. 注意するのはこのindexはあくまでもactiveなもので完結しているので, 一般にattribute variableが持つindexとはまた別である. programは既にlinkを終えている必要がある. *name, *lengthには変数名とその長さが返る. bufSizeで変数名の長さを制限して取得することができる. ACTIVE_ATTRIBUTE_MAX_LENGTHは最大の変数名の長さのステータスでGetProgramivで取得できる. *typeは色々あるが, FLOAT, FLOAT_VEC2, FLOAT_VEC3, FLOAT_MAT2, FLOAT_MAT3x2, INT_VEC4, 他にもいくらでも組み合わせが考えられる. *sizeにはsizeof(*type) が返る. FLOAT_MAT3x2ならsizeは6となる.

クエリすることができる: int GetAttribLocation(uint program, const char *name);でインデックスが取得できる. null terminated. これもリンク後に行う必要がある. activeでなかったりエラーなら-1が返る.

void BindAttribLocation(uint program, uint index, const char *name) では変数名に対するindexを指定できる. これはリンク前に行う.この方法以外にも, GLSLでindexを指定することもできこちらの方が楽だろう. indexはてんでばらばらではなく, matrixなどはcolumnどうしで隣接して並んでいなくてはならない. よってこの割り当てができないようなindexの指定の仕方をするとlinkできなくなる. GLSLでもBindAttribLocationでもindexが指定されている時は, GLSLの方を優先する. 実は, shader objectをattachする前や, プログラムに使われていない変数名に対してもBindAttibLocationで割り当てることができる. 使われていない変数名があるなら無視される.

Uniform Variables に対しては, buffer objectはnamed uniform blockを持ちuniform variableにはuniform block indexが割り当てられる. named uniform block外のものはdefault uniform blockに割り当てられる.

default uniform block内のniform variableのindexはint GetUniformLocation(uint program, const char *name); で取得できる.

named uniform blockも名前からblockのindexを取得できる: uint GetUniformBlockIndex(uint program, const char *uniformBlockName); void GetActiveUniformBlockName(uint program, uint uniformBlockIndex, sizei bufSize, sizei *length, char *uniformBlockName); でactive uniform block's nameを取得できる.

void GetActiveUniformBlockiv(uint programm, uint uniformBlockIndex, enum pname, index *params); でactive uniform blockの情報が引き出せる.

void GetUniformIndices(uint program, sizei uniformCount, const char **uniformNames, uint *uniformIndices); でuniformCount個のuniform variableのインデックスをまとめて取得できる.

void GetActiveUniformName(uint program, uint uniformIndex, sizei bufSize, sizei *length, char *uniformName); でuniformIndexに対応する変数名とその長さを取得できる.

void GetActiveUniform(uint program, uint index, sizei bufSize, sizei *length, int *size, enum *type, char *name);

void GetActiveUniformsiv( uint program, sizei uniformCount, const uint *uniformIndices, enum pname, int *params );

uniform variableに値をロードするには, 次のようなコマンドを使う: void Uniform{1234}{if}(int location, T value); void Uniform{1234}{if}v(int location, T value); void UniformMatrix{234}fv(int location, sizei count, boolean transpose, const float *value); void UniformMatrix{2x3,3x2,2x4,4x2,3x4,4x3}fv( int location, sizei count, boolean transpose, const float *value ); vつきの命令は配列に対してロードするのでロードしたい長さを指定している.

今更だが, 行列の要素は(列, 行)で考えており一般的なものとは逆だ. これは行列をcolumnを並べたものと見れば良い. 最初のインデックスでcolumn指定, そしてrowを指定.

void UniformBlockBinding(uint program, uint uniformBlockIndex, uint uniformBlockBinding); でuniform blockのbinding pointを指定することができる.

Samplerはtexture objectのuniform variableであり, texture image unitを値として持ち, この値でtextureは識別される. GindTextureでtargetにbindできる. GetUniformLocationでsamplerの場所をクエリできる.

void TransformFeedbackVeryings(uint program, sizei count, const char **varyings, enum bufferMode); でレコード. bufferModeはINTERLEAVED_ATTRIBS ofr SEPARETE_ATTRIBS.

void GetTransformFeedbackVarying(uint program, uint index, sizei bufSize, sizei *length, sizei *size, enum *type, char *name);

vertex shaderで処理されたvertexは, geometry shaderがactiveならそこへ渡され, そうでないならば次が行われる:

Shader Inputs: vertex attributes, uniform variablesに加え, vertex shaderはgl_VertexIDとgl_InstanceIDというbuilt-in , Read Onlyの変数にアクセスできる.

gl_VertexIDはDrawArraysなどDraw命令で暗黙に渡されるindexを指す.

gl_InstanceIDはcurrent primitiveのindexを持つ.

Shader Outputs: ユーザー定義のvarying variableに書き込める. built-in special variable gl_Positionはhomogeneous vertex positionを持つ. gl_ClipDistanceやgl_PointSizeなど他にもbuilt-inのものがある.

2.12 Geometry Shaders