MiniShade: 仮想マシンの設計
必要な命令レビューは完了したと思うので、シェーダで実装するバイトコードVMの設計に入る。
活用する制約
WebGL1ではESSL1のみのサポートで良いということなので、いくつかその制約を実装に活用する。
ちなみに、 以下の制約の大部分はWebGL2で撤廃 されており、同じ作戦はWebGL2のサポート時には使えなくなる。
WebGL1のシェーダは浮動小数点専用で良い
シェーダ言語であるESSL1はC言語によく似ているが、C言語にはない制約がいくつか導入されており、 整数演算を実装しなくて良い のはその1つと言える。
4.1.3 Integers
Integers are mainly supported as a programming aid. At the hardware level, real integers would aid efficient implementation of loops and array indices, and referencing texture units. However, there is no requirement that integers in the language map to an integer type in hardware. It is not expected that underlying hardware has full support for a wide range of integer operations. An OpenGL ES Shading Language implementation may convert integers to floats to operate on them. Hence, there is no portable wrapping behavior.
このため、VMに実装するレジスタは浮動小数点レジスタのみでよいことになる。
ループはコンパイル時に展開できる定数回のループのみ
FIXME
uniform
変数のみ
配列のインデックスに変数を使えるのは ESSL1の仕様では:
Appendix A: Limitations for ES 2.0
5 Indexing of Arrays, Vectors and Matrices
Uniforms (excluding samplers)
In the vertex shader, support for all forms of array indexing is mandated. In the fragment shader, support for indexing is only mandated for constant-index-expressions.
と規定されていて、頂点シェーダでは uniform
変数へのアクセスに任意の方法が使えなければならないとされている。理由は:
10.25 Dynamic Indexing
RESOLUTION: Keep dynamic indexing of uniforms (for skinning). Remove for temps (in the limitations
section).
というように(頂点)skinningのためとされている。頂点アトリビュートとして参照する骨の番号を指定するためにdynamic indexingが必要になってしまう。
attribute vec4 a_position;
attribute vec4 a_weights; // 4 weights per vertex
attribute vec4 a_boneNdx; // 4 bone indices per vertex
uniform mat4 bones[MAX_BONES]; // 1 matrix per bone
gl_Position = projection * view *
// ★ ↓ a_boneNdx[...] の値はコンパイル時にはわからない = dynamic
(bones[int(a_boneNdx[0])] * a_position * a_weight[0] +
bones[int(a_boneNdx[1])] * a_position * a_weight[1] +
bones[int(a_boneNdx[2])] * a_position * a_weight[2] +
boneS[int(a_boneNdx[3])] * a_position * a_weight[3]);
ESSL仕様のこの直下には
Should dynamic indexing of vectors and matrices be removed from the specification?
RESOLUTION: Keep in main specification. Support is not mandated
と有るものの、これはWebGL1で制約されている。
In the WebGL API, only the forms of indexing mandated in Appendix A are supported.
WebGL1では、通常mandateされていないオペレーションが含まれたシェーダのコンパイルは失敗するため、optionalな機能については気にする必要はない。
活用 "しない" 制約
使用できる一時変数の数など実行できるシェーダの規模の最低ラインはコンプライアンステストで規定するとされている。が、それを守れるギリギリの仕様にしても現実的なアプリケーションは実行できないと考えられるので、これは制約として活用しないことにした。
VMレジスタの設計
たぶん最も困難なのはVMレジスタの設計だろう。GPUシェーダで使える変数のサイズは非常に限定的になっている。
シェーダは基本的にメモリにはアクセスせず、レジスタのみで処理を完結する必要がある。テクスチャや一般的なバッファは例外だが、頂点アトリビュートは専用のハードウェアが事前にフェッチしてシェーダのレジスタなりなんなりに展開しておいてくれるためコストが掛からない。
... 先にレジスタアロケータだけでも実装して作戦を練らないとダメかな。。
定数レジスタ / 出力レジスタ
ESSLの attribute
なり varying
なりで指定されたデータは定数レジスタや出力レジスタに格納される。これはWebGL実装として確保したい数を考えて適当に設定できる。
C-WebGLでは uniform
の実装にUBOを使用しているため、 uniform
へのアクセスは原則テクスチャ等と同様外部アクセスとなる。まぁキャッシュあるし大丈夫なんじゃないかな。。
どちらのレジスタも1つあたりは vec4
で良い。
データレジスタ
VMのデータレジスタは mat4
(4x4のfloat) を1単位として128個くらい確保したい。 ...近代的なGPUでもこの数は用意できない。例えば、AMDのGCNではVGPRはwave(スレッド)あたり32個程度であり、 mat4
にすると8個ぶんしか用意できない。
Registers: To saturate the GPU, each CU must be assigned two groups of 1024 threads. Given 65,536 available VGPRs for the entire CU, each thread may require, at maximum, 32 VGPRs at any one time.
GCNの場合、LDSと呼ばれる追加のバッファがあり、そちらにスピルすればレジスタ数自体は倍増できる。また、そもそもシェーダで宣言された変数がこれらのレジスタに収まらない場合はスレッドの起動数自体が削減されるため、原理的にはパフォーマンスを犠牲することでレジスタを増やすこともできる。
当然 4x4 の配列を分解して float
なり vec4
なりとして活用する機能も必要になる。
プレディケートレジスタ
↑ のドキュメントにもあるように、GPUのシェーダコンパイラは bool
値について専用の最適化を持っていることがあり、ゼロとの比較を常にやるよりは専用の bool
値レジスタを持った方が効率的に処理できることが多いと考えられる。
数は。。まぁ8個くらいで十分なんじゃないですかね。。