OpenGL のシェーダー記述言語である GLSL は GPU ドライバによって解釈されます。 GPU メーカーの種類だけ GLSL コンパイラが存在しておりそれぞれ実装が異なっています。 この点は DirectX の hlsl との大きな違いとなっています。
SDK や OS に含まれている Microsoft 製の単一の hlsl コンパイラを使います。
C# などの JIT による動作と同じで事前コンパイルながらターゲットハードウエアに依存しません。 ランタイムにコンパイラの dll が含まれているため、GLSL のようにソースからの動的なコンパイルも可能です。
中間のバイトコードが存在しません。 テキストコードのパーサーからオプティマイザまで GPU ドライバ側に含まれることになります。
コンパイラ時間短縮のため、OpenGL ES では Native のバイトコードを出力&生成できる場合があります。 ただし offline コンパイラは単独ではほとんど提供されておらず、ハードウエア毎にバイトコードの互換性もありません。
やはり一番大きいのは互換性の問題だと思われます。
Desktop の場合はほぼ 2強 ( + 1 ) しかなかったので 互換性が問題になることは稀でした。 2種類のビデオカードを用意して動作確認することは簡単だし、 NVIDIA/AMD の両対応は Direct3D でも同様に必要なことなので負担にはなっていませんでした。 ドライバの問題があった場合でも、直接 GPU メーカーの最新ドライバが使えるため、自分で更新すれば解決です。
ところが Mobile の世界では簡単には行きません。まず Desktop よりも GPU core の種類が多くなっています。代表的なものでも
など
同じ GPU core であったとしても SoC も違えば機能が本当に同一かどうかはわかりません。 PowerVR や Mali は様々な組み合わせで使われています。
スマートフォンやタブレットなど、それを採用するメーカーもまたばらばらです。 それぞれどのバージョンのドライバが使われているかわかりませんし、自由にドライバを入れ替えることはできません。 OS のバージョンも複数混在しており種類は膨大な数になります。
このように数多く存在している端末を全部集めてテストすることは非常に困難です。
OpenGL ES の GLSL では互換性を重視した書き方が求められることになります。
できるだけ多くのハードウエアで問題なく動作する事が重要なので、 たとえ GLSL の文法上正しかったとしても、動かないハードが一つでもあるならそれは避けなければなりません。
バグだったとしてもそれにパッチが当たる可能性は低く、修正されたとしても更新まで非常に時間がかかるからです。
できるだけ 多くのハードウエアでテストする必要があります。
端末を買うならデザインや使い勝手とか二の次で、 内部構成を見てできるだけバリエーション違いのハードを手に入れるようにします。
例えば 2010 年の日本メーカー製端末はほとんど全部が Qualcomm QSD8250/8650 で Adreno200 なので、 GLSL プログラマ的には複数手に入れてもあまり意味がありません。 その中で唯一 Galaxy S は PowerVR SGX540 なので、もし選ぶならこのように GPU の違いを意識しておきます。
各 GPU メーカーは PC 上で動作する GPU Emulator (OpenGL ES 2.0 Emulator) をツールとして提供しています。 できるだけ多くの GPU の GLSL をパスするためにはこれを活用しない手はありません。 実端末の入手は難しいですが、 これなら PC 上である程度の互換性テストができるようになります。
ただし問題もあります。 HOST PC の GPU の種類や OpenGL のドライバにかなり依存します。 NVIDIA / AMD でそれぞれ正しく動かない場合があるので注意が必要です。
GPU によって vecN( scalar ) がエラーになる。
例えば vec3 なら vec3( 0.0 ) ではなく vec3( 0.0, 0.0, 0.0 ) のように記述します。
PowerVR SGX540 の古いドライバで発生
chototsu さんの情報
Vertex Shader で「precision mediump float;」がエラーになる GPU が存在します。
PowerVR SGX540 の古いドライバで発生
chototsu さんの情報
Vertex Shader で precision による float のデフォルト宣言に highp 以外を宣言するとエラーになります。 デフォルトの宣言が問題なだけで、変数毎の個別の精度宣言は利用できます。
定数の自動型変換は行われません。 が、中にはこの書き方でもコンパイルできる GPU があるので要注意です。
float v= 0.0; float t= v + 1; // ← エラー
float の値数は必ず 1.0 のように小数点 '.' が必要です。
GLSL では 0.0f のような 'F' をつけた C言語表記は使えません。
Desktop GeForce では可能です。
PC の Tegra Emulator が GLSL の version 1.1 を返します。
GL_VERSION: OpenGL ES 2.0 GL_RENDERER: NV_OES_device emulated by GeForce GTX 460/PCI/SSE2 GL_VENDOR: NVIDIA Corporation GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL 1.10
OpenGL 4.1 の OpenGL ES 2.0 Profile では、 シェーダーバージョン 1.0 を宣言しても GLSL の定数 “GL_ES” が定義され無い場合があります。
シェーダーコードを OpenGL と共有している場合に #ifdef GL_ES 等を使っていると問題になります。
#version 100
#version はシェーダーコードの 1行目になければなりません。 GPU によっては 1行目以外でもエラーにならないものがあります。
プログラム側で glShaderSource() を使い、 include のために複数のソースを渡す場合があります。 この場合シェーダーソースの前に別のコードが挿入されることになり、 結局 #version 行を 1行目に書くことができません。 よって宣言できないか、もし必要ならプログラム側で挿入する必要があります。
Fragment Shader で discard 命令があると GPU が固まります。 OS ごと再起動するか無反応となります。
chototsu さんの情報
discard を使っていても落ちないケースもあります。
その後調査した結果
よって、テクスチャが 1枚だけの単純なシェーダーで、Alpha Test に discard を使っている場合は動きます。
依存がない命令はコンパイラによって最配置されることがあるため確実ではないようです。
確実なのは discard を使わないことです。 多くの GPU では discard 命令を使うと速度が落ちる可能性があります。
例えば PowerVR では TBDR との相性が極めて悪いので、できるだけ discard を使わないで済む方が望ましいと言えます。 特に複数のテクスチャを用いた複雑なシェーダーほど影響が出るため、可能ならテクスチャではなく形状で対応した方が良いでしょう。 Adreno も discard はあまり推奨されていません。
glGetActiveUniform で配列の場合に返す名前が GPU によって異なります。 具体的には、変数名のあとに ”[0]” が付く場合とつかない場合があります。
Vivante GC860 では GLSL で宣言した配列 Uniform への設定 (glUniform4fv 等) で、 Location に offset を指定した部分書き込みができません。
// GLSL ES uniform vec4 Table[32];
// OpenGL float param[4*4]; glUniform4fv( TableLocation + offset, 4, param ); // offset が 0 以外だとエラー
GC860 以外 (Adreno, PowerVR, Tegra, Mali, Desktop GeForce/RADEON) では動作しますが非推奨な方法だと思われます。
EGL で列挙された情報に従いフレームバッファとして 8888 を選ぶと機種によっては OS ごと落ちます。
対処方法としては、Adreno 200 や Z430 の場合はかならずフレームバッファに 565 を選択します。
他の GPU では 8888 を優先し、Adreno 200/Z430 だけ 565 を選択したい場合は GPU 名を判定して処理を分けなければなりません。 GL_RENDERER が返す値として、下記の 3種類存在していることが判明しています。他にもあるかもしれません。
"Adreno" "Adreno 200" "AMD Z430"
GPU によって演算精度が異なります。
lowp/mediump/highp はコンパイラに対する演算精度のヒントを与えるだけで、必ずしも宣言した通りの精度が用いられるわけではありません。
例えば Adreno 系、Mali-400系、Vivante は精度宣言はすべて無視されます。 Adreno は Vertex も Fragment もすべて highp で演算します。
特に Fragment Shader で lowp を指定した場合に GPU によって演算精度の違いがかなり大きいようです。
詳しくは下記の表を参照してください。
Tegra2 / Tegar3 では VertexShader の glGetShaderPrecisionFormat() の結果が highp, mediump, lowp すべて 32bit FLOAT 精度を返します。 ところが実際にシェーダーを走らせると mediump, lowp 宣言で結果が大きく変わり、演算精度が落ちています。
また同じように Mali-400 も VertexShader ではすべて 32bit FLOAT の精度を返しますが、mediump 宣言するとわずかに演算結果の精度が落ちます。 lowp では変わりません。
詳しくは下記の表を参照してください。
Fragment Shader で lowp を使用した場合、GPU によって演算精度が大きく異なります。
カラー演算の最適化のために lowp を多用した場合、これらの精度の違いが問題になることがあります。
Fragment Shader が複雑になると、Tegra2 だけシェーダーが走らなくなることがあります。 シェーダーを読み込んだあとにクラッシュします。
命令を削ると動作するため、シェーダーの最大長制限に引っかかっているものと思われます。
Tegra2 以外は動作しています。
Tegra3 は Fragment Shader Unit が増えて速くなっています。 比較的長い Shader も実用的案速度で走るようになったため、命令容量も増えている可能性があります。
Android OS 2.2 の Tegra2 では動作することが判明しました。 ハードウエアではなく、ドライバなど他に原因が有りそうです。