OpenGL Shading Language 4.60 Specification 読書ノート Part 4¶
4. Variables and Types¶
変数と関数はすべて使用する前に宣言する必要がある。変数や関数の名前とは識別子だ。
既定の型はない。変数と関数の宣言にはすべて、宣言された型が必要で、修飾子がオプションだ。変数を宣言するには、型を指定した後、カンマで区切って一つ以上の名前を指定する。多くの場合、代入演算子 = を使って、宣言の一部として変数を初期化することができる。
ユーザー定義型は、既存の型のリストを一つの名前に集約するために struct を使って定義することができる。
OpenGL Shading Language は型安全だ。型の間には暗黙の変換がある。どのような場合にどのような変換が行われるかについては、4.1.10. Implicit Conversions で述べられ、本仕様書の他の項からも参照されている。
読者ノート
おそらく仕様を C/C++ に寄せている。
4.1. Basic Types¶
基本型 (a basic type) とは当言語のキーワードにより定義されている型だ。
OpenGL Shading Language では、以下のように基本型を分類している:
Transparent Types:
void,bool,int, etc.Floating-Point Opaque Types:
sampler1D,texture1D,image1D, etc.Signed Integer Opaque Types:
isampler1D,itexture1D,iimage1D, etc.Unsigned Integer Opaque Types:
usampler1D,utexture1D,uimage1D, etc.Sampler Opaque Types:
sampler,samplerShadow.
これに加え、これらの基本型を配列や構造体を使って集約し、より複雑な型を構築することができる。
ポインター型はない。
本仕様書では、集約型 (an aggregate) は構造体や配列を意味する。行列やベクトルはそれ自体は集約型ではない。集約型、行列、ベクトルを総称して 合成 (composites) と呼ぶ。
4.1.1. Void¶
値を返さない関数は void として宣言しなければならない。既定の戻り値の型はない。キーワード void は他の宣言では使用できない(空の仮引数リストや実引数リストを除く)。さもないとコンパイルエラーになる。
4.1.2. Booleans¶
真偽型 (a boolean type) とは bool, bvec2, bvec3, bvec4 の任意のいずれかだ。
コードの条件付き実行を表現しやすくするために、真偽型を対処している。
ハードウェアがこの型の変数を直接対処することは想定していない。
これは本物の真偽型であり、真か偽かの二つの値のうちただ一つを保持する。
キーワード
trueとfalseがリテラルな定数として使用できる。条件付きジャンプ (
if,for,? :,while,do…while) に使われる式は真偽型に評価されなければならない。
4.1.3. Integers¶
整数型 (an integral type) とは、符号あり、符号なし、スカラー、ベクトル整数型のことだ。配列や構造体は含まれない。
スカラー整数型 (a scalar integral type) とは、符号ありまたは符号なし整数型のスカラーだ。
ベクトル整数型 (a vector integral type) とは、符号ありまたは符号なしの整数のベクトルだ。
符号ありおよび符号なしの整数変数が完全に対処されている。この文書では、整数という言葉は、符号あり整数と符号なし整数の両方を一般的に意味する。
OpenGL では、符号なし整数は正確に 32 ビットの精度を持つ。
OpenGL では、符号あり整数は、符号ビットを含む 32 ビットを 2 の補数形式で使用する。
加算、減算、および乗算でオーバーフローまたはアンダーフローが発生した場合は、正しい結果 R の低次 32 ビットが得られる。ここで R はオーバーフローまたはアンダーフローを回避するのに十分な精度で計算される。オーバーフローの結果となる除算は、未定義の値となる。
C 言語と同様にしてリテラル整数定数を十進数、八進数、16 進数で表すことができる。
ビットパターンが 32 ビットに収まらないリテラル整数を用意すると、コンパイルエラーになる。リテラルのビットパターンは常に変更されずに使用される。そのため、ビットパターンに符号ビットが設定されている符号ありリテラルは負の値を生じる。
読者ノート
仕様書の例をよく見ておくこと。例えば -1u はどんな値であるか言えるようになること。
4.1.4. Floats¶
一般に、浮動小数点という言葉は、単精度浮動小数点と倍精度浮動小数点の両方を指す。
浮動小数点変数は、以下の例のように定義される:
float a, b = 1.5; // single-precision floating-point
double c, d = 2.0LF; // double-precision floating-point
いずれかの処理単位への入力値として、単精度または倍精度の浮動小数点変数は、精度とダイナミックレンジについて、対応する IEEE 754 浮動小数点定義と一致することが期待される。シェーダー内の浮動小数点変数も、単精度浮動小数点値の IEEE 754 仕様に従って符号化される(物理的には必ずしもそうではないが、論理的には)。
符号化は論理的には IEEE 754 だが、演算は必ずしも IEEE 754 の要求通りには行われない。精度や
NaN,Infの使い方の詳細については 4.7.1. Range and Precision を参照。
指数部が存在する場合は、小数点
.は必要ない。接尾辞の前も含め、浮動小数点定数の中には空白を入れてはいけない。
トークン化の際には、仕様書の BNF による定義に合致する最大のトークンが認識されてから新しいトークンが開始する。
接尾辞
lfまたはLFがある場合、リテラルの型はdoubleだ。それ以外の場合、リテラルはfloat型だ。先頭の単項マイナス記号
-は単項演算子として解釈され、浮動小数点定数の一部ではない。
読者ノート
浮動小数点数とダイナミックレンジの関係を別途理解しておくこと。
4.1.5. Vectors¶
浮動小数点値、整数値、真偽値の 2, 3, 4 成分それぞれの汎用ベクトルのデータ型がある。
浮動小数点ベクトル変数は、色、法線、位置、テクスチャー座標、テクスチャー検索結果などを格納するのに使用できる。
真偽値のベクトルは、数値のベクトルの成分ごとの比較に使用できる。
vec2 texcoord1, texcoord2;
vec3 position;
vec4 myRGBA;
ivec2 textureLookup;
bvec3 less;
ベクトルの初期化はコンストラクターで行う。5.4.2. Vector and Matrix Constructors に記述がある。
4.1.6. Matrices¶
2×2, 2×3, 2×4, 3×2, 3×3, 3×4, 4×2, 4×3, 4×4 の浮動小数点数の行列の型が組み込まれている。
matで始まる行列型は単精度成分を持ち、dmatで始まる行列型は倍精度成分を持つ。型の最初の数字は列数、二番目の数字は行数をそれぞれ表す。数字が一つしかない場合、それは正方行列だ。
mat2 mat2D;
mat3 optMatrix;
mat4 view, projection;
mat4x4 view; // an alternate way of declaring a mat4
mat3x2 m; // a matrix with 3 columns and 2 rows
dmat4 highPrecisionMVP;
dmat2x4 dm;
行列の値の初期化は、コンストラクターを用いて、列優先順 (column-major order) で行われる。
読者ノート
行列コンストラクターには列ベクトルを渡すような書き方をする。ただし、テキストエディター上では列ベクトルの要素を一行で書くことになるはずだから、そこで混乱しないようにする。
4.1.7. Opaque Types¶
不透明型 (an opaque type) とは、型であって、その内部構造が言語から隠されているものだ。
不透明型は他のオブジェクトへの不透明なハンドルとなる変数を宣言する。これらのオブジェクトは、宣言された変数を直接読み書きするのではなく、組み込み関数を通してアクセスされる。これらのオブジェクトは、関数の引数または uniform 修飾された変数としてしか宣言できない。メモリー修飾子を取る不透明な型は image 型だけだ。配列の添字、構造体のメンバーの選択、括弧を除き、不透明変数は式のオペランドになることはできず、そのような使い方をするとコンパイルエラーになる。
不透明な変数は左辺値として扱うことができない。したがって、out または
inout の関数引数として使用することも、代入することもできない。このような使い方をすると、コンパイルエラーになる。ただし、型と記憶修飾子が一致する in 引数として渡すことは可能だ。また、初期化子を付けて宣言することはできない。
単一の不透明型宣言ではハンドル自身と、そのハンドルとなるオブジェクトの二つのオブジェクトが効果的に宣言されるため、格納修飾子とメモリー修飾子の両方を使用する余地がある。格納修飾子は不透明ハンドルを修飾し、メモリー修飾子はハンドルであるオブジェクトを修飾する。
Texture-Combined Samplers¶
テクスチャー混合採取器型 (texture-combined sampler types) とは、 4.1. Basic Types の表(注:仕様書の本物を参照)に記載されている、テクスチャーにアクセスするためのハンドルとなる採取器型のことだ。
samplerやsamplerShadowは含まれない。
テクスチャー対象ごとに、また、float, integer, unsigned integer の各データ型ごとに、異なるテクスチャー混合採取器型がある。テクスチャーへのアクセスは、組み込みテクスチャー関数 (8.9. Texture Functions) を使って行われるが、どのテクスチャーにアクセスするか、またどのようにフィルターリングするかを指定するために、テクスチャー混合採取器型が使われる。
テクスチャー混合採取器型は不透明型で、前述の不透明型と同様に宣言され、動作する。シェーダー内で配列に集約される場合、動的一様な整数式でしかインデックスを付けることができず、そうでない場合、結果は未定義となる。
Images¶
画像型は不透明型で、上記の不透明型と同様に宣言され、動作する。さらにメモリー修飾子で修飾することができる。シェーダー内で配列に集約される場合、動的一様な整数式でしかインデックスを付けることができず、そうでない場合、結果は未定義となる。
画像変数は、画像単位に束縛されたテクスチャー画像の単一レベルのすべてまたは一部に対応する 1, 2, 3 次元のいずれかの画像のハンドルだ。
画像変数はテクスチャー対象ごとに、また、float, integer, unsigned
integer の各データ型ごとに、異なる型がある。画像へのアクセスは、レベルが画像単位に束縛されているテクスチャーの対象に一致する画像型を使用する必要がある。もしくは、3D または配列画像の非レイヤー束縛の場合は、画像のレイヤーの次元に一致する画像型を使用する必要がある。
つまり、3D, 2DArray, Cube, CubeArray のレイヤーは
image2Dを使用し、1DArray のレイヤーは
image1Dを使用し、2DMSArray のレイヤーは
image2DMS
をそれぞれ使用する必要がある。このように画像対象型が束縛された画像と一致しない場合、データ型が束縛された画像と一致しない場合、フォーマットレイアウト修飾子が OpenGL 仕様 8.25 Texture Image Loads and Stores に記載されている画像単位表現形式と一致しない場合、画像アクセスの結果は未定義となるが、プログラムの終了を含むことはできない。
画像変数は 8.12. Image Functions で記述されている画像のロード、格納、不可分関数において、アクセスする画像を指定するために用いられる。
Atomic Counters¶
不可分計数器型とは、計数器への不透明なハンドルであって、前述の不透明型と同様に宣言され、動作するものだ。宣言された変数は、8.10. Atomic Counter Functions で記述がある、組み込み不可分計数器関数を使用するときに、どの計数器にアクセスするかを指定する。また、4.4.7. Atomic Counter Layout Qualifiers で記述があるように、バッファーに束縛される。
シェーダー内の配列に集約された不可分計数器は、動的一様な整数式でしかインデックスを付けることができない。そうでない場合、結果は未定義だ。
構造体のメンバーは不可分計数器型として宣言できない。
Texture, sampler, and samplerShadow Types¶
テクスチャー、採取器、samplerShadow 型は不透明型で、上記の不透明な型と同様に宣言され、動作する。シェーダー内で配列に集約されている場合、これらの型は動的一様な式でしかインデックスを付けることができない。そうしないとテクスチャー検索は未定義の値になる。
テクスチャー変数は、4.1. Basic Types の表に列挙されている一次元、二次元、三次元のテクスチャー、キューブマップなどのハンドルだ。テクスチャー型は、テクスチャー対象ごとに、また、float, integer, unsigned integer の各データ型ごとに異なる。
テクスチャーは、採取器型または samplerShadow 型の変数と組み合わせて、テクスチャーを混合した採取器型 (sampler2D, sampler2DShadow, etc.) を作ることができる。これはコンストラクターで行う。例えば、
sampler2D(texture2D, sampler),sampler2DShadow(texture2D, sampler),sampler2DShadow(texture2D, samplerShadow),sampler2D(texture2D, samplerShadow)
などだ。詳細は 5.4. Constructors で述べる。
Subpass Inputs¶
サブパス入力型 (e.g. subpassInput) は不透明型で、上記の不透明型と同様に宣言され、動作するものだ。シェーダ内ーで配列に集約される場合、動的一様な整数式でしかインデックスを付けることができず、そうでない場合、結果は未定義となる。
サブパス入力型は、二次元の単一採取または複数採取された画像を扱うもので、
float, integer, unsigned integer のデータ型ごとに異なる型がある。
サブパス入力型は、断片シェーダーでのみ利用可能だ。他の段階での使用はコンパイルエラーとなる。
読者ノート
名詞 subpass の概念が(というより pass のそれが)わからないので、わかるまでこのままカタカナにしておく。
4.1.8. Structures¶
C/C++ 言語の構造体 struct と同じようなことができる:
struct light {
float intensity;
vec3 position;
} lightVar;
light lightVar2;
ただし、C/C++ のそれよりは弱い:
構造体には少なくとも一つのメンバー宣言が必要。
メンバー宣言には精度修飾子を付けることができるが、それ以外の修飾子を使用するとコンパイルエラー。
ビットフィールドは対処されていない。
メンバー型はすでに定義されている必要がある(前方参照不可)。
メンバー宣言に初期化子が含まれているとコンパイルエラー。
メンバー宣言には、配列を含めることができる。配列にはサイズが指定されていなければならない。サイズはゼロより大きい定数の整数表現でなければならない (4.3.3. Constant Expressions)。
各レベルの構造体は、メンバー宣言子で指定された名前に対して独自の名前空間を持つ。このような名前は、その名前空間内で一意であればよい。
匿名構造体は対処されていない。
埋め込み構造体の定義は対処されていない。コンパイルエラーになる。
struct S { float f; }; struct T { S; // Error: anonymous structures disallowed struct { ... }; // Error: embedded structures disallowed S s; };
構造体をコンストラクターを使用して宣言時に初期化することができる (5.4.3. Structure Constructors)。
型や修飾子の使用に関するあらゆる制限は、その型や修飾子のメンバーを含むすべての構造体にも適用される。これは、構造体である構造体メンバーにも再帰的に適用される。
4.1.9. Arrays¶
細かいことが色々と仕様化されている。
同じ型の変数は、名前の後に大括弧
[ ]でそのサイズを囲んで宣言することで、配列に集約することができる。宣言の中で配列のサイズを指定する場合、それはゼロより大きい定数整数式 (4.3.3. Constant Expressions) でなければならない。
シェーダー格納ブロック (4.3.9. Interface Blocks) の最後に宣言されたメンバーを除き、配列のサイズは、一定の整数式以外でインデックスを付ける前に宣言 (explicitly sized) しなければならない。
任意の配列のサイズは、それを関数の引数として渡す前に宣言しなければならない。これらの規則に違反すると、コンパイルエラーが発生する。
サイズを指定しないで配列を宣言 (unsized) し、後で同じ名前の配列を同じ型の配列として再宣言してサイズを指定するか、定数整数式のみでインデックスを付ける (implicitly sized) ことは適法だ。ただし、特に断りのない限り、ブロックを再宣言することはできない。
ユーザーが宣言したブロック内のサイズのない配列メンバーは、ブロックの再宣言によってサイズを変更することはできない。
サイズを指定して配列を宣言し、後で(同じシェーダー内で)宣言されたサイズ以上の定数整数式で同じ配列をインデックスするとコンパイルエラー。
サイズのない配列を再宣言して、その配列のインデックスとしてシェーダ内で以前に使用されたインデックスと同等かそれ以下のサイズにすることは、コンパイルエラー。また、負の定数式で配列をインデックスすることもコンパイルエラー。
関数宣言で仮引数として宣言された配列は、サイズを指定しなければならない。
配列のサイズ以上、または 0 未満の非定数式で配列をインデックスすると、未定義の動作となる。
配列は一次元しか持たないが、配列の配列を宣言することができる。
すべての型(基本型、構造体、配列)を配列にすることができる。
すべての配列は本質的に同質 (homogeneous) であり、すべて同じ型とサイズの要素で構成されているが、例外が一つある。サイズのない配列を最後のメンバーとして持つシェーダー格納ブロックだ (runtime-sized)。このようなシェーダー格納ブロックからは、格納ブロックの最後のメンバーの長さが異なっていたとしても、配列を形成することができる。
float frequencies[3];
uniform vec4 lightPosition[4];
light lights[];
const int numLights = 2;
light lights[numLights];
// a shader storage block, introduced in section 4.3.7 "Buffer Variables"
buffer b {
float u[]; // an error, unless u gets statically sized by link time
vec4 v[]; // okay, v will be sized dynamically, if not statically
} name[3]; // when the block is arrayed, all u will be the same size,
// but not necessarily all v, if sized dynamically
配列型は、非配列型の後に配列指定子を指定することで形成できる。このような配列指定子の次元すべてにサイズを含める必要がある。
float[5] // an array of size [5] of float
float[2][3] // an array of size [2][3] of float, not size [3] of float[2]
この型は、他の型が使用できる場所であればどこでも使用できる。関数からの戻り値であってもいい:
float[5] foo() { }
配列のコンストラクターとして:
float[5](3.4, 4.2, 5.0, 5.2, 1.1)
名前なし引数として:
void foo(float[5])
または変数や関数の引数パラメータを宣言する手段の代わりとしても使用できる。
float[5] a;
配列は、配列コンストラクターから形成される初期化子を持つことができる:
float a[5] = float[5](3.4, 4.2, 5.0, 5.2, 1.1);
float a[5] = float[](3.4, 4.2, 5.0, 5.2, 1.1); // same thing
配列の配列を宣言することができる。次の宣言はどれも vec4 の長さ 2 の一次元配列の長さ 3 の一次元配列だ:
vec4 a[3][2]; // size-3 array of size-2 array of vec4
vec4[2] a[3]; // size-3 array of size-2 array of vec4
vec4[3][2] a; // size-3 array of size-2 array of vec4
透過的なメモリー(一様ブロックなど)では、最内周(宣言では右端)の次元が外周の次元よりも速く反復されるレイアウトになっている。上記の配列の場合、メモリー上の順序は次のようになる:
Low address : a[0][0] : a[0][1] : a[1][0] : a[1][1] : a[2][0] : a[2][1] : High address
コンストラクターと名前なしの引数の両方に必要な a の型は vec4[3][2] となる:
vec4 b[2] = vec4[2](vec4(0.0), vec4(0.1));
vec4[3][2] a = vec4[3][2](b, b, b); // constructor
void foo(vec4[3][2]); // prototype with unnamed parameter
初期化子リスト構文を使って配列の配列を初期化することもできる:
vec4 a[3][2] = { vec4[2](vec4(0.0), vec4(1.0)),
vec4[2](vec4(0.0), vec4(1.0)),
vec4[2](vec4(0.0), vec4(1.0)) };
サイズなし配列を宣言時に初期化子で明示的にサイズを指定することができる:
float a[5];
...
float b[] = a; // b is explicitly size 5
float b[5] = a; // means the same thing
float b[] = float[](1,2,3,4,5); // also explicitly sizes to 5
しかし、サイズなし配列に代入するのはコンパイルエラーとなる。これは、初期化子と代入が異なるセマンティクスを持っているように見えるまれなケースだ。配列の配列では、サイズなし次元は初期化子によってサイズが明示的に決まる:
vec4 a[][] = { vec4[2](vec4(0.0), vec4(1.0)), // okay, size to a[3][2]
vec4[2](vec4(0.0), vec4(1.0)),
vec4[2](vec4(0.0), vec4(1.0)) };
配列は length() メソッドを使って含まれる要素の数を取得できる:
float a[5];
a.length(); // returns 5
配列のサイズが明示的に決められている場合、
length()が返す値は定数式だ。配列のサイズが明示的に設定されておらず、シェーダー格納ブロックの最後に宣言されたメンバーである場合、戻り値は定数式ではなく、ブロックに対して格納所を用意するバッファーオブジェクトのサイズに基づいて実行時に決定される。このような配列は runtime sized だ。Runtime sized 配列において、配列が 0 より小さいか、配列内のブロック数以上の非定数式でインデックス付けられたシェーダー格納ブロックの配列に含まれている場合、
length()の戻り値は未定義だ。
length() を runtime sized ではなく、かつ明示的にサイズが設定されていない配列に対しては呼び出すことができない。コンパイルエラーとなる。
length() がコンパイル時定数を返す場合、length() が適用される式は、式中の左辺値への書き込みや、それ自体が副作用を持つ関数呼び出しなどの副作用を含めることはできない。コンパイル時定数 length 自体しか計算する必要はない。
式に他の効果が含まれている場合、コンパイルエラー報告を含め、動作と結果は未定義だ。
float a, b;
const int s = float[2](a=3.0, ++b).length(); // illegal side effects
length() は配列の配列に対しても同様に機能する:
vec4 a[3][2];
a.length() // this is 3
a[x].length() // this is 2
length() がコンパイル時定数を返す場合、括弧 [ ] 内の式は解析され、配列のインデックスに必要な規則に従うものの、配列を逆参照しない。このように、式に副作用がない限り、式の実行時の値が範囲外であっても、動作はきちんと定義される。
length() が(コンパイル時定数ではなく)実行時の値を返すと、配列を逆参照する。例えば、x がコンパイル時定数ではなく、範囲外の場合は、未定義の値を返す。より一般的には、関係する式すべてが完全に評価され、実行される。
// for a compile time-sized array b containing a member array a:
b[x+3].a.length(); // b is never dereferenced, x+3 is evaluated and checked
b[++x].a.length(); // not allowed; results are undefined
// for an array s of a shader storage object (run-time sized) containing a member array a:
s[++x].a.length(); // s is dereferenced; ++x needs to be a valid index
暗黙的なサイズの配列や実行時サイズの配列では、最も外側の次元しかサイズを欠落することができない。不明な配列サイズを含む型は、明示的なサイズが得られるまで配列を形成することができない。ただし、シェーダー格納ブロックの場合は、唯一であるサイズなし配列メンバーがブロックの最後のメンバーになる。
シェーダー格納ブロックでは、最後のメンバーは明示的なサイズを指定せずに宣言することができる。この場合、実効的な配列サイズは、インターフェイスブロックをバックアップするデータ格納場所のサイズから実行時に推論される。このような実行時サイズの配列は、一般的な整数式でインデックスを付けることができる。ただし、関数の引数として渡したり、負の定数式でインデックスを作成したりするとコンパイルエラーとなる。
読者ノート
ゴチャゴチャした節だが、配列の理論はレンダリングの観点から明らかに重要だ。読み落としはいけない。
4.1.10. Implicit Conversions¶
状況によっては、式とその型が暗黙的に異なる型に変換されることがある。許されるすべての暗黙の変換が表に示されている。
暗黙の変換は、コンストラクターを使った明示的な変換と同じだ。コンストラクターによる明示的な変換については:ref:khronos18-5.4.1 にある。
二項演算子の暗黙の変換を行う場合、オペランド二つが変換できるデータ型が複数ある場合がある。例えば int 型の値を uint 型の値に加算する場合、両方の値が
uint, float, double に暗黙的に変換される可能性がある。
このような場合、どちらかのオペランドが浮動小数点型であれば、浮動小数点型が採用される。
また、どちらかのオペランドが符号なし整数型であれば、符号あり整数型が採用される。
それ以外の場合は、符号あり整数型が採用される。
オペランドが同じ基本データ型から派生する複数のデータ型に暗黙的に変換できる場合は、構成要素のサイズが最も小さいデータ型が採用される。
4.1.11. Initializers¶
宣言には、変数の初期値を指定することができ、等号 = の後に初期化子を指定する。初期化子 (an initializer) とは、assignment-expression であるか、中括弧
{ } で囲まれた初期化子のリストのどちらかをいう。
読者ノート
仕様書の BNF によると、モダン C++ のそれと同じと考えて良さそうだ。
assignment-expression は通常の式であって、括弧の外にあるカンマ , は初期化子の終わりと解釈される。これにより入れ子になった初期化子を記述することができる。変数型型とその初期化子は、入れ子、各レベルに存在する成分・要素・メンバーの数・型の点で正確に一致しなければならない。大域スコープの assignment-expression には、ユーザー定義関数の呼び出しを含めることができる。
初期化子にある assignment-expression は、初期化するオブジェクトと同じ型であるか、4.1.10. Implicit Conversions に従ってオブジェクトの型に変換できる型でなければならない。コンストラクターを含むため、合成変数はコンストラクターでも初期化リストでも初期化でき、初期化リストの要素をコンストラクターにすることもできる。
初期化子が中括弧で囲まれた初期化子リストの場合、宣言する変数はベクトル、行列、配列、構造体のいずれかでなければならない。
int i = { 1 }; // illegal, i is not a composite
釣り合う中括弧で囲まれた初期化子のリストが合成体一つに適用される。これは、宣言されている変数、または宣言されている変数に含まれる合成体であってかまわない。初期化子リストの個々の初期化子は、合成体の要素・メンバーに対してその順に適用される。
合成体がベクトル型の場合、リストの初期化子は、ベクトルの成分に成分 0 から順に適用される。初期化子の数は成分の数と一致しなければならない。
合成体が行列型の場合、リストの初期化子はベクトルの初期化子でなければならず、行列の列に、列 0 から順に適用される。初期化子の数は行列の列数と一致しなければならない。
合成体に構造体型がある場合、リストの初期化子は、構造体で宣言されている順序で、最初のメンバーから順に構造体のメンバーに適用される。初期化子の数はメンバーの数と一致しなければならない。
これらの規則が適用されるので、次の宣言はすべて同値だ:
mat2x2 a = mat2( vec2( 1.0, 0.0 ), vec2( 0.0, 1.0 ) );
mat2x2 b = { vec2( 1.0, 0.0 ), vec2( 0.0, 1.0 ) };
mat2x2 c = { { 1.0, 0.0 }, { 0.0, 1.0 } };
次の宣言はすべてコンパイルエラーだ:
float a[2] = { 3.4, 4.2, 5.0 }; // illegal
vec2 b = { 1.0, 2.0, 3.0 }; // illegal
mat3x3 c = { vec3(0.0), vec3(1.0), vec3(2.0), vec3(3.0) }; // illegal
mat2x2 d = { 1.0, 0.0, 0.0, 1.0 }; // illegal, can't flatten nesting
struct {
float a;
int b;
} e = { 1.2, 2, 3 }; // illegal
いずれの場合も、オブジェクトに適用される最も内側の初期化子(つまり、中括弧で囲まれた初期化子のリストではない)は、初期化されるオブジェクトと同じ型であるか、 4.1.10. Implicit Conversions に従ってオブジェクトの型に変換できる型でなければならない。後者の場合、代入が行われる前に初期化子に対して暗黙の変換が行われる。
struct {
float a;
int b;
} e = { 1.2, 2 }; // legal, all types match
struct {
float a;
int b;
} e = { 1, 3 }; // legal, first initializer is converted
次の宣言はすべてコンパイルエラーだ:
int a = true; // illegal
vec4 b[2] = { vec4(0.0), 1.0 }; // illegal
mat4x2 c = { vec3(0.0), vec3(1.0) }; // illegal
struct S1 {
vec4 a;
vec4 b;
};
struct {
float s;
float t;
} d[] = { S1(vec4(0.0), vec4(1.1)) }; // illegal
サイズなし配列に初期化子(いずれかの形式)が提供されている場合、配列のサイズは、初期化子内のトップレベルの(入れ子になっていない)初期化子の数が決定する。以下の宣言はすべて、明示的に 5 要素を持つ配列を生成する:
float a[] = float[](3.4, 4.2, 5.0, 5.2, 1.1);
float b[] = { 3.4, 4.2, 5.0, 5.2, 1.1 };
float c[] = a; // c is explicitly size 5
float d[5] = b; // means the same thing
初期化される合成体の初期化子リストに含まれる初期化子の数に過不足があるとコンパイルエラーだ。つまり、配列の要素すべて、構造体のメンバーすべて、行列の列すべて、ベクトルの成分すべてには、厳密に一つの初期化子式が存在し、消費されない初期化子があってはならない。
4.2. Scoping¶
変数のスコープは、その宣言位置によって決まる。
すべての関数定義の外側で変数が宣言されている場合、それは大域スコープを持ち、宣言された場所から始まり、そのシェーダーの最後まで存続する。
while検定やfor文の中で宣言されている場合は、それに続く部分文の最後までスコープされる。if文やelse文の中で宣言されている場合は、その文の最後までスコープされる。6.2. Selection, 6.3. Iteration を参照。複文の中の文として宣言されている場合は、その複文の末尾にスコープされる。
関数定義の中で引数として宣言されている場合は、その関数定義の最後までスコープされる。
関数の引数宣言と本体は、大域スコープに入れ子になった単一のスコープを形成する。
if文の式では、新しい変数を宣言することができないため、新しいスコープを形成しない。
宣言の中では、名前のスコープは、初期化子がある場合はその直後から、ない場合は宣言されている名前の直後から始まる。
読者ノート
仕様書ではここに C/C++ プログラマーなら納得の行くコード例が挙げられている。
for ループも while ループも、部分文自体は変数名の新しいスコープを導入していないので、以下は再宣言のコンパイルエラーになる:
for ( /* nested scope begins here */ int i = 0; i < 10; i++) {
int i; // redeclaration error
}
do … while ループの本体では、本体が単文か複文かによらず、do と
while の間(判定式は含まない)だけ続く新しいスコープが導入される。
int i = 17;
do
int i = 4; // okay, in nested scope_
while (i == 0); // i is 17, scoped outside the do-while body
switch(...) に続く文は、入れ子スコープを形成する。
あるスコープ内のすべての変数名、構造体型名、関数名は同じ名前空間を共有する。関数名は、同じスコープ内で、同じまたは異なるパラメータでエラーなしで再宣言できる。暗黙的なサイズの配列は、同じ基本型の配列と同じスコープで再宣言できる。それ以外の場合、一つのコンパイル単位内では、宣言された名前を同じスコープで再宣言することはできない。再宣言するとコンパイルエラーになる。入れ子になったスコープが外側のスコープで使用されている名前を再宣言すると、その名前の既存の使用がすべて隠される。隠された名前にアクセスしたり、隠蔽を解除したりするには、隠蔽したスコープを終了しなければならない。
組み込み関数のスコープは、ユーザーが大域変数を宣言する大域スコープの外側にある。つまり、シェーダーの大域スコープは、ユーザー定義関数や大域変数を使えられ、組み込み関数を含むスコープの中に入れ子になっている。入れ子になったスコープで関数名を再宣言すると、外側のスコープでその名前で宣言された関数すべてが隠蔽される。関数の宣言(プロトタイプ)は、関数の内部には記述できない。大域スコープか、組み込み関数の場合は大域スコープの外側に記述しなければならず、記述しない場合はコンパイルエラーになる。
共有大域とは、同一言語(頂点などの同一段階)内で独立してコンパイルされた単位(シェーダー)内で同じ名前で宣言された大域変数で、単一のプログラムを作る際にリンクされるものだ(異なるシェーダー言語間のインターフェイスとなる大域については別の節で述べる)。共有大域は同じ名前空間を共有し、同じ型で宣言する必要がある。また、同じ格納域を共有する。
共有大域配列は、同じ基本型と同じ明示的なサイズでなければならない。あるシェーダーで暗黙的にサイズ設定された配列は、同じ段階の別のシェーダーで明示的にサイズ設定することができる。ある段階のどのシェーダーも配列の明示的なサイズを持っていない場合、その段階の最大の暗黙的なサイズ(使用されている最大のインデックスよりも 1 多い)が採用される。段階をまたいだ配列のサイズ変更はない。これは、配列が他の段階やアプリケーションと共有されるインターフェイスブロック内で宣言されている場合に関係する(他の未使用の配列は最適化により排除されてもかまわない)。
共有される大域スカラーは、厳密に同じ型名と型定義を持たなければならない。構造体は、同じ名前、一連の型名、型定義、およびメンバー名を持たなければ、同じ型とはみなされない。この規則は入れ子になった型や埋め込まれた型にも再帰的に適用される。共有大域に複数の初期化子がある場合、初期化子はすべて定数式であり、すべて同じ値でなければならない。そうでない場合は、リンクエラーが発生する(初期化子が一つしかない共有大域では、その初期化子を定数式にする必要はない)。
読者ノート
最後の 3 パラグラフについては、仕様書を全部読んでいない段階では、シェーダーを書くときに main の外側に置く変数が関係しているだろうと想像する。
4.3. Storage Qualifiers¶
変数宣言では、型の前に高々一つの格納修飾子を指定することができる。
- (格納修飾子なし:既定値)¶
局所的な読み書き可能なメモリー、または関数の入力引数。
const¶値を変更することができない変数。
in¶前の段階のシェーダーにリンケージを持つ場合、変数が入力コピーされる。
out¶シェーダの後段へにリンケージを持つ場合、変数が出力コピーされる。
attribute¶互換性プロファイルおよび頂点言語のみ。頂点シェーダーの場合は
inと同じ。uniform¶処理される基本形状間で値が変化しない、一様変数はシェーダー、API, アプリケーション間のリンクを形成する。
varying¶互換性プロファイルのみ、頂点言語および断片言語のみ。頂点シェーダーの場合は
outと同じ。断片シェーダーの場合はinと同じ。buffer¶値はバッファーオブジェクトに格納され、シェーダー呼び出しと API の両方で読み書きできる。
計算シェーダーのみ。変数格納は作業グループ内のすべての作業項目で共有される。
入出力修飾変数の中には、高々一つの補助格納修飾子を追加できるものがある。
すべての修飾子の組み合わせが許されるわけではない。補助格納修飾子は in または
out 修飾子と一緒にしか使用できない。その他の修飾子の規則については、以降の節で説明。
局所変数は const 修飾子のみ使用できる(または格納修飾子を使用しない)。
関数の引数には const, in, out を使用できるが、引数修飾子としては使用できないことに注意。引数修飾子については:ref:khronos18-6.1.1 を参照。
関数の戻り値の型や構造体のメンバーには格納修飾子を使用しない。
大域宣言の初期化子は、格納修飾子がないか、const 修飾子があるか、または
uniform 修飾子がある大域変数の宣言でしか使用できない。
格納修飾子のない大域変数は、その宣言またはアプリケーションで初期化されないと、初期化されずに未定義の値で main() に入る。
あるシェーダー段階の出力と後続のシェーダー段階の入力を比較するとき、補助修飾子(またはその欠落)が同じでない場合、入力と出力は一致しない。
4.3.1. Default Storage Qualifier¶
大域変数に修飾子がない場合、その変数はアプリケーションや他のパイプライン段階で実行されるシェーダーとはリンクしていない。大域変数でも局所変数でも、修飾されていない変数では、宣言は対象となる処理器に関連するメモリーが割り当てられているように見える。この変数は、この割り当てられたメモリーへの読み取り・書き込みアクセスを提供する。
読者ノート
意味不明。
4.3.2. Constant Qualifier¶
名前付きのコンパイル時定数や読み取り専用の変数は const 修飾子を使って宣言できる。const 修飾子は、非 void 透過基本データ型や、それらの構造体や配列で使用できる。宣言されていない const 変数に書き込むコードはコンパイルエラーとなるから、宣言時に初期化する必要がある。
const vec3 zAxis = vec3(0.0, 0.0, 1.0);
const float ceiling = a + b; // a and b not necessarily constants
構造体のメンバーは const で修飾することはできない。構造体変数は const として宣言し、構造体のコンストラクターまたは初期化子で初期化できる。
大域スコープでの const 宣言の初期化子は、次節で定義されるように、定数式でなければならない。
4.3.3. Constant Expressions¶
SPIR-V の特殊化定数は、4.11. Specialization-Constant Qualifier で記述されるように、GLSL では
const にレイアウト修飾子 const_id を付けて表現される。
定数式 (a constant expression) とは次のいずれかだ:
リテラル値
const修飾子と初期化子で宣言された変数で、初期化子が定数式であるもの。これはlayout(const_id = ...)のような特殊化定数レイアウト修飾子を付けて宣言された``const`` と、特殊化定数レイアウト修飾子を付けずに宣言されたconstの両方を含む。constとして修飾された組み込み変数。定数配列の要素、定数構造体のメンバー、定数ベクトルの構成要素の取得など、すべて定数式であるオペランドに対して演算子で形成される式。
オブジェクト自体が定数であるかどうかに関わらず、明示的なサイズのオブジェクトに対する
length()メソッドの適切な使用(暗黙的なサイズの配列や実行時サイズの配列は定数式を返さない)。引数がすべて定数式であるコンストラクター。
非特殊化定数の場合のみ:引数がすべて定数式である特定の組み込み関数の呼び出しの戻り値(少なくとも以下のリストを含む)。メモリーにアクセスしないその他の組み込み関数(テクスチャー探索関数、画像アクセス、不可分計数器などを除く)であって、戻り値の型が非
voidでout引数がなく、ノイズ関数ではないものも定数とみなされる場合がある。関数が特殊化定数である引数で呼び出された場合、その結果は定数式ではない。角度および三角関数
指数関数
普通の関数
幾何関数
ユーザー定義関数(非組み込み関数)の関数呼び出しは、定数式には使用できない。
定整数式 (a constant integral expression) とは定数式であって、スカラーの符号付きまたは符号なしの整数に評価されるものだ。
定数式は不変的な方法で評価されるため、複数のシェーダーに同じ定数式が現れた場合、同じ値が生成される。4.8.1. The Invariant Qualifier, 4.7.2. Precision Qualifiers を参照。
定数式は precision 修飾子と invariant 修飾子を尊重するが、そのような修飾子の使用とは関係なく、常に不変的に評価されるため、複数のシェーダーに同じ定数式が現れた場合には、同じ値が生成される。
定数式はホストプラットフォームで評価される可能性がある。それゆえ同じ式がシェーダー実行対象で評価するような値と同じものを計算する必要はない。ただし、ホストは対象が使用するのと同じか、それ以上の精度を使用しなければならない。精度修飾子が決定できない場合、式は highp で評価される。 4.7.3. Default Precision Qualifiers 参照。
特殊化定数式は、コンパイラーのフロントエンドでは評価されないで、代わりに、後でホスト上で評価するために必要な式の操作を留めておく。
4.3.4. Input Variables¶
シェーダーの入力変数は in 格納修飾子で宣言される。この変数は、API パイプラインの前段階と宣言したシェーダーとの間の入力インターフェイスを形成する。入力変数は大域スコープで宣言しなければならない。前のパイプライン段階から来る値は、シェーダー実行開始時に入力変数にコピーされる。入力として宣言された変数に書き込むコードはコンパイルエラーとなる。
前の段階で書き込む必要があるのは、静的に読み込まれる入力変数だけであり、入力変数の宣言が余計に付いていても構わない。
読者ノート
本文ではこのことをまとめた表がここに示されているが、割愛する。
消費エラーは静的な使用にしかよらない。未定義の値を消費する可能性のある動的な使用方法をコンパイラーが推論する場合、エラーではなく警告を発する。組み込み入力名の一覧は 7. Built-In Variables を参照。
頂点シェーダーの入力変数(または属性)は、頂点ごとのデータを受け取る。頂点シェーダー入力に補助格納修飾子や補間修飾子を使用すると、コンパイルエラーとなる。コピーされた値は API またはレイアウト識別子 location の使用により設けられる。
以下の型で頂点シェーダー入力を宣言するとコンパイルエラーとなる:
真偽型
不透明型
構造体
頂点シェーダーにおける入力宣言の例を示す:
in vec4 position;
in vec3 normal;
in vec2 texCoord[4];
グラフィックスハードウェアでは、頂点入力を渡すための固定のベクトル場所が足りないことが予想される。そのため、OpenGL Shading Language では、行列以外の入力変数は、そのようなベクトル位置を一つ使用すると定義している。使用できる場所の数には実装依存の制限があり、これを超えるとリンクエラーが発生する(静的使用でないと宣言された入力変数は、この制限に含まれない)。スカラー入力は vec4 と同じようにカウントされるので、アプリケーションでは、内在するハードウェアの性能をよりよく活用するために、関係のない 4 つの float 入力のグループをまとめてベクトルにすることを検討するとよいだろう。行列入力は、複数の場所を使用する。使用される場所の数は、行列の列数と同じだ。
細分化制御、細分化評価、幾何シェーダーの入力変数は、前のアクティブシェーダー段階で同じ名前の出力変数によって書き出された、頂点ごとの値を取得する。これらの入力では centroid や補間修飾子が使えるが、効果はない。細分化制御、細分化評価、幾何シェーダーは頂点集合を操作するので、各入力変数(または入力ブロック、下記のインターフェイスブロックを参照)は配列として宣言する必要がある。
in float foo[]; // geometry shader input for vertex "out float foo"
このような配列の各要素は、処理される基本形状の一つの頂点に対応する。各配列はオプションでサイズを宣言することができる。幾何シェーダーの場合、配列のサイズは、4.4.1. Input Layout Qualifiers のように、入力基本形状の型を設定する入力 layout
宣言によって設定される。
入力と出力は配列されているものもある。これは、シェーダー段階二つの間にあるインターフェイスでは、入力または出力の宣言を一致させるために、余分なレベルの配列インデックスが必要になることを意味する。例えば、頂点シェーダーと幾何シェーダーのインターフェイスでは、頂点シェーダーの出力変数と幾何シェーダーの入力変数の同名の変数は、型が一致していなければならない。ただし、幾何シェーダーは、頂点のインデックス付けを可能にするために、頂点シェーダーよりも 1 多い配列次元を持つことになる。このように配列されたインターフェイス変数が、必要である追加的入力・出力配列次元で宣言されていない場合、リンクエラーとなる。幾何シェーダー入力、細分化制御シェーダー入出力、細分化評価入力はすべて、他のシェーダー入出力に比べて追加的配列レベルを持っている。これらの入力と出力は、頂点ごとに配列された (per-vertex-arrayed) 入力と出力として知られている。配列されたインターフェイス
(gl_MaxTessControlInputComponents, etc.) の成分制限は、インターフェイス全体に対する制限ではなく、頂点ごとの制限だ。
非配列のインターフェイス(=段階間配列の次元が変わらない)では、入力変数が一致する出力変数と配列の次元を含めて同じ型で宣言されていないとリンクエラーとなる。
リンク時の型マッチング規則は、使用されているか否かに関わらず、宣言されたすべての入力変数と出力変数に適用される。
さらに、細分化評価シェーダーは patch および in 修飾子で宣言された
patch ごとの入力変数を対処している。パッチごとの入力変数には、細分化制御シェーダーによって書き込まれたパッチごとの出力変数の値が入る。パッチごとの入力は一次元配列として宣言できるが、頂点数によるインデックスは付けられない。入力への
patch 修飾子の適用は、細分化評価シェーダでしか行えない。他の入力変数と同様に、パッチごとの入力は、前の(細分化制御)シェーダー段階からのパッチごとの出力と同じ型と修飾子を使って宣言しなければならない。他の段階の入力で patch を使用することは、コンパイルエラーとなる。
細分化制御、細分化評価、幾何シェーダーの入力を、以下のいずれかの型で宣言するとコンパイルエラーとなる:
真偽型
不透明型
断片シェーダーの入力は、前の段階の出力から補間された断片ごとの値をふつうは取得する。補助格納修飾子 centroid と sample も、補間修飾子 flat,
noperspective, smooth` と同様に適用できる。
断片シェーダーの入力を次のいずれかの型で宣言するとコンパイルエラーとなる:
真偽型
不透明型
整数型または倍精度浮動小数点型であるか、またはそれを含む断片シェーダー入力には補間修飾子 flat が必要だ。
断片入力は以下の例のように宣言される:
in vec3 normal;
centroid in vec2 TexCoord;
invariant centroid in vec4 Color;
noperspective in float temperature;
flat in vec3 myColor;
noperspective centroid in vec2 myTexCoord;
断片シェーダーの入力は、頂点処理パイプラインにおける最終アクティブシェーダーとのインターフェイスを形成する。このインターフェイスでは、最終アクティブシェーダー段階出力変数と断片シェーダーの入力変数の同名の変数は、いくつかの例外(格納修飾子の一方は in で他方は out でなければならない)を除いて、型と修飾子が一致していなければならない。また、補間修飾子や補助修飾子も異なる場合がある。これらのミスマッチは任意の段階対の間で許される。補間修飾子や補助修飾子が一致しない場合は、断片シェーダーで提供される修飾子が前段階で提供される修飾子よりも優先される。断片シェーダーにそのような修飾子が全くない場合は、前段階で宣言されていた修飾子ではなく、既定の修飾子が使用される。つまり、重要なのは断片シェーダーで何が宣言されているかであり、前段階のシェーダーで何が宣言されているかではないということだ。
シェーダー段階間のインターフェイスが別々のプログラムオブジェクトのシェーダーを使って形成されている場合、プログラムがリンクされたときに入力と出力の間の不一致を検出することはできない。このようなインターフェイスでは、入力と出力の間に不一致があると、インターフェイスを介して渡される値は、部分的にまたは完全に未定義となる。
シェーダーで入出力レイアウト修飾子 (4.4.1. Input Layout Qualifiers, 4.4.2. Output Layout Qualifiers) を使用するか、ブロックや変数の入力宣言と出力宣言を同一にすることで、このようなインターフェイス間のマッチングを担保することができる。インターフェイスのマッチングに関する完全な規則は、OpenGL 仕様書の 7.4.1 “Shader Interface Matching” に記載されている。
計算シェーダはユーザ定義の入力変数を許可せず、他のシェーダー段階との正式なインターフェイスを形成しない。組み込みの計算シェーダー入力変数については 7.1.6. Compute Shader Special Variables を参照。計算シェーダーへの他のすべての入力は、画像ロード、テクスチャー取得、一様変数または一様バッファーからのロード、または他のユーザーコードによって明示的に取得される。計算シェーダーの組み込み入力変数を再宣言することはできない。
4.3.5. Uniform Variables¶
読者ノート
英単語 uniform をどう訳したらしっくり来るだろうか。数学科専攻としては「一様」にしたい。中国語の仕様書を見たら「統一」に相当する単語をあてているようだ。
修飾子 uniform は、処理される基本形状全体で値が同じになる大域変数を宣言するために用いられる。uniform 変数はすべて読み取り専用で、リンク時または API を通じて外部から初期化される。リンク時の初期値は、変数の初期化子が存在する場合はその値で、存在しない場合は 0 だ。不透明型は初期化子を持つことができず、そうでない場合はコンパイルエラーが発生する。
uniform vec4 lightPosition;
uniform vec3 color = vec3(0.7, 0.7, 0.2); // value assigned at link time
修飾子 uniform は、基本的なデータ型のいずれか、または構造体を型とする変数を宣言するとき、あるいはこれらのいずれかの配列を宣言するときに使用できる。
シェーダーの種類ごとに使用できる uniform 用変数の収容量には実装依存の制限がある。これを超えるとコンパイル時またはリンク時にエラーとなる。宣言されているが使用されていない uniform 変数はこの制限に入らない。ユーザー定義の uniform
変数の個数と、シェーダー内で使用されている組み込みの uniform 変数の個数の和で、利用可能な収容量を超えているかどうかを判断する。
シェーダー内の uniform 変数は、プログラムまたは分割可能なプログラムにリンクされている場合、すべて単一の大域名前空間を共有する。したがって、同じ名前で静的に使用される uniform 変数の型、初期化子、および任意の location 指定子は、単一プログラムにリンクされているすべてのシェーダーで一致しなければならない。ただし、リンクされたすべてのシェーダーで初期化子や location` 指定子を繰り返す必要はない。uniform 変数名がある段階(例:頂点シェーダー)で宣言され、別の段階(例:断片シェーダー)で宣言されていない場合、その名前は別の段階で別の用途に使用することが許される。
4.3.6. Output Variables¶
シェーダー出力変数は out 格納修飾子で宣言される。出力変数は、宣言したシェーダーと API パイプラインの後続段階との間の出力インターフェイスを形成する。出力変数は大域スコープで宣言しなければならない。シェーダーの実行中は、修飾のない通常の大域変数として振る舞う。シェーダーの終了時にその値は後続のパイプライン段階にコピーされる。後続のパイプライン段階で読まれる出力変数だけが書き込まれる必要があり、出力変数の余計な宣言があっても構わない。
単一の変数名をシェーダーの入力と出力の両方として宣言するための inout のような格納修飾子は存在しない。一つの変数に in と out の両方の修飾子をつけて宣言することもまたできない。コンパイル時またはリンク時にエラーとなる。出力変数は、入力変数とは異なる名前で宣言しなければならない。ただし、インスタンス名を持つインターフェイスブロックの中に入力または出力を入れ子にすると、ブロックのインスタンス名で参照されるものと同じ名前を使うことができる。
頂点、細分化評価、幾何の出力変数は、頂点ごとのデータを出力し、out 修飾子を用いて宣言される。出力への patch の適用は、細分化制御シェーダーでのみ可能だ。それ以外の段階での適用はコンパイルエラーとなる。
頂点、細分化評価、細分化制御、幾何それぞれのシェーダーの出力を、以下の型のいずれかで宣言するとコンパイルエラーとなる:
真偽型
不透明型
out vec3 normal;
centroid out vec2 TexCoord;
invariant centroid out vec4 Color;
flat out vec3 myColor;
sample out vec4 perSampleColor;
これらは 4.3.9. Interface Blocks で述べられるように、インターフェイスブロックにも出現する。インターフェイスブロックでは、頂点シェーダーから幾何シェーダーへのインターフェイスに、より単純に配列を追加することができる。また、断片シェーダーに、ある頂点シェーダーの幾何シェーダーと同じ入力インターフェイスを持たせることができる。
細分化制御シェーダーの出力変数は、頂点ごとのデータとパッチごとのデータを出力するために用いられる。頂点ごとの出力変数は配列され (4.3.4. Input Variables)、out
修飾子で patch 修飾子なしに宣言される。パッチごとの出力変数は patch 修飾子と out 修飾子で宣言される。
細分化制御シェーダーは、複数の頂点からなる配列された基本形状を生成するため、各頂点ごとの出力変数(または出力ブロック)は、配列として宣言する必要がある:
out float foo[]; // feeds next stage input "in float foo[]"
このような配列の要素それぞれが、生成される基本形状一つの頂点に対応する。各配列はオプションでサイズを宣言することができる。配列のサイズは出力パッチの頂点の数を定める出力レイアウト宣言によって設定される (Tessellation Control Outputs)(か、宣言されている場合は、それと一致しなければならない)。
各細分化制御シェーダー呼び出しは、対応する出力パッチの頂点を持ち、その対応する頂点に属しているときに限り、頂点ごとの出力に値を割り当てることができる。頂点ごとの出力変数を左辺値として使用する場合、頂点のインデックスを示す式が識別子
gl_InvocationID でないと、コンパイルエラーまたはリンクエラーになる。
同じ入力パッチに対する他の呼び出しに対する細分化制御シェーダー呼び出しの相対的な実行順序は、組み込み関数 barrier() が使用されない限り、未定義だ。これにより、相対的な実行順序の制御が可能になる。シェーダーの呼び出しが barrier() を呼び出すと、他のすべての呼び出しが同じ実行箇所に到達するまでその実行は一時停止する。barrier() を呼び出す前に実行された任意の呼び出しによって実行された出力変数の割り当ては、barrier() の呼び出しが戻った後、他の任意の呼び出しから見えるようになる。
細分化制御シェーダーの呼び出しは、障壁間で未定義の順序で実行されるので、頂点ごと、あるいはパッチごとの出力変数の値は時々未定義になる。シェーダー実行の開始と終了、および barrier() の各呼び出しを同期時点と考えるといい。出力変数の値が不定になるのは、以下の三つの場合のいずれかだ:
実行の開始時
以下の場合を除く各同期時点:
前回の同期時点の後に値が定義され、その後どの呼び出しによっても書き込まれなかった場合、または
前回の同期時点以降、厳密に一つのシェーダー呼び出しによって値が書き込まれた場合、または
前回の同期時点以降に複数のシェーダー呼び出しによって値が書き込まれ、そのようなすべての呼び出しによって実行された最後の書き込みが同じ値を書き込んだ場合。
シェーダーの呼び出しによって読み込まれたとき、もし
その値が前回の同期時点で未定義であり、その後同じシェーダー呼び出しによって書き込まれていない場合、または
前回と次回の同期時点の間に他のシェーダー呼び出しによって出力変数が書き込まれた場合(その割り当てが読み取り後のコードで発生したときでさえ)
断片出力は、断片ごとのデータを出力し、out 修飾子で宣言される。断片シェーダー出力の宣言に補助格納修飾子や補間修飾子を使用するとコンパイルエラーとなる。断片シェーダー出力を以下の型で宣言するとコンパイルエラーとなる:
真偽型
倍精度スカラーまたはベクトル (
double,dvec2,dvec3,dvec4)不透明型
行列型
構造体
断片出力宣言の例:
out vec4 FragmentColor;
out uint Luminosity;
計算シェーダーは組み込みの出力変数を持たず、ユーザー定義の出力変数も対処せず、他のシェーダー段階との正式なインターフェイスを形成しない。計算シェーダーからの出力はすべて、画像格納や不可分計数器の演算などの副作用の形をとる。
4.3.7. Buffer Variables¶
修飾子 buffer は API を通じて束縛されたバッファーオブジェクトのデータ格納空間に値が格納される大域変数を宣言するために使用される。バッファー変数はすべてのアクティブなシェーダー呼び出しの間で内包される格納空間を共有して読み書きすることができる。単一のシェーダー呼び出し内でのバッファー変数のメモリーの読み取りと書き込みは順番に処理される。しかし、ある起動で実行される読み込みと書き込みの順序は、他の呼び出しで実行されるものと比較するとほとんど未定義だ。バッファー変数は内包されているメモリーへのアクセス方法に影響を与えるメモリー修飾子
(4.10. Memory Qualifiers) で修飾することができる。
修飾子 buffer は、インターフェイスブロック (4.3.9. Interface Blocks) を宣言するために使用することができ、これらのブロックはシェーダー格納ブロックとして参照される。ブロックの外でバッファー変数を宣言するとコンパイルエラーとなる。
// use buffer to create a buffer block (shader storage block)
buffer BufferName { // externally visible name of buffer
int count; // typed, shared memory...
... // ...
vec4 v[]; // last member may be an array that is not sized
// until after link time (dynamically sized)
} Name; // name of block within the shader
シェーダーの種類ごとに使用されるシェーダー格納ブロックの数、プログラムに使用されるシェーダー格納格納ブロックの合計数、個々のシェーダー格納ブロックが必要とする格納空間の量には、実装に依存した制限がある。これらの制限を超えた場合は、コンパイル時またはリンク時にエラーが発生する。
複数のシェーダーがリンクされている場合、それらのシェーダーは単に大域バッファー変数の名前空間を共有することになる。したがって、同じ名前で宣言されたバッファー変数の型は、単一のプログラムにリンクされているすべてのシェーダーで一致しなければならない。
4.3.9. Interface Blocks¶
入力、出力、一様、バッファー変数の宣言は、名前の付いたインターフェイスブロックにまとめることができ、個別の宣言では達せられない粗い粒度の backing が可能になる。これらはオプションでインスタンス名を持つことができ、シェーダー内でそのメンバーを参照するために用いられる。あるプログラム可能段階の出力ブロックは、後続のプログラム可能段階の対応する入力ブロックによって back される。一様ブロック (a uniform block) は、アプリケーションによってバッファーオブジェクトで back される。シェーダー格納ブロック (a shader storage block) と呼ばれるバッファー変数のブロックも、アプリケーションによってバッファーオブジェクトで back される。頂点シェーダーの入力ブロックや断片シェーダーの出力ブロックを持つことは、コンパイルエラーとなる。これらの用途は将来のために予約されている。
インターフェイスブロックは、キーワード in`, ``out, uniform, buffer
と、ブロック名、そして中括弧 { で始まる。
読者ノート
BNF による一覧を省略。
次のコードは Transform という名前の一様ブロックを定義する。uniform 変数四つがグループ化される。
uniform Transform {
mat4 ModelViewMatrix;
mat4 ModelViewProjectionMatrix;
uniform mat3 NormalMatrix; // allowed restatement of qualifier
float Deformation;
};
型と宣言子は、ブロック外の他の入力、出力、一様、バッファー変数の宣言と同じだ、以下の例外がある:
初期化子は許されない。
不透明型は許されない。
ブロック内での構造体定義の入れ子は許されない。
これらはいずれもコンパイルエラーになる。
メンバー宣言にオプションの修飾子がない場合、メンバーの修飾には、
interface-qualifier で決定されるすべての in, out, patch,
uniform, buffer が含まれる。オプション修飾子を使用する場合、補間修飾子、補助格納修飾子、格納修飾子を含むことができ、ブロックのインターフェイス修飾子と一致する入力、出力、一様メンバーを宣言しなければならない。すなわち、入力変数、出力変数、一様変数、buffer メンバーは、in ブロック、out ブロック、
uniform ブロック、シェーダー格納ブロック内それぞれにしか存在しない。
メンバーの格納修飾子に in, out, patch, uniform, buffer のいずれかのインターフェイス修飾子を繰り返すことはオプションだ。例えば:
in Material {
smooth in vec4 Color1; // legal, input inside in block
smooth vec4 Color2; // legal, 'in' inherited from 'in Material'
vec2 TexCoord; // legal, TexCoord is an input
uniform float Atten; // illegal, mismatched storage qualifier
};
シェーダーインターフェイス (a shader interface) とは、次のうちの一つとする:
プログラムの中で宣言されたすべての一様変数と一様ブロック。これは、一つのプログラム内でリンクされているすべてのコンパイル単位に及ぶ。
プログラム内で宣言された
bufferブロックすべて。隣接するプログラム可能パイプライン段階間の境界。最初の段階のコンパイル単位すべてで宣言された出力すべてと、次の段階のコンパイル単位すべて宣言された入力すべてに及ぶ。なお、実際には断片シェーダーに渡された値すべては、まずラスタライザーと補間器を通過するが、この定義では、断片シェーダーと先行するシェーダーは境界を共有すると考えられる。
ブロック名 (block-name) は、シェーダーインターフェイス内での照合に用いられる。つまり、あるパイプライン段階の出力ブロックは、後続のパイプライン段階の同名の入力ブロックに合致する。一様ブロックやシェーダー格納ブロックの場合、アプリケーションはブロック名を使ってブロックを識別する。シェーダー内ではインターフェイスの照合以外にはブロック名を利用できない。大域スコープのブロック名をブロック名として以外に使用することはコンパイルエラーとなる(例えば、大域変数名や関数名にブロック名を使用することが現在予約されている)。ブロックの内容が同じであっても、一つのシェーダー内の同じシェーダーインターフェイスの複数のブロック宣言に同じブロック名を使用すると、コンパイルエラーとなる。
一つのシェーダインターフェイスの中で合致したブロック名は、同じ数の宣言と同じ型の並び、同じメンバー名の並び、そしてメンバーごとのレイアウト修飾が一致していなければならない。一致した一様ブロック名またはシェーダー格納ロック名(入力ブロック名または出力ブロック名ではない)は、すべてインスタンス名がないか、またはすべてインスタンス名があり、それらのメンバーが同じスコープレベルにあることも必要だ。合致したブロック名の上にインスタンス名がある場合、インスタンス名が違っていても構わない。さらに、合致ブロックが配列として宣言されている場合は、配列のサイズまでも一致していなければならない。 (または、連続するシェーダー段階間のシェーダーインターフェイスの配列一致規則に従う)。不一致の場合、リンクタイムエラーとなる。ブロック名は、同一シェーダー内の異なるシェーダーインターフェイスで異なる定義であることが許されており、例えば、入力ブロックと出力ブロックが同じ名前であることも可能である。
インスタンス名 (instance-name) を使用しない場合、ブロック内で宣言された名前は大域レベルでスコープされ、ブロックの外で宣言されたかのようにアクセスされる。インスタンス名を使用すると、スコープ内のすべてのメンバーが独自の名前空間内に置かれ、構造体と同様の、フィールドセレクター演算子 . でアクセスされる:
in Light {
vec4 LightPos;
vec3 LightColor;
};
in ColoredTexture {
vec4 Color;
vec2 TexCoord;
} Material; // instance name
vec3 Color; // different Color than Material.Color
vec4 LightPos; // illegal, already defined
...
... = LightPos; // accessing LightPos
... = Material.Color; // accessing Color in ColoredTexture block
シェーディング言語の外では、メンバーは同様に識別されるが、インスタンス名の代わりにブロック名が常に使用される。API のアクセスはシェーダーではなくシェーダーインターフェイスに対して行われるのだ。インスタンス名がない場合、API はメンバーにアクセスするためにブロック名を使用せず、メンバー名だけを使用する。
シェーダーインターフェイス内では、同じ大域名の宣言すべては同じオブジェクトに対するものでなければならず、型や、インスタンス名のないブロックの変数やメンバーを宣言しているかどうかが一致していなければならない。また、API はシェーダーインターフェイス内のオブジェクトを一意に識別するためにこの名前を必要とする。次のどちらかが成り立つならば、どのシェーダーインターフェイスもリンクエラーになる:
インスタンス名を持たず、同じ名前のメンバーを持つ相異なるブロックが含まれている。
ブロック外の変数と、インスタンス名のないブロックで、その変数がブロック内のメンバーと同じ名前である。
out Vertex {
vec4 Position; // API transform/feedback will use "Vertex.Position"
vec2 Texture;
} Coords; // shader will use "Coords.Position"
out Vertex2 {
vec4 Color; // API will use "Color"
float Color2;
};
// in same program as Vertex2 above:
out Vertex3 {
float Intensity;
vec4 Color; // ERROR, name collision with Color in Vertex2
};
float Color2; // ERROR, collides with Color2 in Vertex2
配列として宣言されたブロックでは、メンバーにアクセスする際に配列のインデックスも含める必要がある:
uniform Transform { // API uses "Transform[2]" to refer to instance 2
mat4 ModelViewMatrix;
mat4 ModelViewProjectionMatrix;
vec4 a[]; // array will get implicitly sized
float Deformation;
} transforms[4];
...
... = transforms[2].ModelViewMatrix; // shader access of instance 2
// API uses "Transform.ModelViewMatrix" to query an offset or other query
transforms[x].a.length(); // same length for 'a' for all x
Transform[x]; // illegal, must use 'transforms'
Transform.a.length(); // illegal, must use 'transforms'
...transforms[2].a[3]... // if these are the only two dereferences of 'a',
...transforms[3].a[7]... // then 'a' must be size 8, for all
transforms[x]
配列として宣言された一様ブロックやシェーダー格納ブロックでは、個々の配列要素が、ブロックのインスタンス一つを back する、個別のバッファーオブジェクトの結合範囲に対応する。配列のサイズは必要なバッファーオブジェクトの数を示すため、一様ブロックやシェーダー格納ブロックの配列宣言では、配列のサイズを指定する必要がある。一様ブロックやシェーダー格納ブロックの配列は、動的一様な整数式でしかインデックスを付けることができず、それ以外の場合は結果が未定義だ。
OpenGL API の入場点を使用してブロックの配列内の個々のブロックの名前を特定する場合、Transform[2] のようにして、名前の文字列に配列のインデックスを含めることができる。OpenGL API の入場点を使用してブロックメンバーのオフセットやその他の性質を参照する場合、Transform.ModelViewMatrix のように、配列インデックスを抜かなければならない。
細分化制御、細分化評価、幾何シェーダー入力ブロックは、配列として宣言されなければならず、それぞれの段階のシェーダー入力すべては配列宣言とリンク規則に従わなければならない。その他のすべての入出力ブロックの配列は、配列サイズを指定しなければならない。
段階ごとに使用できる一様ブロックの個数とシェーダー格納ブロックの個数は、実装依存の制限がある。いずれかの制限を超えた場合、リンクエラーとなる。
4.4. Layout Qualifiers¶
レイアウト修飾子は宣言形式がいくつかある。前節の文法で示したように、インターフェイスブロックの定義やブロックメンバーの一部として現れることがある。一つの layout-qualifier だけで、その修飾子を使った他の宣言のレイアウトを設定することもできる。
また、インターフェイス修飾子で宣言された個別の変数で出現することもある:
レイアウトの宣言は、大域スコープまたはブロックメンバーでしか行えず、以下の節で示される場所でしか行えない。
レイアウト修飾子は次のように展開する:
layout ( layout-qualifier-id-list )sharedlayout-qualifier-name に使用されるトークンは、キーワードではなく識別子で、 layout-qualifier-id としてキーワード shared を使用することができる。一般的に、これらはどのような順序でも並べてよい。順序に依存する意味は、下で明示されている場合に限り存在する。同様に、これらの識別子は、明示的に別段の記載がない限り、大文字と小文字を区別しない。
単一の宣言にレイアウト修飾子を複数含めることができる。また、同じ
layout-qualifier-name が、一つのレイアウト修飾子の中で複数回現れたり、同じ宣言の中で複数のレイアウト修飾子にまたがって現れたりすることがある。同じ
layout-qualifier-name が単一の宣言の中で複数回出現する場合、最後に出現したものがそれ以前に出現したものよりも優先される。さらに、このような
layout-qualifier-name が後続の宣言やその他の観察可能な挙動に影響する場合、影響を与えるのは最後に出現するものだけであり、以前に出現したものは存在しないかのように振る舞う。これは layout-qualifier-name を上書きする場合にも当てはまり、一方が他方を上書き場合 (e.g. row_major vs. column_major) には、最後に出現するものだけが影響を及ぼす。
integer-constant-expression は 4.3.3. Constant Expressions で constant integral expression として定義されており、integer-constant-expression が特殊化定数であることはコンパイルエラーになる。
読者ノート
仕様書本文ではレイアウト修飾子の使用法の概要が表になって掲載されている。巨大なデータなので引用は控える。
4.4.1. Input Layout Qualifiers¶
計算シェーダーを除くすべてのシェーダーでは、入力変数宣言、入力ブロック宣言、入力ブロックメンバー宣言に対してレイアウト修飾子 location を使うことができる。このうち、変数とブロックメンバー(ブロックは不可)には、さらにレイアウト修飾子
component が使用できる。
location = layout-qualifier-valuecomponent = layout-qualifier-value例:
layout(location = 3) in vec4 normal;
const int start = 6;
layout(location = start + 2) int vec4 v;
これにより、シェーダー入力の normal はベクトル位置番号 3 に、v は位置番号 8 に割り当てられる。頂点シェーダー入力の場合、位置を入力値を取得する頂点属性の番号で指定する。他のすべてのシェーダー型の入力では、そのシェーダーが別のプログラムオブジェクト内にあったとしても、位置を以前のシェーダー段階からの出力との照合に使用できるベクトル番号で指定する。
続く言語では、特定の型で消費される位置の数を記述する。ただし、幾何シェーダー入力、細分化制御シェーダー入出力、細分化評価入力はすべて、他のシェーダー入出力に対して追加の配列レベルを持っている。この外側の配列レベルは、型が消費する位置の数を考慮する前に、型から取り除かれる。
Vulkan を対象にしている場合を除き、頂点シェーダー入力が任意のスカラーまたはベクトル型である場合、位置を一つを消費する。非頂点シェーダーの入力や Vulkan を対象にしている場合の段階入力が dvec3 や dvec4 以外のスカラー型やベクトル型の場合は、位置を一つを消費するが、dvec3 や dvec4 型の場合は連続した二つの位置を消費する。double 型や dvec2 型の入力は、段階すべてで一つの位置しか消費しない。
上述のように外側の配列レベルを削除した後、宣言された入力がサイズ n の配列で、各要素が m 個の位置を取る場合、指定された位置から始まる m×n 個の連続した位置が割り当てられる。例えば:
layout(location = 6) in vec4 colors[3];
これにより、シェーダーの入力色がベクトルの位置番号 6, 7, 8 に割り当てられることが確定する。
宣言された入力が n×m 行列の場合は、指定された位置から始まる複数の位置が割り当てられる。各行列に割り当てられる位置の数は、m 成分のベクトルの n 要素の配列と同じになる。例えば:
layout(location = 9) in mat4 transforms[2];
これにより、シェーダー入力 transforms がベクトルの 9..16番に割り当てられる:
transforms[0]が 9, 10, 11, 12 番に、transforms[1]が 13, 14, 15, 16 番に
割り当てられる。
宣言された入力が構造体やブロックの場合、そのメンバーには宣言順に連続した位置が割り当てられ、最初のメンバーにはレイアウト修飾子で指定された位置が割り当てられる。構造体の場合、この処理は構造体全体に適用される。layout 修飾子を構造体のメンバーに使用するとコンパイルエラーとなる。ブロックの場合、この処理はブロック全体に適用される。つまり location レイアウト修飾子を持つ最初のメンバーに到達するまで適用される。
ブロックのメンバーが location 修飾子付きで宣言されている場合、そのメンバーの位置はその修飾子に由来し、メンバーの location 修飾子がブロックレベルの宣言よりも優先される。後続のメンバーには、次の location 修飾子が宣言されたメンバーまで、最新の位置に基づいて連続した位置が再び割り当てられる。位置に使用される値は、昇順に宣言する必要はない。
ブロックレベルの location 修飾子がないブロックでは、そのメンバーのすべてが
location 修飾子を持つか、またはそれを一つも持たないことが要求される。さもないとコンパイルエラーとなる。配列として宣言されたブロックには location がブロックレベルでしか適用できないものがある。ブロックが配列として宣言されていて、ブロックの配列要素ごとに各メンバーに追加の位置が必要な場合、ブロックのメンバーに位置を指定するとコンパイルエラーになる。つまり、ブロックメンバー上に位置を適用することで指定不足になる場合、それは認められない。配列されたインターフェイス(一般にインターフェイスの拡張により余計な配列を持つもの)では、この規則を適用する前に外側の配列が取り除かれる。
ブロックや構造体のメンバーが消費する位置は、構造体のメンバーが同じ型の入力変数として宣言されているかのように、上記の規則を再帰的に適用して決定される:
layout(location = 3) in struct S
{
vec3 a; // gets location 3
mat2 b; // gets locations 4 and 5
vec4 c[2]; // gets locations 6 and 7
layout(location = 8) vec2 A; // ERROR, can't use on struct member
} s;
layout(location = 4) in block
{
vec4 d; // gets location 4
vec4 e; // gets location 5
layout(location = 7) vec4 f; // gets location 7
vec4 g; // gets location 8
layout(location = 1) vec4 h; // gets location 1
vec4 i; // gets location 2
vec4 j; // gets location 3
vec4 k; // ERROR, location 4 already used
};
シェーダーが利用できる入力位置の数には制限がある。頂点シェーダーでは、その制限は公示された頂点属性の数だ。その他のシェーダーでは、制限は実装に依存し、公示された最大入力成分数の 1/4 以上でなければならない。
取り付けられたシェーダーが対処されている個数を超える位置を使用している場合、デバイス依存最適化がプログラムに利用可能なハードウェア資源内に収まるようにさせない限り、プログラムはリンクに失敗する。
明示的な位置割り当てにより、リンカーが他の明示的割り当てのない変数のための空間を見つけられない場合、プログラムはリンクに失敗する。
非頂点入力が以前のシェーダー段階の出力と一致するかどうかを判断するためには、
location レイアウト修飾子がもしあれば一致していなければならない。
シェーダーテキストに位置が割り当てられていない頂点シェーダー入力変数に OpenGL APIで指定された位置がある場合は、その指定位置が使用される。そうでなければ、そのような変数はリンカーによって場所が割り当てられる。入力変数が同じ言語の複数のシェーダーで宣言されていて、位置が競合している場合、リンクエラー。
修飾子 component を使用すると、スカラーやベクトルの位置をより細かく指定することができ、消費される位置内の個々の成分まで指定することができる修飾子
location を指定せずに component を使用すると、コンパイルエラーになる(順序は重要でない)。位置内の成分は 0, 1, 2, 3 だ。成分 N で始まる変数やブロックメンバーは、そのサイズまでの成分 N, N+1, N+2, … を消費する。この成分の並びが 3
より大きくなると、コンパイルエラーになる。スカラー double はこれらの成分のうち二つを消費し、dvec2 はある位置で利用可能な四つの成分全てを消費する。
dvec3 や dvec4 は成分指定なしでしか宣言できない。dvec3 は一つ目の位置の四つの成分と、二つ目の位置の成分 0 と 1 をすべて消費する。これにより、成分 2
と 3 は他の成分修飾された宣言に使用できる。
// a consumes components 2 and 3 of location 4
layout(location = 4, component = 2) in vec2 a;
// b consumes component 1 of location 4
layout(location = 4, component = 1) in float b;
// ERROR: c overflows component 3
layout(location = 3, component = 2) in vec3 c;
// d consumes components 2 and 3 of location 5
layout(location = 5, component = 2) in double d;
// ERROR: e overflows component 3 of location 6
layout(location = 6, component = 2) in dvec2 e;
// ERROR: f overlaps with g
layout(location = 7, component = 0) double f;
layout(location = 7, component = 1) float g;
layout(location = 8) in dvec3 h; // components 0,1,2 and 3 of location 8
// and components 0 and 1 of location 9
layout(location = 9, component = 2) in float i; // okay, compts 2 and 3
変数が配列の場合、配列の各要素は順番に連続した位置に割り当てられるが、各位置の中ですべて同じ指定された成分になる:
// component 3 in 6 locations are consumed
layout(location = 2, component = 3) in float d[6];
この場合、位置 2 の成分 3 は d[0] を、位置 3 の成分 3 は d[1] を、… 位置 7 の成分 3 に d[5] を格納する。
これにより二つの配列を同じ位置にまとめることができる:
// e consumes beginning (components 0, 1 and 2) of each of 6 slots
layout(location = 0, component = 0) in vec3 e[6];
// f consumes last component of the same 6 slots
layout(location = 0, component = 3) in float f[6];
これを配列の配列に適用する場合は、すべてのレベルの配列性が取り除かれ、指定された成分に位置ごとに割り当てられた要素になる。これらの非配列要素は 4.1.9. Arrays が指定する順序で配列の配列に対する位置を埋める。
修飾子 component を行列、構造体、ブロック、またはこれらを含む配列に適用するとコンパイルエラーになる。component 1 または 3 を double または dvec2
の先頭に使用するとコンパイルエラーになる。プログラム内で同じ変数に異なる成分を指定すると、リンクエラーになる。
位置エイリアシング (location aliasing) とは、二つの変数やブロックメンバーに同じ位置番号を持たせることだ。成分エイリアシング (component aliasing) とは、二つの位置エイリアスに同じ(つまり重複する)成分番号を割り当てることだ(
component を使用しない場合は 0 から始まる成分が割り当てられることを思い出せ)。一つの例外を除いて、位置エイリアシングは成分エイリアシングを起こさない場合に限り許される。さらに、位置エイリアシングを行う場合、その位置を共有するエイリアスは、内在する数値型とビット幅が同じでなければならず、補助格納修飾子と補間修飾子も同じでなければならない。成分エイリアシングが許可される例外とは、頂点シェーダーへの二つの入力変数(ブロックメンバーではない)に対して OpenGL を対象にする場合で、これらは成分エイリアシングが許されている。この頂点変数の成分エイリアシングは、各実行パスがエイリアシングされた各成分ごとに高々一つの入力にアクセスする頂点シェーダーを対処することしか目的としていない。頂点シェーダー実行形式を通るすべての実行パスが、任意の単一成分にエイリアスされた複数の入力にアクセスすることを検出した場合、実装はこれをリンクタイムエラーとすることが許されているが、必須ではない。
Tessellation Evaluation Inputs¶
細分化評価シェーダーで使える追加的な入力レイアウト修飾子の識別子は次の通り:
識別子 primitive-mode は細分化基本形状生成器が使用する。
trianglesquadsisolinesprimitive-mode がもしあれば、細分化基本形状生成器は三角形をより小さな三角形に、四角形を三角形に、四角形を線の集合体にそれぞれ細分するべきだと指定する。
レイアウト識別子の二番目のグループである頂点間隔は、細分化基本形状生成器が辺を細分化する際の間隔を指定するために用いられる。
equal_spacingfractional_even_spacingfractional_odd_spacingequal_spacing は辺を同じ大きさのセグメントの集まりに分割することを指定する。
fractional_even_spacing は、辺を偶数個の同じ長さのセグメントとさらに二つのより短い「小数」のセグメントに分割することを指定する。
fractional_odd_spacing は、辺を奇数個の同じ長さのセグメントとさらに二つのより短い「小数」セグメントに分割することを指定する。
三番目のレイアウト識別子である ordering は、細分化基本形状生成器が、OpenGL 仕様にある座標系に従い、時計回りまたは反時計回りのどちらの順序で三角形を生成するのかを指定する。
識別子 cw と ccw は、それぞれ時計回りと反時計回りの三角形を示す。細分化基本形状生成器が三角形を生成しない場合、この順序は無視される。
最後の point-mode は、細分化基本形状生成器が、線や三角形を生成するのではなく、細分化された基本形状のそれぞれの紛れのない頂点に対して一点を生成するべきであることを示す。
これらの識別子は、単一の入力レイアウト宣言の中で、一回またはそれ以上指定することができる。プログラムの細分化評価シェーダーの中で、primitive-mode, vertex-spacing, ordering が複数回宣言されている場合、そのような宣言はすべて同じ識別子を使用しなければならない。
プログラム内の少なくとも一つの細分化評価シェーダー(コンパイル単位)は、その入力レイアウトで primitive-mode を宣言しなければならない。vertex-spacing, ordering, point_mode 識別子の宣言はオプションだ。プログラム内の細分化評価シェーダーすべてが primitive_mode を宣言するということは必須ではない。 vertex-spacing や ordering の宣言が省略された場合、細分化基本形状生成器は、それぞれ等間隔または反時計回りの頂点順序を採用する。point-mode の宣言が省略された場合、細分化基本形状生成器は、primitive-mode に従って線分または三角形を生成する。
Geometry Shader Inputs¶
幾何シェーダー入力に対する追加のレイアウト修飾子識別子には、基本形状 (primitive) 識別子と呼び出し回数 (invocation count) 識別子がある:
pointslineslines_adjacencytrianglestriangles_adjacencyinvocations = layout-qualifier-value識別子 points, lines, lines_adjacency, triangles,
triangles_adjacency は、幾何シェーダーが受け付ける入力基本形状の種類を指定するためのもので、これらのうちただ一つを受け付ける。プログラム内の少なくとも一つの幾何シェーダー(コンパイル単位)は、この入力基本形状のレイアウトを宣言しなければならず、プログラム内の幾何シェーダーすべての入力レイアウト宣言は同じレイアウトを宣言しなければならない。プログラム内のすべての幾何シェーダーが入力基本形状レイアウトを宣言することは必須ではない。
識別子 invocations は、受けとった入力基本形状それぞれに対して幾何シェーダー実行形式が呼び出される回数を指定するために用いられる。回数の宣言はオプションだ。プログラム内のどの幾何シェーダーでも回数が宣言されていない場合、幾何シェーダーは入力基本形状それぞれに対して一度実行される。宣言されている場合は、すべての宣言で同じ回数を指定しなければならない。シェーダーが実装依存の最大値を超える呼び出し回数を指定したり、ゼロ以下の呼び出し回数を指定したりすると、コンパイルエラーとなる。
layout(triangles, invocations = 6) in;
例えば上の宣言では、幾何シェーダーへの入力はすべて三角形であり、幾何シェーダーの実行形式は、処理される三角形ごとに 6 回実行される。
幾何シェーダー入力サイズなし配列宣言のすべてで、以前の入力基本形状レイアウト修飾子がある場合、サイズが変更される:
points: 1lines: 2lines_adjacency: 4triangles: 3triangles_adjacency: 6
内在的に宣言された入力配列 gl_in[] は、任意の入力基本形状レイアウト宣言によってもサイズが決定される。そのため、式 gl_in.length() 式は上にある値を返す。
gl_in などの内在的に宣言された入力、配列サイズを持たずに宣言された入力については、メソッド length() を使用する前か、配列サイズを知る必要のあるその他の配列使用の前にレイアウトを宣言しなければならない。
レイアウト宣言の配列サイズが、同じシェーダー内の入力変数の宣言で指定されている明示的な配列サイズすべてと一致しない場合はコンパイルエラーとなる。コンパイルエラーの例:
// code sequence within one shader...
in vec4 Color1[]; // legal, size still unknown
in vec4 Color2[2]; // legal, size is 2
in vec4 Color3[3]; // illegal, input sizes are inconsistent
layout(lines) in; // legal for Color2, input size is 2, matching Color2
in vec4 Color4[3]; // illegal, contradicts layout of lines
layout(lines) in; // legal, matches other layout() declaration
layout(triangles) in; // illegal, does not match earlier layout() declaration
プログラム内の幾何シェーダーすべてにおいて、提供されたサイズのすべて(サイズ付き入力配列とレイアウトサイズ)が一致しない場合は、リンクエラーとなる。
Fragment Shader Inputs¶
gl_FragCoord には以下のような追加的断片レイアウト修飾子がある:
origin_upper_leftpixel_center_integerOpenGL の gl_FragCoord は既定ではウィンドウの座標は左下を原点とし、画素中心は半画素の座標にあるとしている。例えば、ウィンドウの左下端の画素に対しては (x,
y) 座標 (0.5, 0.5) が返される。原点は gl_FragCoord に
origin_upper_left 修飾子を付けて再宣言することで変更することができ、
gl_FragCoord の原点をウィンドウの左上に移動させ、y はウィンドウの下に向かって値を大きくしていく。また、返される値は、pixel_center_integer によって、x と y の両方で半画素ずつずらすことができ、画素が整数のオフセットで中心に置かれているように見える。これは gl_FragCoord で返される (x, y) の値が既定値が (0.5, 0.5) であるのに対し、pixel_center_integer で (0.0,
0.0) に移動する。
Vulkan を対象にする場合、gl_FragCoord の原点は左上で、画素中心は半画素座標に配置されていると仮定し、要求される。この原点を明示的に設定するには
gl_FragCoord を origin_upper_left 識別子で再宣言する。
再宣言は次のように行う:
in vec4 gl_FragCoord; // redeclaration that changes nothing is allowed
// All the following are allowed redeclaration that change behavior
layout(origin_upper_left) in vec4 gl_FragCoord;
layout(pixel_center_integer) in vec4 gl_FragCoord;
layout(origin_upper_left, pixel_center_integer) in vec4 gl_FragCoord;
gl_FragCoord がプログラム内のいずれかの断片シェーダーで再宣言された場合、そのプログラム内で gl_FragCoord を静的に使用しているすべての断片シェーダーで再宣言されなければならない。単一のプログラム内にある断片シェーダーすべてにおける
gl_FragCoord の再宣言はすべてが同じ修飾子の集合でなければならない。どのシェーダー内でも gl_FragCoord の最初の再宣言が gl_FragCoord のどの使用の前にも現れなければならない。組み込み gl_FragCoord は断片シェーダーでしか事前に宣言されていないので、他のシェーダー言語で再宣言するとコンパイルエラーになる。
origin_upper_left 修飾子と pixel_center_integer 修飾子の両方とも、またはいずれか一方をつけて gl_FragCoord を再宣言しても gl_FragCoord.x と
gl_FragCoord.y にしか影響しない。ラスタライズ、座標変換、その他の API パイプラインや言語機能には影響しない。
断片シェーダーでは、OpenGL 仕様書の 15.2.4 “Early Fragment Tests” に記載されているように、断片シェーダーの実行前に断片検定を行うことを要求するために、in のみで次のレイアウト修飾子を使用することが許される(変数宣言では不可):
layout(early_fragment_tests) in;
例えば上の宣言では、断片シェーダーの実行前に断片ごとの検定が行われるようになる。これを宣言しない場合は、断片シェーダーの実行後に断片ごとの検定が行われる。この宣言が必要となるのは、一つの断片シェーダー(コンパイル単位)だけだが、複数の断片シェーダーが宣言することもできる。少なくとも一つがこれを宣言していれば有効になる。
Compute Shader Inputs¶
計算シェーダー入力にはレイアウト位置修飾子がない。
計算シェーダー入力に対するレイアウト修飾子の識別子は、作業グループサイズ修飾子だ:
local_size_x = layout-qualifier-valuelocal_size_y = layout-qualifier-valuelocal_size_z = layout-qualifier-valuelocal_size_x, local_size_y, local_size_z 各修飾子は、それぞれ 1, 2,
3 次元の計算シェーダーによる固定作業グループのサイズを宣言するために用いられる。シェーダーが次元のサイズをここから指定していない場合、その次元は 1 となる。
例えば、計算シェーダーで次のように宣言した場合、
layout(local_size_x = 32, local_size_y = 32) in;
作業グループサイズが 32×32 要素の二次元計算シェーダーを宣言するために用いられる。これは、3次元目のサイズが 1 である三次元計算シェーダーに相当する。
読者ノート
上の一文は何を言っているのか。
もう一つ別の例として、次の宣言は、一次元の計算シェーダーがコンパイルされており、そのサイズが実質的には 8 要素であることを指定している:
layout(local_size_x = 8) in;
いずれかの次元におけるシェーダーの固定作業グループサイズがゼロ以下、または実装で対処される最大サイズよりも大きい場合、コンパイルエラー。また、このようなレイアウト修飾子が同一シェーダー内で複数回宣言されている場合、それらの宣言はすべて同じ作業グループサイズの集合を設定し、同じ値を設定しなければならない。さもなければコンパイルエラー。単一のプログラムオブジェクトに取り付けられた複数の計算シェーダーが固定作業グループサイズを宣言する場合、その宣言は同一でなければならない。さもなければリンクエラー。
さらに、プログラムオブジェクトが計算シェーダーを何か含む場合、少なくとも一つはプログラムの固定作業グループサイズを指定する入力レイアウト修飾子を含まなければならない。さもなければリンクエラー。
4.4.2. Output Layout Qualifiers¶
出力レイアウト修飾子には、シェーダー段階すべてに適用されるものと、特定の段階にしか適用されないものがある。ここでは前者を議論する。
入力レイアウト修飾子と同様に、計算シェーダーを除くすべてのシェーダーでは、出力変数宣言、出力ブロック宣言、出力ブロックメンバー宣言に対して location レイアウト修飾子を許す。このうち、変数とブロックメンバー(ブロックは不可)には、さらに
component レイアウト修飾子を許す。
location = layout-qualifier-valuecomponent = layout-qualifier-valueブロックや構造体に location 修飾子や component 修飾子を適用する際の使い方や規則は 4.4.1. Input Layout Qualifiers のとおりだ。また、断片シェーダーの出力では、二つの変数が同じ位置に配置される場合、それらの変数の内容される型は同じでなければならない(浮動小数点・整数)。出力変数やメンバーの成分エイリアスは許されない。
断片シェーダーでは index 出力レイアウト修飾子を追加的に付けられる:
index = layout-qualifier-valueこれらの修飾子は、それぞれ高々一度現れる。index が指定された場合 location
も指定しなければならない。index が指定されていない場合は値 0 が使用される。
例えば次の断片シェーダーでは、断片シェーダー出力色を混合方程式の最初(インデックス 0)の入力として断片カラー 3 に割り当てる設定をする:
layout(location = 3) out vec4 color;
次の例は、断片シェーダーの出力係数が、混合方程式の 2 番目(インデックス 1)の入力として断片色 3 に割り当てる設定をする:
layout(location = 3, index = 1) out vec4 factor;
断片シェーダーの出力では、位置とインデックスは、出力の値を受け取る色出力番号とインデックスを指定する。他のすべてのシェーダー段階の出力では、位置は、後続のシェーダー段階の入力と照合するために使用できるベクトル番号を指定する(シェーダーが別のプログラムオブジェクトにある場合も同様)。
宣言された出力が dvec3 または dvec4 以外のスカラーまたはベクトル型である場合は、単一の位置を消費する。dvec3 または dvec4 型の出力は連続した二つの場所を消費する。double 型や dvec2 型の出力は全ての段階で一つの位置しか消費しない。
宣言された出力が配列の場合は、指定された場所から連続した場所が割り当てられる。例えば、次の宣言ではベクトルの位置番号 2, 3, 4 に colors が割り当てられる。
layout(location = 2) out vec4 colors[3];
宣言された出力が n×m の行列の場合、指定された位置から始まる複数の位置が割り当てられる。割り当てられる場所の数は m 成分ベクトルの n 要素の配列と同じだ。
宣言された出力が構造体の場合、そのメンバーには宣言された順に連続した場所が割り当てられ、構造体に指定された場所が最初のメンバーに割り当てられる。構造体のメンバーが消費する位置の個数は、その構造体のメンバーが同じ型の出力変数として宣言されているかのように、前述の規則が再帰的に適用される。
location レイアウト修飾子は、構造体として宣言された出力変数に使用することができる。ただし、構造体メンバーに location 修飾子を使用するとコンパイルエラーとなる。location レイアウト修飾子は出力ブロックと出力ブロックメンバーに使用できる。
シェーダーで使用できる出力位置の数は限界がある。断片シェーダーでは、その限界は公表されたドローバッファー数だ。
その他のシェーダーの場合、制限は実装依存であり、公表されている最大出力成分数の 1/4 以上でなければならない(計算シェーダーには出力がない)。取り付けられたシェーダーが対処されている個数以上の位置を使用している場合、デバイス依存最適化によってプログラムが利用可能なハードウェア資源内に収まるようにしない限り、プログラムはリンクに失敗する。
また、コンパイル時にリンクが失敗することがわかっている場合には、コンパイルエラーがあり得る。出力位置が負の場合は、コンパイルエラーになる。断片シェーダーがレイアウトインデックスを 0 未満または 1 以上に設定した場合も、コンパイルエラーとなる。
次のいずれかが発生すると、コンパイルエラーまたはリンクエラー:
断片シェーダーの出力変数の二つが両者とも同じ位置とインデックスに割り当てられている。
同じ(頂点|細分化|幾何)シェーダー段階からの出力変数二つが同じ位置に割り当てられている。
断片シェーダー出力では layout 修飾子または OpenGL API を使用して位置を割り当てることができる。
すべてのシェーダー型において、明示的な位置の割り当てが明示的な割り当てのない他の変数のための空間をリンカーに見つけさせられなくなる場合、プログラムはリンクに失敗する。
シェーダーテキストで位置やインデックスが割り当てられていない出力変数が OpenGL APIを通じて位置を指定されている場合は API で指定された位置が採用される。それ以外の場合は、リンカーがそのような変数に場所を割り当てる。このような割り当てはすべて、色インデックスがゼロになる。詳細は、OpenGL 仕様書の 15.2 “Shader Execution”を参照。出力変数が同じ言語の複数のシェーダで宣言されており、位置やインデックスの値が衝突している場合、リンクエラーが発生する。
断片ではない出力が後続のあるシェーダー段階からの入力と一致するかどうかを判定するために、location レイアウト修飾子が(もしあれば)一致しなければならない。
Transform Feedback Layout Qualifiers¶
頂点、細分化、幾何各段階では、シェーダーが変換反響を制御することができる。それをするときに、シェーダーはどの変換反響バッファーを使用するか、どの出力変数をどのバッファーに書き込むか、各バッファーをどのようにレイアウトするかを決定する。これを実現するために、シェーダでは出力宣言に次のレイアウト修飾子識別子を使用できる:
xfb_buffer = layout-qualifier-valuexfb_offset = layout-qualifier-valuexfb_stride = layout-qualifier-valueこれらの xfb_ 修飾子を(前処理後に)静的に使用するシェーダーは、変換反響捕捉モードとなり、変換反響の設定を記述する責任が生じる。このモードでは
xfb_offset で選択された出力を、直接または間接的に、変換反響バッファーに取り込むことになる。
xfb_offset で選択された出力をどの変換反響バッファーに取り込むかは
xfb_buffer 修飾子が指定する。xfb_buffer 修飾子は out 修飾子、出力変数、出力ブロック、出力ブロックメンバーのどれにでも適用できる。変換反響捕捉モードのシェーダーには次の初期の大域既定がある:
layout(xfb_buffer = 0) out;
この既定を変更するには、インターフェイス修飾子 out に xfb_buffer をつけて別のバッファーを宣言する。これが大域既定を変更する唯一の方法だ。
xfb_buffer 修飾子を付けずに変数や出力ブロックを宣言した場合は、大域既定バッファーを継承する。xfb_buffer 修飾子をつけて変数や出力ブロックを宣言すると、その宣言されたバッファーを持る。あるブロックのメンバーすべてがそのブロックのバッファーを継承する。メンバーは xfb_buffer を宣言することができるが、そのバッファーはそのブロックから継承されたバッファーと一致しなければならない。そうでなければコンパイルエラー。
layout(xfb_buffer=2, xfb_offset=0) out block { // block's buffer is 2
layout(xfb_buffer = 2) vec4 v; // okay, matches the inherited 2
layout(xfb_buffer = 3) vec4 u; // ERROR, mismatched buffer
vec4 w; // inherited
};
layout(xfb_offset=16) out vec4 t; // initial default is buffer 0
layout(xfb_buffer=1) out; // new global default of 1
out block { // block has buffer 1
vec4 x; // x has buffer 1 (not captured)
layout(xfb_buffer = 1) vec4 y; // okay (not captured)
layout(xfb_buffer = 0) vec4 z; // ERROR, mismatched buffer
};
layout(xfb_offset=0) out vec4 g; // g has buffer 1
layout(xfb_buffer=2) out vec4 h; // does not change global default
layout(xfb_offset=16) out vec4 j; // j has buffer 1
これが意味するのは、ブロックのメンバーのうち、変換反響バッファーに行くものはすべて同じバッファーに行くということだ。
ブロックが配列として宣言されている場合、ブロックの配列要素 0 のメンバーすべては、前述のように、宣言または継承された xfb_buffer に取り込まれる。一般に、ブロックの大きさ N の配列は、N 個の連続したバッファに取り込まれ、ブロックの配列要素 E のメンバーすべては、バッファ B に取り込まれる。ここで、B は宣言または継承された xfb_buffer に E を加えたものに等しくなる。
xfb_buffer には、ブロックの配列を捕捉するために必要な追加のバッファーを含めて、0 より小さいか、実装依存の定数 gl_MaxTransformFeedbackBuffers 以上の値を指定すると、コンパイルエラーかリンクエラーとなる。
修飾子 xfb_offset は、変換反響バッファー内のバイトオフセットを割り当てる。
xfb_offset をつけることができるのは、変数、ブロックメンバー、またはブロックしかない。ブロックが xfb_offset で修飾されていれば、そのブロックのメンバーすべてに変換反響バッファー内のオフセットが割り当てられる。ブロックが
xfb_offset で修飾されていない場合、そのようなブロックのメンバーには変換反響バッファーのオフセットは割り当てられない。オフセットが割り当てられた変数やブロックメンバーだけが捕捉される(つまり、ブロックの適切な部分集合が捕捉される)。このような変数やブロックメンバーがシェーダー内で書き込まれるたびに、書き込まれた値は割り当てられたオフセットで捕捉される。シェーダーの呼び出し時の間にこのようなブロックメンバーや変数が書き込まれない場合、割り当てられたオフセットでのバッファーの内容は未定義となる。変換反響オフセットが割り当てられている変数やメンバーへの静的な書き込みがない場合でも、その空間はバッファーに割り当てられ、ストライドに影響を与える。
xfb_offset で修飾された変数やブロックメンバーには、スカラー、ベクトル、行列、構造体、およびこれらの(サイズ付き)配列を指定できる。このオフセットは、最初に修飾された変数やブロックメンバーの第一成分のサイズの倍数でなければならない。そうでなければコンパイルエラー。さらに、double を含む集合体に適用される場合、オフセットもバッファーに取られる空間も 8 の倍数でなければならない。与えられたオフセットは、修飾されたエンティティの最初のメンバーの最初の成分に適用される。その後、修飾された実体中で、後続の成分はそれぞれその成分のサイズの倍数に整列した、次に利用可能なオフセットに順に割り当てられる。集約型は成分レベルまで平坦化され、この成分の並びが得られる。サイズなし配列の宣言に xfb_offset を適用するとコンパイルエラー。
出力バッファーのエイリアシングは許されない。変換反響オフセットが重なり合っている変数を指定するとコンパイルエラーまたはリンクエラー。
修飾子 xfb_stride は、捕捉した各頂点が何バイト消費するかを指定する。その宣言の変換反響バッファーが継承されているか、明示的に宣言されているかに関わらず、これが適用される。この修飾子は、変数、ブロック、ブロックメンバー、あるいは単に修飾子
out にも適用できる。バッファーが倍精度成分を持つ出力を捕捉する場合、バッファー幅は 8 の倍数であるか、そうでない場合は 4 の倍数でなければならず、そうでない場合はコンパイルエラーかリンクエラー。xfb_stride をオーバーフローさせるような xfb_offset を持つことは、xfb_stride の前後の宣言であろうと、異なるコンパイル単位であろうと、コンパイルエラーかリンクエラー。xfb_stride は、同じバッファーに対して複数回宣言することができるが、異なる値のストライドを指定するとコンパイルエラーかリンクエラー。
// buffer 1 has 32-byte stride
layout(xfb_buffer = 1, xfb_stride = 32) out;
// same as previous example; order within layout does not matter
layout(xfb_stride = 32, xfb_buffer = 1) out;
// everything in this block goes to buffer 0
layout(xfb_buffer = 0, xfb_stride = 32) out block1 {
layout(xfb_offset = 0) vec4 a; // a goes to byte offset 0 of buffer 0
layout(xfb_offset = 16) vec4 b; // b goes to offset 16 of buffer 0
};
layout(xfb_buffer = 3, xfb_offset = 12) out block2 {
vec4 v; // v will be written to byte offsets 12 through 27 of buffer
float u; // u will be written to offset 28
layout(xfb_offset = 40) vec4 w;
vec4 x; // x will be written to offset 56, the next available offset
};
layout(xfb_buffer = 2, xfb_stride = 32) out block3 {
layout(xfb_offset = 12) vec3 c;
layout(xfb_offset = 24) vec3 d; // ERROR, requires stride of 36
layout(xfb_offset = 0) vec3 g; // okay, increasing order not required
};
バッファーに xfb_stride が指定されていない場合、バッファー幅は、最も高いオフセットに置かれた変数を保持するのに必要な最小値となり、必要な詰め物 (padding) も含まれる:
// if there no other declarations for buffer 3, it has stride 32
layout(xfb_buffer = 3) out block4 {
layout(xfb_offset = 0) vec4 e;
layout(xfb_offset = 16) vec4 f;
};
結果のバッファー幅(暗黙的または明示的)を 4 で割って、実装依存の定数
gl_MaxTransformFeedbackInterleavedComponents 以下でなければならない。
Tessellation Control Outputs¶
変換反響レイアウト修飾子を除き、細分化制御シェーダーでは、出力ブロック、ブロックメンバー、または変数宣言ではなく、インターフェイス修飾子 out 上でのみ出力レイアウト修飾子を使用することができる。細分化制御シェーダーで使用できる出力レイアウト修飾子の識別子は次のとおり:
vertices = layout-qualifier-value識別子 vertices は細分化制御シェーダーが生成する出力パッチの頂点数を指定し、細分化制御シェーダーの呼び出し回数も指定する。出力頂点数がゼロ以下であったり、実装依存の最大パッチサイズより大きかったりすると、コンパイルエラーやリンクエラーとなる。
内在的に宣言された細分化制御出力配列 gl_out[] は、任意の出力レイアウト宣言によってもサイズが決定される。したがって、次の式は以前の出力レイアウト修飾子で指定された出力パッチの頂点数を返す:
gl_out.length()
内在的に宣言された出力を含む、配列サイズなし宣言された出力については、メソッド
length() の使用や、サイズを知る必要のある他の配列使用の前に、レイアウトを宣言しなければならない。
出力レイアウト修飾子で指定された出力パッチ頂点数が、同じシェーダー内の出力変数宣言で指定された配列サイズのどれとも一致しない場合は、コンパイルエラーとなる。
プログラム内のすべての細分化制御シェーダーのレイアウト宣言は、同じ出力パッチ頂点数を指定しなければならない。細分化制御シェーダーを含むプログラムには、出力パッチの頂点数を指定するレイアウト修飾子が少なくとも一つ必要だが、すべての細分化制御シェーダーでそのような宣言が必要ということではない。
Geometry Outputs¶
幾何シェーダーは、出力基本形状型、最大出力頂点数、出力ごとのストリーム番号の三種の出力レイアウト識別子を追加的に持てる。基本形状型と頂点数の識別子は、出力ブロック、ブロックメンバー、変数宣言ではなく、インターフェイス修飾子 out でしか許されない。ストリーム識別子は、インターフェイス修飾子 out, 出力ブロック、変数宣言上で許される。
幾何シェーダー出力に対するレイアウト修飾子の識別子は次のとおり:
pointsline_striptriangle_stripmax_vertices = layout-qualifier-valuestream = layout-qualifier-value基本形状型識別子の points, line_strip, triangle_strip は、幾何シェーダーが生成する出力基本形状の型を指定するために使用され、これらのうちただ一つが受け入れられる。プログラム内の少なくとも一つの幾何シェーダー(コンパイル単位)は、出力基本形状型を宣言しなければならず、プログラム内のすべての幾何シェーダーの出力基本形状型宣言は、同じ基本形状型を宣言しなければならない。プログラム内のすべての幾何シェーダーが出力基本形状型宣言をすることは必須ではない。
頂点数識別子 max_vertices は、シェーダーが単一の呼び出しで出力する最大の頂点数を指定するために使用される。プログラム内の少なくとも一つの幾何シェーダー(コンパイル単位)は、最大出力頂点数を宣言しなければならず、プログラム内のすべての幾何シェーダーの出力頂点数宣言は、同じ回数を宣言しなければならない。プログラム内のすべての幾何シェーダーが回数を宣言することは必須ではない。
layout(triangle_strip, max_vertices = 60) out; // order does not matter
layout(max_vertices = 60) out; // redeclaration okay
layout(triangle_strip) out; // redeclaration okay
layout(points) out; // error, contradicts triangle_strip
layout(max_vertices = 30) out; // error, contradicts 60
この例では、幾何シェーダーからの出力はすべて三角形であって、シェーダーから出力されるのは高々 60 個の頂点だ。最大の頂点数が gl_MaxGeometryOutputVertices よりも大きい場合はエラーとなる。
識別子 stream は、幾何シェーダーの出力変数またはブロックが、特定の頂点ストリーム(ゼロから始まる番号)に関連付けられていることを指定するために用いられる。ストリーム番号の既定値を、次の例のようにインターフェイス修飾子 out によって大域スコープで宣言することができる:
layout(stream = 1) out;
このような宣言で指定されたストリーム番号は、それまでの既定値を置き換え、新しい既定値が設定されるまで、後続のすべてのブロックおよび変数の宣言に適用される。初期設定のストリーム番号はゼロだ。
各出力ブロックまたは非ブロック出力変数は、頂点ストリームに関連付けられる。ブロックまたは変数がストリーム識別子とともに宣言されている場合は、指定されたストリームに関連付けられ、そうでない場合は、現在の既定ストリームに関連付けられる。ブロックメンバーはストリーム識別子を付けて宣言することができるが、指定されたストリームは含まれるブロックに関連付けられたそれと一致しなければならない。例:
layout(stream=1) out; // default is now stream 1
out vec4 var1; // var1 gets default stream (1)
layout(stream=2) out Block1 { // "Block1" belongs to stream 2
layout(stream=2) vec4 var2; // redundant block member stream decl
layout(stream=3) vec2 var3; // ILLEGAL (must match block stream)
vec3 var4; // belongs to stream 2
};
layout(stream=0) out; // default is now stream 0
out vec4 var5; // var5 gets default stream (0)
out Block2 { // "Block2" gets default stream (0)
vec4 var6;
};
layout(stream=3) out vec4 var7; // var7 belongs to stream 3
幾何シェーダーで出力された各頂点は、特定のストリームに割り当てられ、出力された頂点の属性は、対象となるストリームに割り当てられた出力ブロックと変数の集合から取得される。各頂点が放出された後、すべての出力変数の値は未定義となる。さらに、各頂点ストリームに関連する出力変数は、格納を共有してもよい。あるストリームに関連する出力変数に書き込むと、他のストリームに関連する出力変数が上書きされることがある。幾何シェーダーは、各頂点を放出する際に、その頂点が放出されるストリームに関連するすべての出力に書き込み、他のストリームに関連する出力には書き込まない。
幾何シェーダー出力ブロックや同変数が複数回宣言された場合、そのような宣言のすべてで、その変数は同じ頂点ストリームに関連付けられなければならない。存在しないストリーム番号を指定したストリーム宣言がある場合、シェーダーのコンパイルは失敗する。
組み込み幾何シェーダー出力は頂点ストリーム 0 に常に関連付けられる。
プログラム内のすべての幾何シェーダー出力レイアウト宣言は、同じレイアウトと同じ
max_vertices 値を宣言しなければならない。幾何シェーダーがプログラムに含まれている場合、そのプログラムのどこかに少なくとも一つの幾何出力レイアウト宣言がなければならないが、すべての幾何シェーダー(コンパイル単位)がそれを宣言する必要はない。
Fragment Outputs¶
組み込み断片シェーダ変数 gl_FragDepth は、次のレイアウト修飾子のいずれかを使用して再宣言することができる:
depth_anydepth_greaterdepth_lessdepth_unchangedgl_FragDepth に対するレイアウト修飾子は、任意のシェーダー呼び出しによって書き込まれる gl_FragDepth の最終値の意図を制約する。レイアウト修飾子に整合する
gl_FragDepth のすべての値が不合格・合格する場合には、与えられた断片に対して奥行き検定が不合格・合格すると仮定して最適化を行うことが GL 実装はできる。これには、断片がさえぎられ、シェーダーに副作用がないために廃棄される場合に、シェーダーの実行を飛ばすことが含まれる可能性がある。gl_FragDepth の最終値がそのレイアウト修飾子と整合しない場合、対応する断片の奥行き検定の結果は未定義となる。しかし、この場合にはエラーとはならない。奥行き検定が合格し、奥行き書き込みが有効な場合、奥行きバッファーに書き込まれる値は、レイアウト修飾子と整合しているか否かに関わらず、常に gl_FragDepth の値となる。
既定では gl_FragDepth は depth_any として修飾される。gl_FragDepth のレイアウト修飾子が depth_any の場合、シェーダーコンパイラーは
gl_FragDepth への割り当てが未知の方法で変更されていることに留意し、奥行き検定は常にシェーダーの実行後に行われる。レイアウト修飾子が depth_greater の場合、gl_FragDepth の最終値が gl_FragCoord の z 成分で与えられる断片の補間奥行き値以上であると仮定することができる。レイアウト修飾子が depth_less
の場合、gl_FragDepth を修正してもその値が減少するだけだと考えることができる。レイアウト修飾子が depth_unchanged の場合、シェーダーコンパイラーは
gl_FragDepth のいかなる修正も尊重するが、他の部分は gl_FragDepth に新しい値が割り当てられていないと仮定することができる。
gl_FragDepth の再宣言は以下のように行われる:
// redeclaration that changes nothing is allowed +
out float gl_FragDepth;
// assume it may be modified in any way
layout(depth_any) out float gl_FragDepth;
// assume it may be modified such that its value will only increase
layout(depth_greater) out float gl_FragDepth;
// assume it may be modified such that its value will only decrease
layout(depth_less) out float gl_FragDepth;
// assume it will not be modified
layout(depth_unchanged) out float gl_FragDepth;
gl_FragDepth がプログラム内のいずれかの断片シェーダーで再宣言された場合、
gl_FragDepth への静的割り当てを持つそのプログラム内の断片シェーダーすべてで再宣言されなければならない。単一プログラム内の断片シェーダーすべてにおける
gl_FragDepth の再宣言のすべてが同じ修飾子の集合を持たなければならない。どのシェーダー内でも、
gl_FragDepth の最初の再宣言がその使用よりも前に現れなければならない。組み込み gl_FragDepth は断片シェーダーでのみ事前宣言されているので、他のシェーダー言語で再宣言するとコンパイルエラーになる。
4.4.3. Uniform Variable Layout Qualifiers¶
レイアウト修飾子は、一様変数とサブルーチン一様に使用できる。一様変数とサブルーチン一様(変数)に対するレイアウト修飾子識別子は次のとおり:
location = layout-qualifier-value位置識別子は、既定ブロック一様変数とサブルーチン一様(変数)で使用できる。位置は、API が一様を参照し、その値を更新するための位置を指定する。一様配列の個々の要素には連続した位置が割り当てられ、最初の要素は位置 location だ。プログラム内でリンクされた同じ位置を共有する既定ブロック一様変数宣言は、名前、型、修飾子、配列性が一致しなければならない。配列の場合は、配列の次元と配列のサイズが一致していなければならない。構造体の場合、この規則がメンバーすべてに再帰的に適用される。サブルーチン一様変数二つが同じシェーダー段階内で同じ位置に存在することはできない。コンパイルエラーかリンクエラーとなる。既定ブロック変数の有効な位置は、0 から実装定義の一様位置の最大数から 1 を引いた範囲にある。サブルーチン一様(変数)の有効な位置は、0 から実装定義の段階ごとのサブルーチン一様(変数)の最大位置数から 1
を引いた値までの範囲にある。
位置は既定ブロック一様配列や構造体に割り当てることができる。最も内側にある最初のスカラー、ベクトル、行列のメンバーや要素は指定された location を取り、コンパイラーは次の最も内側にあるメンバーや要素に次の増分の位置値を割り当てる。それ以降の最内側のメンバーや要素は、構造体や配列全体の増分の位置を取得する。この規則は、入れ子になった構造体や配列にも適用され、最も内側にあるスカラー、ベクトル、行列の各メンバーに一意の位置を与える。明示的サイズのない配列の場合は、静的な使用量に基づいてサイズが計算される。リンカーが明示的位置のない一様(変数)の位置を生成する場合、明示的位置のある一様(変数)すべてについて、その配列要素や構造体のメンバーがすべて使用されていると仮定し、その要素やメンバーが使用されていないと判断された場合でも、リンカーは競合する位置を生成しない。
個々の(既定ブロック)透明一様変数を受け入れる API に対する SPIR-V を生成するとき、それらを宣言するときに位置を含めないとコンパイルエラー。
Vulkan を対象にしている場合、修飾子 push_constant はブロック全体を宣言するために使用され、Vulkan API で定義されているように、定数 push の集合を表す。これを一様ブロック宣言以外に適用したり、Vulkan を対象にしていない場合はコンパイルエラー。ブロック内の値は Vulkan API の仕様に基づいて初期化される。
layout(push_constant) で宣言されたブロックは、オプションで instance-name
を含めることができる。push_constant ブロックは段階ごとに一つだけで、そうでない場合はコンパイルエラーやリンクエラー。push_constant 配列は、動的一様なインデックスでしかインデックスを付けられない。push_constant で宣言された一様ブロックは、宣言されていないブロックとは異なる資源を使用し、別々に計上される。
4.4.4. Subroutine Function Layout Qualifiers¶
レイアウト修飾子をサブルーチン関数に使用できる。サブルーチン関数のレイアウト修飾子識別子は次のとおり:
index = layout-qualifier-valueシェーダー内でインデックス修飾子を持つ各サブルーチンには、一意のインデックスが与えられなければならない。そうでない場合はコンパイルエラーまたはリンクエラー。インデックスは 0 から実装定義のサブルーチン最大数から 1 を引いた範囲でなければならない。OpenGL サブルーチン関数列挙 API がアクティブインデックスすべてに対して空ではない名前を返すように、シェーダーは 0 から始まる飛び飛びにならないインデックス値の範囲を割り当てることを推奨するが、必須ではない。
4.4.5. Uniform and Shader Storage Block Layout Qualifiers¶
レイアウト修飾子は一様およびシェーダー格納ブロックに使用できるが、非ブロック一様宣言には使用できない。一様およびシェーダー格納ブロックのレイアウト修飾子の識別子(および shared キーワード)は次のとおり:
sharedpackedstd140std430row_majorcolumn_majorbinding = layout-qualifier-valueoffset = layout-qualifier-valuealign = layout-qualifier-valueこれらはいずれも、宣言されている変数の使用法に意味的な影響を全く与えず、メモリー内でのデータの配置方法を示すに過ぎない。例えば、行列のセマンティクスは、どのようなレイアウト修飾子が使用されていても、この仕様の他の部分で述べられたように、常に列に基づく。
(一様|シェーダー格納)ブロックレイアウト修飾子は、大域スコープ、単一の(一様|シェーダー格納ブロック|ブロックメンバー)宣言上で宣言することができる。
一様ブロックには大域スコープでの既定レイアウトが次のように設定されている:
layout(layout-qualifier-id-list) uniform;
シェーダー格納ブロックの場合には:
layout(layout-qualifier-id-list) buffer;
これが行われると、直前の既定修飾がまず継承され、それから宣言にある各修飾について、以下に述べる上書き規則に従って上書きされる。その結果、後続の(一様|シェーダー格納)ブロックの定義に適用される新しい既定修飾となる。
SPIR-V を生成する際のコンパイルの初期状態は、次のように宣言されたかのようなものだ:
layout(std140, column_major) uniform;
layout(std430, column_major) buffer;
ところが push_constant を宣言すると、バッファーの既定レイアウトは std430
になる。この既定を大域的に設定する方法はない。
SPIR-V を生成していないときのコンパイルの初期状態は、次のように宣言されたかのようなものだ:
layout(shared, column_major) uniform;
layout(shared, column_major) buffer;
(一様|シェーダー格納)ブロックは、オプションでレイアウト修飾子を付けて宣言することができ、その個々のメンバー宣言も同様だ。このようなブロックレイアウト修飾は、ブロックの内容にのみ適用される。大域レイアウト宣言と同様に、ブロックレイアウト修飾は、まず現在の既定の修飾を継承し、次にそれを上書きする。同様に、個々のメンバーのレイアウト修飾は、メンバー宣言だけに適用され、ブロックの修飾を継承して上書きする。
修飾子 shared は std140, std430, および packed 修飾子のみを上書きし、その他の修飾子は継承される。コンパイラーかリンカーは、すべての配列が明示的なサイズで宣言され、すべての行列が一致する row_major 修飾子と
column_major 修飾子の両方を、または一方を(ブロック定義外の宣言から得られる場合もある)持つ限り、この定義を含む複数のプログラムおよびプログラム可能段階が、このブロックのために同じメモリーレイアウトを共有することを保証する。これにより、異なるプログラム間で同じバッファーを使用して同じブロック定義をback することができる。SPIR-Vを生成する際に shared を使用するとコンパイルエラーとなる。
修飾子 packed は std140, std430, shared のみを上書きし、その他の修飾子は継承される。packed を使用した場合、共有可能なレイアウトは保証されません。コンパイラーとリンカーは、どの変数が活発的に使用されるかや、その他の条件に基づいて、メモリー使用を最適化することができる。ブロック内のどこにどの変数があるかを保証する方法が他にないため、オフセットを問い合わせる必要がある。
プログラム内の複数の段階で同じ充満一様ブロックやシェーダー格納ブロックにアクセスするとリンクエラー。プログラム間で同じ充満一様ブロックやシェーダー格納ブロックにアクセスしようとすると、メンバーオフセットが衝突したり、未定義の値が読み込まれたりする可能性がある。しかし、実装では充満ブロックのための標準的なレイアウトを使用することで、充満ブロックのアプリケーション管理を支援することができる。SPIR-V の生成時に packed 修飾子を使用するとコンパイルエラー。
std140 および std430 修飾子は packed, shared, std140,
std430 修飾子のみを上書きし、その他の修飾子は継承される。std430 修飾子はシェーダー格納ブロックでのみ対処されている。一様ブロックに std430 修飾子を使用したシェーダーは、それが push_constant も修飾されていない限りコンパイルエラー。
OpenGL 仕様書の 7.6.2.2 “Standard Uniform Block Layout” に記載されているように、レイアウトはこれによって明示的に決定される。したがって、上記の shared と同様に、結果となるレイアウトはプログラムをまたがって共有可能だ。
メンバー宣言のレイアウト修飾子には shared, packed, std140,
std430 の各修飾子は使用できない。これらの修飾子は、(オブジェクトなし)大域スコープまたはブロック宣言でのみ使用でき、使用しない場合はコンパイルエラー。
row_major および column_major 修飾子は、構造体や配列に含まれるすべての行列を含むすべての入れ子の深さの行列のレイアウトにしか影響を与えない。これらの修飾子は他の型にも適用できるが、効果はない。
row_major 修飾子は column_major 修飾子のみを上書きし、その他の修飾子は継承される。行列の行内の要素は、メモリー内で連続している。
column_major 修飾子は row_major 修飾子よりも優先されるが、その他の修飾子は継承される。行列の列内の要素は、メモリー内で連続している。
binding 修飾子は(一様|シェーダー格納)ブロックに対応する一様バッファー束縛点を指定し、ブロックのメンバー変数の値を取得するのに使用される。大域スコープやブロックメンバーの宣言に束縛修飾子を指定することはコンパイルエラーとなる。
binding 修飾子を指定せずに宣言された(一様|シェーダー格納)ブロックは、最初はブロック束縛点 0 に割り当てられる。プログラムがリンクされた後、binding 修飾子の有無にかかわらず宣言された(一様|シェーダー格納)ブロックに使用される束縛点は API によって更新することができる。
OpenGL で使用する場合、binding 修飾子が配列としてインスタンス化された一様ブロックやシェーダー格納ブロックで使用されると、配列の最初の要素は指定されたブロック束縛を取り、それ以降の各要素は次の連続した束縛点を取る。配列の配列では、要素それぞれ(例えば a[2][3] の場合は 6 要素)が束縛点を得て、
4.1.9. Arrays で説明した配列の配列の順序に従って順序付けられる。
Vulkan を対象にした場合、配列としてインスタンス化された一様ブロックやバッファーブロックで binding 修飾子が使用されると、配列全体が提供された束縛番号のみを受け取る。次の連続した束縛番号を別のオブジェクトで利用できる。配列の配列の場合、記述子集合アクセスに使用される記述子集合配列要素の番号は、
4.1.9. Arrays で説明した配列の配列の順序に従って順序付けられる。
(一様|シェーダー格納)ブロックインスタンスの束縛点がゼロより小さい場合、または対応する実装依存のバッファー束縛の最大数以上の場合は、コンパイルエラーとなる。(一様|シェーダー格納)ブロックのインスタンスをサイズ N の配列として
binding 修飾子を使用する場合、binding から binding + N - 1 までの配列のすべての要素は、この範囲内に収まらなければならない。同じ束縛番号を複数の一様ブロックや複数のバッファーブロックに使用するとコンパイルエラーまたはリンクエラーとなる。
修飾子 set は Vulkan を対象にしている場合に限り使用できる。このオブジェクトが属する記述子集合を指定する。独立した修飾子、ブロックメンバー、または記述子集合を対処する API を対象にしていない場合に set を適用するとコンパイルエラー。
push_constant 修飾されたブロックに set を適用するとコンパイルエラー。既定では set 識別子なしで宣言された非 push-constant (一様|シェーダー格納)ブロックは、記述子集合 0 に割り当てられる。同様に、set 識別子なしで一様として宣言された採取器、テクスチャー、またはサブパス入力型も、記述子集合 0 に割り当てられる。
配列として宣言されたオブジェクトに適用された場合、その配列のすべての要素は、指定された set に属する。
SPIR-V を生成する際に set または binding の値がフロントエンド構成で指定された最大値を超えるとコンパイルエラーとなる。
一つのレイアウト宣言に複数の引数がリストされている場合、左から右の順に一つずつ宣言されたのと同じ効果が得られる。それぞれが前の修飾の結果を引き継いで上書きする。例えば、次の例は column_major 修飾になる:
layout(row_major, column_major)
さらなる例:
layout(shared, row_major) uniform; // default is now shared and row_major
layout(std140) uniform Transform { // layout of this block is std140
mat4 M1; // row major
layout(column_major) mat4 M2; // column major
mat3 N1; // row major
};
uniform T2 { // layout of this block is shared
...
};
layout(column_major) uniform T3 { // shared and column major
mat4 M3; // column major
layout(row_major) mat4 m4; // row major
mat3 N2; // column major
};
Vulkan を対象にしている場合、ブロックおよびブロックメンバーの offset および
align 修飾子は、一様ブロックおよびバッファーブロックに限り使用できる。
Vulkan を対象にしていない場合、これらの修飾子は std140 または std430 レイアウトで宣言されたブロックに限り使用できる。
修飾子 offset はブロックメンバーに限り使用できる。修飾子 offset は、修飾されたメンバーを指定された layout-qualifier-value (バッファーの先頭からのバイトオフセット)以降に強制的に開始させる。ブロックの他のメンバー内にあるオフセットを明示的または代入的に持たせるとコンパイルエラーとなる。SPIR-V を生成しない場合、ブロック内の直前のメンバーのオフセットよりも小さいオフセットを指定するとコンパイルエラーとなる。同じプログラム内で同じブロック名でリンクされた二つのブロックは、offset で修飾されたメンバーの集合が全く同じであり、それらの
layout-qualifier-value の値が同じでなければならず、そうでなければリンクエラーとなる。指定されたオフセットは、それが修飾するブロックメンバーの型の base
alignment の倍数でなければならず、そうでなければコンパイルエラーになる。
修飾子 align は、各ブロックメンバーの開始点を最小の byte alignment にする。各メンバー内の内部レイアウトには影響を与えず、std140 または std430 の規則に従う。指定された alignment は 0 よりも大きく、かつ 2 のべき乗でなければならず、そうでない場合はコンパイルエラー。
メンバーの実際の alignment は、指定された align alignmentと、そのメンバーの型の標準(std140 など)base alignment のうち大きい方になる。メンバーの実際のオフセットは次のように計算される: offset が宣言されている場合は、そのオフセットから開始し、そうでない場合は、宣言順で先行メンバーの直後のオフセットから開始する。結果オフセットが実際の alignment の倍数でない場合は、実際の alignment の倍数である最初のオフセットまで増やす。これにより、メンバーの実際のオフセットが得られる。
align が配列に適用された場合、配列の開始点のみに影響し、配列の内部幅には影響しない。宣言では offset 修飾子と align 修飾子の両方を指定できる。
修飾子 align は、ブロックで使用された場合、ブロックで宣言されたのと同じ
align 値で各メンバーを修飾するのと同じ効果があり、これが行われた場合と同じコンパイル結果とコンパイルエラーが得られる。前述のように、個々のメンバーは独自の
align を指定することができ、それはブロックレベルの align よりも優先されるが、そのメンバーに限られる。
例:
layout(std140) uniform block {
vec4 a; // a takes offsets 0-15
layout(offset = 32) vec3 b; // b takes offsets 32-43
layout(offset = 40) vec2 c; // ERROR, lies within previous member
layout(offset = 48) vec2 d; // d takes offsets 48-55
layout(align = 16) float e; // e takes offsets 64-67
layout(align = 2) double f; // f takes offsets 72-79
layout(align = 6) double g; // ERROR, 6 is not a power of 2
layout(offset = 80) float h; // h takes offsets 80-83
layout(align = 64) dvec3 i; // i takes offsets 128-151
layout(offset = 164, align = 8)
float j; // j takes offsets 168-171
};
4.4.6. Opaque Uniform Layout Qualifiers¶
不透明な一様変数は、結合のために一様レイアウト修飾子を取ることができる。
binding = layout-qualifier-valuebinding 修飾子は、その変数が束縛される点を指定する。binding 修飾子なしで宣言された不透明変数は、既定束縛 0 になる。
OpenGL で使用する場合、binding 修飾子が配列とともに使用されると、配列の最初の要素は指定された束縛点を取り、それ以降の各要素は連続した次の束縛点を取る。配列の配列の場合は先程の a[2][3] の例を述べた文と同じ。
Vulkan を対象にした場合、配列で binding 修飾子が使用されると、提供された束縛番号だけを配列全体が取る。次の連続した束縛番号は、別のオブジェクトに利用できる。
binding が 0 より小さい場合、または実装依存の対処される束縛点の最大数以上の場合は、コンパイルエラーとなる。サイズ N の配列で binding 修飾子を使う場合、番号 binding から番号 binding + N - 1 までの配列のすべての要素は、この範囲内に収まる必要がある。同じ番号 binding を共有する不可分計数器のオフセットがすべて異なっていない限り、複数の不可分計数器に同じ束縛番号を使用すると、コンパイルエラーまたはリンクエラーとなる。
一つのプログラム内のシェーダー二つが同じ opaque-uniform 名に対して異なる
layout-qualifier-value 束縛を指定するとリンクエラーとなる。ただし、次の例のように、同じ名前の宣言の一部に binding を指定しても、すべての宣言に
binding を指定してもエラーではない:
// in one shader...
layout(binding=3) uniform sampler2D s; // s bound to point 3
// in another shader...
uniform sampler2D s; // okay, s still bound at 3
// in another shader...
layout(binding=4) uniform sampler2D s; // ERROR: contradictory bindings
4.4.7. Atomic Counter Layout Qualifiers¶
Vulkan を対象にした場合、不可分計数器は使用できない。
不可分計数器のレイアウト修飾子は、その宣言で使用できる:
binding = layout-qualifier-valueoffset = layout-qualifier-value例えば下は、不可分計数器 a の不透明ハンドルを、不可分計数器バッファーの束縛点 2 に、そのバッファーの 4 基本マシン単位のオフセットで束縛するように設定する。束縛点 2 の既定オフセットは 4(不可分計数器のサイズ)で後置インクリメントされる:
layout(binding = 2, offset = 4) uniform atomic_uint a;
後続の不可分計数器宣言は、直前の(インクリメントされた)オフセットを継承する。例えば次の宣言では、不可分計数器 bar がバッファーの束縛点 2 に 8基本マシン単位のオフセットで束縛されるように設定する。結合点 2 のオフセットは、再び 4 だけ後置インクリメントされる:
layout(binding = 2) uniform atomic_uint bar;
一つのレイアウト宣言に複数の変数がリストされている場合、左から右の順に一つずつ宣言したのと同じ効果が得られる。
束縛点は継承されず、オフセットのみが継承される。各束縛点では、同じ束縛を使用する後続の変数の継承のために、それ自身の現在の既定オフセットを追跡する。コンパイルの初期状態では、すべての束縛点のオフセットが 0 になる。オフセットは、大域スコープで(変数を宣言せずに)束縛点ごとに設定できる。例えば、以下のようになる:
layout(binding = 2, offset = 4) uniform atomic_uint;
上記は結合点 2 に対する次の atomic_uint 宣言がオフセット 4 を継承するように設定する(ただし、既定束縛は設定しない)。
layout(binding = 2) uniform atomic_uint bar; // offset is 4
layout(offset = 8) uniform atomic_uint bar; // error, no default binding
不可分計数器は同じ束縛点を共有することができるが、束縛が共有されている場合、それらのオフセットは、明示的または暗黙的に一意的であり、重なり合わないことが必要だ。
シェーダーの先頭を想定した、有効な一様宣言の例:
layout(binding=3, offset=4) uniform atomic_uint a; // offset = 4
layout(binding=2) uniform atomic_uint b; // offset = 0
layout(binding=3) uniform atomic_uint c; // offset = 8
layout(binding=2) uniform atomic_uint d; // offset = 4
無効な一様宣言の例:
layout(offset=4) ... // error, must include binding
layout(binding=1, offset=0) ... a; // okay
layout(binding=2, offset=0) ... b; // okay
layout(binding=1, offset=0) ... c; // error, offsets must not be shared
// between a and c
layout(binding=1, offset=2) ... d; // error, overlaps offset 0 of a
gl_MaxAtomicCounterBindings 以上の束縛値で不可分計数器を束縛するとコンパイルエラーになる。サイズなし atomic_uint の配列を宣言するとコンパイルエラー。
4.4.8. Format Layout Qualifiers¶
フォーマットレイアウト修飾子は、画像変数の宣言(キーワードに "image" を持つ基本型で宣言されたもの)に使用できる。画像変数宣言のためのフォーマットレイアウト修飾子の識別子は次のとおり(一部略):
binding = layout-qualifier-valuergba32frgba16frgba32irgba32uiformat レイアウト修飾子は、宣言された画像変数に関連する画像表現形式を指定する。画像変数宣言一個につき、フォーマット修飾子を一つだけ指定できる。浮動小数点成分型("image" で始まるキーワード)、符号付き整数成分型("iimage" で始まるキーワード)、または符号なし整数成分型("uimage" で始まるキーワード)の画像変数では、使用するフォーマット修飾子は、それぞれ
float-image-format-qualifier, int-image-format-qualifier,
uint-image-format-qualifier 文法規則に一致しなければならない。フォーマット修飾子が画像変数の型と一致しない画像変数を宣言するとコンパイルエラーとなる。
画像読み込みや不可分操作に使用される画像変数は、フォーマットレイアウト修飾子を指定しなければならない。フォーマットレイアウト修飾子なしで宣言された画像一様変数や関数引数を画像読み込みや不可分関数に渡すとコンパイルエラーとなる。
writeonly で修飾されていない一様(変数)はフォーマットレイアウト修飾子を持たなければならない。読み込みアクセスのために関数に渡された画像変数を writeonly
として宣言することはできないので、フォーマットレイアウト修飾子を付けて宣言しなければならないことに注意を要する。
修飾子 binding については 4.4.6. Opaque Uniform Layout Qualifiers に記述がある。
4.4.9. Subpass Input Qualifier¶
サブパス入力は Vulkan を対象にしている場合に限り利用可能だ。
サブパス入力は、基本 subpassInput 型で宣言される。これらはレイアウト修飾子
input_attachment_index を付けて宣言しないとコンパイルエラー。例えば:
layout(input_attachment_index = 2) uniform subpassInput t;
これはどのサブパスから入力が読み込まれるかを選択する。
input_attachment_index に割り当てられた値、たとえば i とする
(input_attachment_index = i) と、パスの入力リストの中のその i 番目の登場を選択する。
サイズ N の配列が宣言されている場合は、提供されたものから始まる N 個の連続した
input_attachment_index 値を消費する。
同じ input_attachment_index を異なる変数に宣言すると、コンパイルエラーまたはリンクエラーとなる。これには、配列宣言で消費される暗黙の
input_attachment_index の重複も含まれる。
input_attachment_index に割り当てられた値が gl_MaxInputAttachments 以上の場合、コンパイルエラーとなる。
4.5. Interpolation Qualifiers¶
補間される可能性のある入力および出力は、以下の補間修飾子のうち高々一つによってさらに修飾される:
修飾子 |
意味 |
|---|---|
|
透視図法的補正補間 |
|
補間なし |
|
線形補間 |
補間の有無と型は上記の補間修飾子と補助格納修飾子 centroid と sample によって制御される。補間修飾子がない場合は smooth 補間が行われる。複数の補間修飾子を使用するとコンパイルエラーとなる。補助格納修飾子 patch は補間には使用されない。補間修飾子を patch と共に使用するとコンパイルエラーとなる。
flat と指定された変数は補間されない。代わりに、基本形状内の断片みんなに対して同じ値を持つ。この値は、API で説明されているように、単一の provoking vertex から得られる。flat と修飾された変数は centroid や sample と修飾されることもあり、これは flat としか修飾されていないのと同じ意味になる。
読者ノート
この provoking vertex とは?
smooth と修飾された変数は、レンダリングされている基本形状の上に、透視図法的な補正による方法で補間される。遠近法的に正しい方法での補間は、OpenGL 仕様の式
14.7 および 14.5 “Line Segments” で規定されている。
nonperspective と修飾された変数は、OpenGL 仕様書 3.5 “Line Segments” の式
3.7 で述べられているように、スクリーン空間内で線形に補間しなければならない。
多重採取ラスタライズが無効の場合、または centroid 修飾も sample 修飾もない断片シェーダー入力変数の場合、割り当てられた変数の値は、OpenGL 仕様で許可されている範囲内で、画素内の任意の場所に補間することができ、画素内の各標本に単一の値を割り当てることができる。
多重採取ラスタライズが有効な場合、centroid と sample を使用して、修飾された断片シェーダー入力の採取位置と頻度を制御することができる。断片シェーダー入力が centroid で修飾されている場合、その変数には画素内のすべての標本に対して単一の値を割り当てることができるが、その値は、基本形状で網羅されている画素の標本を含めて、画素とレンダリングされている基本形状の両方に位置する場所で補間されなければならない。変数が補間される位置は隣接する画素で異なる可能性があり、微係数は隣接する画素間の差を計算することで算出されるため、centroid 採取された入力の微係数は、centroid 以外で補間された変数のそれよりも精度が低い可能性がある。断片シェーダーの入力が sample で修飾されている場合、その変数には、画素内の網羅された標本ごとに個別の値が割り当てられなければならず、その値は個々の標本の位置で採取されなければならない。
同一段階内で同じ名前の変数の補間修飾子が一致しないとリンクエラーになる。
4.5.1. Redeclaring Built-In Interpolation Variables in the Compatibility Profile¶
互換性プロファイルを使用する場合、次の宣言済み変数を補間修飾子付きで再宣言できる。
頂点、細分化制御、細分化評価、幾何の各言語
gl_FrontColorgl_BackColorgl_FrontSecondaryColorgl_BackSecondaryColor
断片言語
gl_Colorgl_SecondaryColor
例:
in vec4 gl_Color; // predeclared by the fragment language
flat in vec4 gl_Color; // redeclared by user to be flat
flat in vec4 gl_FrontColor; // input to geometry shader, no "gl_in[]"
flat out vec4 gl_FrontColor; // output from geometry shader
理想的には、7.1.7. Compatibility Profile Built-In Language Variables で記述されるように、インターフェイスブロックの再宣言の一部としてこれらを再宣言する。しかし、上記の目的のために、インターフェイスブロックの外部で、大域スコープの個々の変数として再宣言することもできる。このような再宣言をすると、出力変数に変換反響修飾子である xfb_buffer,
xfb_stride, xfb_offset を付けることもできる。
変数上の
xfb_bufferは大域既定バッファーを変化させない。
シェーダーが、インタフェイスブロックの再宣言と、そのインタフェイスブロックのメンバーの再宣言を、このインタフェースブロックの再宣言の外側で別々に行うと、コンパイルエラーになる。
gl_Color が補間修飾子で再宣言された場合、gl_FrontColor と
gl_BackColor も同じ補間修飾子で再宣言されなければならず、その逆もまた然り。
gl_SecondaryColor が補間修飾子付きで再宣言されたならば、
gl_FrontSecondaryColor と gl_BackSecondaryColor も同じ補間修飾子付きで再宣言されなければならず、その逆もまた然り。このような、宣言済みの変数に対する修飾子の符合判定は、プログラム中のシェーダー内で静的に使用される変数に対してのみ必要だ。
4.6. Parameter Qualifiers¶
精度修飾子とメモリー修飾子に加えて、次のパラメータ修飾子を引数に付けることができる:
修飾子 |
意味 |
|---|---|
なし(既定) |
|
|
書き込めない引数 |
|
関数に渡される引数 |
|
関数に渡される引数のうち、初期化されていないもの |
|
関数の中と外の両方に渡される引数 |
引数修飾子については 6.1.1. Function Calling Conventions に記述がある。
4.7. Precision and Precision Qualifiers¶
Vulkan を対象にしていない場合:精度修飾子は OpenGL ES とのコードの可搬性のために追加されたものであって、機能的なものではない。精度修飾子は OpenGL ES と同じ構文を持つが、セマンティックな意味はなく、変数の格納や演算に使用される精度にも影響しない。拡張機能が OpenGL ES 2.0 仕様の精度修飾子と同じセマンティクスと機能を追加する場合は、その目的のために本節で記述されるキーワードを再利用することができる。
Vulkan を対象とする場合:インターフェイスの照合処理では、一様変数、一様ブロック、バッファーブロックメンバーは、同じ精度修飾を持たなければならない。同じシェーダー段階にリンクされていて異なるコンパイル単位で宣言された大域変数は、同じ精度修飾子で宣言されなければならない。
あるシェーダー段階の出力が直後の段階の入力と一致するかどうかを判断する目的では、精度修飾子が一致する必要はない。
4.7.1. Range and Precision¶
highp 単精度および倍精度浮動小数点変数の精度は 32 ビットおよび 64 ビット浮動小数点数の IEEE 754 規格により定義される。
これには、NaN と Inf、および正と負のゼロの対処が含まれる。
以下の規則は、単精度と倍精度の演算の両方で highp に適用される。符号付きの無限大とゼロは、IEEE の規定に従って生成されるが、次の表(省略)で許可されている精度が適用される。シェーダーに入力された非正規化値、またはシェーダー内の任意の演算で潜在的に生成される値は、0 に flush することができる。丸めモードは設定することができず、未定義だが、結果に 1ULP 以上の影響を与えてはならない。NaN を生成する必要はない。NaN の signaling の対処は必須ではなく、例外は決して発生しない。NaN``を操作する組み込み関数を含む操作は、結果として ``NaN を返す必要はない。しかし、NaN が生成された場合 isnan() は正しい値を返さなければならない。
特に断りのない限り、精度は ULP 単位で最大相対誤差で表される。
単精度演算の場合、精度は以下のように(略)求められる。
読者ノート
ちょっとした表が入るが、省略。
上記の操作から作られた式を持つ、仕様書で定義された組み込み関数は、上記の誤差を継承する。
これらには、例えば、幾何関数、共通関数、および多くの行列関数が含まれる。上記に記載されておらず、上記の方程式で定義されていない組み込み関数は、精度が未定義だ。例えば、三角関数や行列式などが該当する。
倍精度演算の精度は、少なくとも単精度のそれはある。
4.7.2. Precision Qualifiers¶
単精度浮動小数点型、整数型、不透明型宣言では、これらの精度修飾子の一つを型の前に付けることができる:
修飾子 |
意味 |
|---|---|
|
整数に対しては 32 ビット長 2の補数、浮動小数点数に対しては 32 ビット長 IEEE 754 浮動小数点 |
|
Vulkan を対象にしている場合は SPIR-V RelaxedPrecision, それ以外の場合はなし。 |
|
同上 |
例:
lowp float color;
out mediump vec2 P;
lowp ivec2 foo(lowp mat3);
highp mat4 m;
リテラル定数には精度修飾子がない。真偽変数にもない。コンストラクターにもない。
この段落で言う「操作」には演算子、組み込み関数、コンストラクターを含み、「オペランド」には関数実引数とコンストラクター実引数を含むことにする。演算を内部的に評価するために使用される精度と、その後に生じる中間値に関連する精度修飾子は、演算によって消費されるオペランドの最高精度修飾子と少なくとも同じでなければならない。
オペランドが精度修飾子を持たない場合には、精度修飾子は他のオペランドから得られる。精度修飾子のあるオペランドがない場合は、式の中で次に消費される演算のオペランドの精度修飾子が使用される。この規則は、精度修飾されたオペランドが見つかるまで再帰的に適用することができる。必要に応じて、代入のための左辺値の精度修飾、初期化子のための被宣言変数の精度修飾、関数呼び出し実引数に対する仮引数の精度修飾、関数戻り値のための関数戻り型の精度修飾も含まれる。この方法で精度が決定できない場合、例えば、式全体が精度修飾子のないオペランドだけで構成されていて、その結果が代入されたり実引数として渡されたりしない場合は、その型の既定精度あるいはそれ以上で評価される。断片シェーダーでこのような事態が発生した場合、既定の精度を定義する必要がある。
例えば、次の文を考える:
uniform highp float h1;
highp float h2 = 2.3 * 4.7; // operation and result are highp precision
mediump float m;
m = 3.7 * h1 * h2; // all operations are highp precision
h2 = m * h1; // operation is highp precision
m = h2 - h1; // operation is highp precision
h2 = m + m; // addition and result at mediump precision
void f(highp float p);
f(3.3); // 3.3 will be passed in at highp precision
精度修飾子は、他の修飾子と同様に、変数の基本型には影響しない。特に、精度変換のためのコンストラクターはない。コンストラクターは型を変換するだけだ。同様に、精度修飾子は、他の修飾子と同様に、引数型に基づく関数のオーバーロードには関与しない。 6.1.1. Function Calling Conventions で述べられているように、関数入出力はコピーによって行われるので、修飾子は一致しなくても構わない。
変数の精度はその変数が宣言された時点で決定され、その後変化することはあり得ない。
定整数式または定浮動小数点式の精度が指定されていない場合、評価は highp で行われる。この規則は式の精度修飾には影響しない。
定数式の評価は不変でなければならず、通常はコンパイル時に実行される。
4.7.3. Default Precision Qualifiers¶
精度文
precision precision-qualifier type;を既定精度修飾子を設定するために使用することができる。type 欄は int,
float, 不透明型のいずれかであり、precision-qualifier には lowp,
mediump, highp が宣言される。
これ以外の型や修飾子を指定するとコンパイルエラーになる。type が float の場合、この指令は精度修飾のない単精度浮動小数点型(スカラー、ベクトル、行列)の宣言に適用される。type が int の場合は、精度修飾のない整数型(スカラー、ベクトルー、符号あり、符号なし)の宣言に適用される。これには、大域変数宣言、関数戻り値宣言、関数引数タ宣言、局所変数宣言が含まれる。
精度修飾のない宣言は、スコープ内にある直近の精度文で指定された精度修飾子を使用する。precision 文には変数宣言と同じスコープ規則がある。複文の中で宣言された場合、その効果は宣言された最も内側の文の終わりで停止する。入れ子になったスコープ内の精度文は、外側のスコープ内の精度文を上書きする。同じ基本型に対する複数の精度文が同じスコープ内に現れることができ、そのスコープ内で遅れて現れる文が早く現れる文を上書きする。
精度修飾子を受け付ける型に対する既定精度修飾子は highp だ。精度修飾子を必要とする全ての型には既定精度があるので、精度修飾子の省略によるエラーはない。
4.7.4. Available Precision Qualifiers¶
組み込みマクロ GL_FRAGMENT_PRECISION_HIGH は 1 に定義されている:
#define GL_FRAGMENT_PRECISION_HIGH 1
このマクロは計算以外の言語すべてで使用できる。
4.8. Variance and the Invariant Qualifier¶
この節でいうばらつき (variance) とは、異なるプログラムにある同じ式から異なる値が得られる可能性を意味する。例えば、異なるプログラムにある二つの頂点シェーダーがそれぞれ gl_Position を同じ式で設定し、その式への入力値が両方のシェーダーの実行時に同じであるとする。シェーダー二つが独立してコンパイルされているため、これらの実行時に gl_Position に代入される値が完全に同じではないことがあり得る。この例では、多重パスアルゴリズムにおける幾何の alignment に問題が生じ得る。
一般的には、シェーダー間のこのようなばらつきは許容される。特定の出力変数にそのようなばらつきがない場合、その変数は不変である (invariant) と言われる。
4.8.1. The Invariant Qualifier¶
特定の出力変数が不変であることを保証するには、修飾子 invariant を使用する。この修飾子を以前に宣言された変数が不変であることを修飾するのに使用することもできる:
invariant gl_Position; // make existing gl_Position be invariant
out vec3 Color;
invariant Color; // make existing Color be invariant
または、変数が宣言されたときにその宣言の一部とする:
invariant centroid out vec3 Color;
不変性の候補はシェーダーの出力変数しかない。ユーザー定義の出力変数と組み込み出力変数を含む。不変宣言できるのは出力のみなので、あるシェーダー段階の出力は、入力が不変宣言されていなくても後続段階の入力と一致する。
ブロック上の入力または出力インスタンス名は、組み込み変数を再宣言する際には使用されない。
キーワード invariant の後には、以前に宣言された識別子をカンマで区切って列挙することができる。invariant の使用はすべて、大域スコープまたはブロックメンバー上で、かつ invariant として宣言された変数が使用される前でなければならない。
特定の出力変数の不変性を二つのプログラムを横断して保証するには、次も成り立っていなければならない:
出力変数が両方のプログラムで不変であると宣言されている。
出力変数に割り当てられた値に関与する式や制御フローで消費されるシェーダー入力変数すべてに同じ値を入力する必要がある。
テクスチャーフォーマット、テクセル値、およびテクスチャーフィルタリングは、出力変数の値に関与するすべてのテクスチャー関数呼び出しに対して同じ方法に設定される。
入力値すべてが同じ方法ですべて操作される。消費される式と中間式の操作すべてが、評価の順序が同じになるように、オペランドの順序と結合性(左結合とか右結合とかのこと)を同じにしなければならない。中間変数と関数は、同じ明示的または暗黙的精度修飾子を持つ同じ型として宣言されなければならない。出力値に影響を与えるすべての制御フローは同じでなければならず、この制御フローを決定するために消費されるどんな式も、これらの不変性規則に従わなければならない。
不変出力変数を設定するためのデータフローと制御フローすべては、単一のコンパイル単位に宿る。
原則的に、不変出力に至るデータフローと制御フローのすべてで一致しなければならない。
初期状態では、既定で、すべての出力変数に variant が許されている。すべての出力変数を強制的に invariant にするには、プラグマ:
#pragma STDGL invariant(all)
をシェーダー内のすべての宣言の前に使用する。このプラグマがいずれかの変数や関数の宣言の後に使用された場合、不変の動作をする出力集合は未定義となる。
一般に、不変性は最適化の柔軟性を犠牲にして確保されるため、不変性を使用することでパフォーマンスが低下する可能性がある。したがって、このプラグマの使用は、出力変数すべてを一つ一つ invariant と宣言するのを避けるためのデバッグ支援を目的としている。
4.8.2. Invariance of Constant Expressions¶
定数式には不変性が保証されなければならない。特定の定数式は、それが同じシェーダーに現れようが異なるシェーダーに現れようが、同じ結果になるように評価しなければならない。これには、同じ式が同じ言語の二つのシェーダーに現れる場合と、二つの異なる言語のシェーダーに現れる場合が含まれる。
定数式は、前述の不変変数と同様に操作しても、同じ結果に評価しなければならない。
4.9. The Precise Qualifier¶
アルゴリズムの中には、ほぼ同等の結果をより高いパフォーマンスで得られる最適化を対処している実装であっても、ソースコードで指定された演算順序に正確に従い、すべての演算を着実に処理することを浮動小数点計算に要求するものがある。例えば、多くの実装は、次のような浮動小数点式を計算する multiply-add 命令を対処している:
result = (a * b) + (c * d);
これは三度の演算の代わりに二度で、すなわち、二度の乗算と一度の加算の代わりに、一度の乗算と一度の multiply-add 演算を行う。
浮動小数点の multiply-add 結果は、最初に浮動小数点の結果が得られる乗算を行ってから浮動小数点の加算を行った場合と同じになるとは限らない。したがって、この例では、二つの乗算はつじつまが合うようには扱われず、二つの乗算は事実上、異なる精度であるように見える可能性がある。
一貫性を持たせる必要がある重要な計算は、細分化の際に現れる。下図(注:本書には巨大な図式が掲載されている)のように、細分化のための中間点が異なる方向に合成されるが、同じ結果を得る必要がある。
読者ノート
ここにあるイラストは、細分化を実装するようなプログラマーでないと理解できないものだと思う。
最適化されたコードが最適化されていないコードと比較してわずかに異なる結果をもたらすかもしれなくても、実装は修飾子なしで、式の評価に使用される演算の順序または数を効果的に変更する最適化を実行することが許可される。
修飾子 precise は変数の値に与する演算が、指定された順序で、演算子の一貫性を持って行われることを保証する。演算子の順序は、5.1. Operators で記述されているように、演算子の優先順位と括弧によって決定される。演算子の一貫性とは、例えば乗算演算子 * のような特定の演算子について、その演算が常に同じ精度で計算されることを意味する。具体的には、コンパイラーで生成されたコードで計算された値は、以下の恒等式に従わなければならない:
次を防ぐことができる一方:
a + (b + c)が(a + b) + cになることは許されない。a * (b * c)が(a * b) * cになることは許されない。a * b + cが単一の演算fma(a, b, c)になることは許されない 。
ここで a, b, c, d は行列ではなく、スカラーまたはベクトルとする(行列は一般には可換でない)。これらの規則に基づいて計算を表現するのはシェーダー作成者の責任であり、これらの規則に従うのはコンパイラーの責任だ。細分化段階が従うべき規則については gl_TessCoord の説明を参照。これらの規則は、上記と合わせて、細分化する際のひび割れを避けることができる。
例:
precise out vec4 position;
は position の値を生成するために使用される演算が、ソースコードで指定された順序で正確に実行されなければならず、すべての演算子が着実に扱われなければならないことを宣言している。修飾子 invariant と同様に、修飾子 precise は組み込み変数や事前に宣言されたユーザー定義変数が正確であることを修飾するために使用できる。
out vec3 Color;
precise Color; // make existing Color be precise
ブロック、構造体型、または構造体型の変数に precise を適用すると、変数のメンバーそれぞれに再帰的に precise を適用することになる。
この修飾子は、特定の関数内の右辺値の評価に影響を与えるが、それは、その結果が最終的に同じ関数内で precise と修飾された左辺値によって消費される場合に、かつその場合に限られる。関数内の他の式は影響を受けない。これには、precise 宣言されていないが、関数の外で precise 修飾された変数によって最終的に消費される戻り値や出力引数も含まれる。影響を受けない式には、選択文と反復文と条件演算子の条件式とにある式を制御するものも含まれる。
precise の使用例(略)。
あるシェーダー段階の出力が直後の段階の入力と一致するかどうかを判定する目的では、修飾子 precise は入力と出力の間で一致する必要はない。
すべての定数式は precise が存在するかどうかにかかわらず、それが存在するかのように評価される。ただし、コンパイル時の定数式が対応する非定数式と同じ値に評価されるという要件はない (4.3.3. Constant Expressions)。
4.10. Memory Qualifiers¶
シェーダー格納ブロックとその内側で宣言された変数と画像型(キーワードに
"image" を含む基本不透明型)として宣言された変数は、以下(略)の記憶修飾子の一つまたは複数を追加的に修飾することができる:
修飾子 |
意味 |
|---|---|
|
(面倒なので本文参照) |
|
|
|
|
|
|
|
修飾子 coherent を使って宣言された画像変数へのメモリーアクセスは、他のシェーダー呼び出しからの同じ場所へのアクセスとともに密着的に実行される。特に
coherent 宣言された変数を読み出す場合、戻り値には、他のシェーダー呼び出しで実行された以前に完了した書き込みの結果が反映される。また、coherent 宣言された変数を書き込む場合、書き込まれた値は、他のシェーダー呼び出しで実行された後の密着的読み込みに反映される。
OpenGL 仕様書の 7.12 “Shader Memory Access” にあるように、シェーダーメモリーの読み取りと書き込みは、ほとんど定義されていない順序で行われる。必要であれば、組み込み関数 memoryBarrier() を使って、単一のシェーダー呼び出しで実行されるメモリーアクセスの完了と相対的な順序を保証できる。
coherent と宣言されていない変数を使ってメモリーにアクセスする場合、シェーダーがアクセスするそのメモリーは、同じアドレスへの将来のアクセスのために、実装がキャッシュすることがある。メモリー格納は、書き込まれた値が同じメモリーにアクセスする他のシェーダー呼び出しから見えないような方法でキャッシュされることがある。実装は、メモリー読みによって持ってきた値をキャッシュし、内在するメモリーが最初のメモリー読み以降に変更された場合でも、同じメモリーにアクセスするシェーダー呼び出しに同じ値を返すことができる。coherent と宣言されていない変数は、シェーダー呼び出し間の通信には役立たないかもしれないが、非密着式のアクセスを使用することで、より高いパフォーマンスが得られる可能性がある。
修飾子 volatile を使用して宣言された画像変数へのメモリーアクセスは、シェーダー実行中の任意の時点で、実行中のシェーダー呼び出し以外のソースによって読み書きされる可能性があるかのように、内在するメモリーを扱わなければならない。
volatile 変数が読み取られた場合、その値は、読み取りを実行するシェーダー呼び出しが以前に同じメモリーからその値を持ってきていたとしても、内在するメモリーから再取得されなければならない。volatile 変数が書き込まれた場合、その値が後続の書き込みによって上書きされることをコンパイラーが明確に判断したとしても、その値は内在するメモリーに書き込まれなければならない。volatile 変数を読み書きする外部ソースは別のシェーダー呼び出しである可能性があるため、volatile 宣言された変数は自動的に coherent として扱われる。
修飾子 restrict を使って宣言された画像変数へのメモリーアクセスは、メモリーアクセスを行うために使用される変数が、当該シェーダー段階を使って内在するメモリーにアクセスする唯一の方法であると仮定してコンパイルすることができる。これにより、コンパイラーは他のコードが内在する画像を読み書きしないと仮定できるので、非
restrict 画像変数では許可されない方法で、restrict 画像変数を使用してロードや格納をまとめたり、並べ替えたりすることができます。アプリケーションは
restrict 変数が参照する画像メモリーが同じスコープの他の変数から参照されないようにする責任がある。
修飾子 readonly を使用して宣言された画像変数へのメモリーアクセスは内在するメモリに対する読み取りのみ可能で、読み取り専用のメモリーとして扱われ、書き込みはできない。readonly 画像変数を imageStore() やその他画像メモリーを変更する組み込み関数に渡すとコンパイルエラー。
修飾子 writeonly を使用して宣言された画像変数へのメモリーアクセスは内在するメモリーに対する書き込みのみ可能で、内在するメモリーを読み取ることはできない。
writeonly 画像変数を imageLoad() やその他画像メモリーを読み込む組み込み関数に渡すとコンパイルエラー。
変数を readonly と writeonly の両方で修飾して、読み込みと書き込みの両方を禁止することもできる。このような変数でも、imageSize() や .length() など、いくつかの問い合わせで使用することができる。
記憶修飾子 coherent, volatile, restrict, readonly, writeonly
は、バッファー変数(つまりシェーダー格納ブロックのメンバー)の宣言に使用できる。バッファー変数が記憶修飾子付きで宣言されている場合、上述の画像変数を含むメモリーアクセスに指定された動作は、そのバッファー変数を含むメモリーアクセスにも同じように適用される。readonly バッファー変数への代入や writeonly バッファー変数からの読み出しはコンパイルエラー。readonly writeonly の組み合わせは可能。
さらに、シェーダー格納ブロックのブロックレベルの宣言では、readonly writeonly
の組み合わせを含む記憶修飾子を使用することができる。ブロック宣言が記憶修飾子で修飾されている場合、そのブロックのすべてのメンバーが同じ記憶修飾子で宣言されているかのようになる。例えば、
coherent buffer Block {
readonly vec4 member1;
vec4 member2;
};
このブロック宣言は次のものに等価だ:
buffer Block {
coherent readonly vec4 member1;
coherent vec4 member2;
};
記憶修飾子は画像変数、バッファー変数、シェーダー格納ブロックの宣言でしか対処されていない。その他の宣言でこれらの修飾子を使用するとエラーとなる。
ユーザー定義関数を呼び出す際、coherent, volatile, readonly,
writeonly で修飾された変数を、そのような修飾語がない仮引数を持つ関数に渡すことはできない。仮引数に記憶修飾子を追加することは合法だが、修飾子 restrict を持たない仮引数が呼び出し引数から取り去ることができるのは restrict だけだ。
組み込み関数が呼び出されたとき、生成されるコードは実引数の実際の修飾子に基づくものであり、プロトタイプの仮引数に指定された記憶修飾子のリストに基づくのではない。
vec4 funcA(restrict image2D a) { ... }
vec4 funcB(image2D a) { ... }
layout(rgba32f) uniform image2D img1;
layout(rgba32f) coherent uniform image2D img2;
funcA(img1); // OK, adding "restrict" is allowed
funcB(img2); // illegal, stripping "coherent" is not
レイアウト修飾子は仮引数には使用できず、引数照合処理に含まれない。
画像変数の宣言で const を使用すると、参照する画像ではなく、宣言されている変数の const 性を修飾することに注意してください。修飾子 readonly は(その変数を介してアクセスされる)画像メモリーを修飾し、修飾子 const は変数自体を修飾する。
4.11. Specialization-Constant Qualifier¶
特殊化定数は SPIR-V でのみ使用され、レイアウト修飾子 constant_id を使用して宣言される。例えば:
layout(constant_id = 17) const int arraySize = 12;
これは既定値が 12 である特殊化定数を作る。17 という数字は、API や他のツールが後でこの独特な特殊化定数を参照するために、著者が選んだ例示的な ID だ。それが final
lowering 前に決して変更されなければ 12 の値を維持する。スカラー bool,
int, uint, float, double の SPIR-V 生成以外で修飾子
constant_id を使用するとコンパイルエラー。
組み込み定数を特殊化定数として宣言することができる。例えば:
layout(constant_id = 31) gl_MaxClipDistances; // add specialization_id
この宣言では、先に宣言された組み込み変数の名前だけを使用し、レイアウト修飾子
constant_id 宣言をしている。定数が使用された後にこれを行うとコンパイルエラーとなる。定数は厳密に非特殊化定数か特殊化定数のどちらか一方であり、両方ではない。
組み込み定数ベクトル gl_WorkGroupSize は、修飾子 local_size_{x,y,z}_id
を使って、成分に個別に ID を与えることで特殊化できる。例えば:
layout(local_size_x_id = 18, local_size_z_id = 19) in;
gl_WorkGroupSize.y は非特殊化定数として残され、gl_WorkGroupSize は部分的に特殊化されたベクトルとなる。その x, z 成分は、SPIR-V を生成した後にID
18 および 19 を使用して、後で特殊化することができる。これらの ID は作業グループサイズの宣言とは別に宣言される。
layout(local_size_x = 32, local_size_y = 32) in; // size is (32,32,1)
layout(local_size_x_id = 18) in; // constant_id for x
layout(local_size_z_id = 19) in; // constant_id for z
local_size_{xyz} の宣言に関する既存の規則は変化しない。
local_size_{xz}_id については、同じ ID に異なる ID 値を与えたり、使用後に ID
値を与えたりするとコンパイルエラーとなる。それ以外では、順序、配置、文の個数、および複製はエラーにならない。
特殊化定数でサイズ調整された二つの配列は、同じ記号でサイズ調整され、かつ演算を伴わない場合に限り、同じ型となる。例えば:
layout(constant_id = 51) const int aSize = 20;
const int pad = 2;
const int total = aSize + pad; // specialization constant
int a[total], b[total]; // a and b have the same type
int c[22]; // different type than a or b
int d[aSize + pad]; // different type than a, b, or c
int e[aSize + 2]; // different type than a, b, c, or d
読者ノート
なぜこのようになるのか説明できるか?
特殊化定数でサイズ調整された配列を含む型を比較すること、集約として代入すること、初期化を使って宣言すること、初期化として使用することができない。ただし、同じ型の仮引数を持つ関数の実引数として渡すことはできる。配列の配列として宣言された変数の最も外側の次元しか特殊化定数とはならず、そうでなければコンパイルエラーとなる。
ブロック内の配列のサイズを特殊化定数で指定することはできるものの、ブロックは静的レイアウトを有するようになる。特殊化サイズを変化させても、ブロックは再配置されない。明示的なオフセットがない場合は、そのレイアウトは配列の既定サイズに基づいたものになる。
4.12. Order and Repetition of Qualification¶
一つの宣言に修飾子が複数ある場合、それらの順序は何でもよいが、型の前にすべて置かなければならない。修飾子 layout は、一度を超えて現れることができる唯一の修飾子だ。さらに、一つの宣言は、格納修飾子、補助格納修飾子、補間修飾子を高々一つ持てる。inout が使用された場合、in も out も使用できまない。記憶修飾子を複数使用することができる。これらの規則に違反すると、コンパイルエラーとなる。
4.13. Empty Declarations¶
空宣言 (empty declarations) とは、変数名のない宣言のことであって、その宣言によってインスタンス化されるオブジェクトがないことを意味する。一般的に、空宣言は許されている。構造体を宣言するときに便利なものもあれば、何の効果もないものもある。例えば:
int; // No effect
struct S {int x;}; // Defines a struct S
コンパイルエラーやリンクエラーが発生する修飾子の組み合わせは、例えば、宣言が空であってもなくても同じだ。
invariant in float x; // Error. An input cannot be invariant.
invariant in float; // Error even though no variable is declared.