GLSL

OpenGL のシェーダー記述言語である GLSL は GPU ドライバによって解釈されます。 GPU メーカーの種類だけ GLSL コンパイラが存在しておりそれぞれ実装が異なっています。 この点は DirectX の hlsl との大きな違いとなっています。

HLSL との違い

DirectX HLSL

SDKOS に含まれている Microsoft 製の単一の hlsl コンパイラを使います。

  1. HLSL コンパイラは共通の中間バイトコードにコンパイルする
  2. 各メーカーの GPU ドライバが中間コードをネイティブコードに変換して実行する

C# などの JIT による動作と同じで事前コンパイルながらターゲットハードウエアに依存しません。 ランタイムにコンパイラの dll が含まれているため、GLSL のようにソースからの動的なコンパイルも可能です。

利点

  • offline コンパイラが時間をかけて最適化できる。
  • offline コンパイラなのでエラー出力がしっかりしており原因がわかりやすい。
  • 事前にコンパイルと共通の最適化が施されておりランタイム時の変換時間を短縮できる。
  • 単一のコンパイラなので構文解析や動作が明らかで、実行時の構文の差など基礎的な部分で詰まることがない。
  • ドライバが独自に機能追加することが出来ず互換性が保たれます。

欠点

  • バージョンが変わり、機能を拡張した場合はバイトコードも合わせて拡張する必要があります。
    • ドライバが独自に機能を追加することができません。
  • ハードウエアの性能が高くても中間バイトコードの制限を受けることがあります。
    • 例えばハードウエア的にはより多くのメモリにアクセスできる場合でも、バイトコードのレジスタフィールド幅が決められていれば、シェーダープログラムはそれ以上のメモリにアクセスすることができません。

OpenGL GLSL

中間のバイトコードが存在しません。 テキストコードのパーサーからオプティマイザまで GPU ドライバ側に含まれることになります。

  1. アプリケーションはテキストソースコードのままシェーダーを所有
  2. 動的に GPU ドライバがコンパイルを行い、直接ネイティブコードを生成する

コンパイラ時間短縮のため、OpenGL ES では Native のバイトコードを出力&生成できる場合があります。 ただし offline コンパイラは単独ではほとんど提供されておらず、ハードウエア毎にバイトコードの互換性もありません。

利点

  • ソースコードなので機能拡張の制限がありません。extension の追加が容易です。
  • 中間バイトコードの制限を受けません。
  • 動的にコンパイルするため、コンパイラ(ドライバ)の改良の恩恵を受けやすくなります。
  • GLSL のための特殊なツールが不要です。ドライバのほか既存の OpenGL lib とヘッダファイルだけで開発できます。

欠点

  • GPU メーカー毎にコンパイラの実装が違うので、構文やエラー判定など細かな違いではまることがあります。
  • シェーダープログラムが複雑になるほど実行時にコンパイル時間が必要となります。起動が遅くなります。
  • コンパイル時間や動作メモリの制限があるため極端に強い最適化ができません。
  • ただの syntax error であっても実行しないとわからないことと、GLES ではドライバによってエラーメッセージが返らないことがあります。
  • HLSL や Cg, OpenCL 等が比較的 C言語に近いのに対して独自の構文となっています。

GLSL の問題

やはり一番大きいのは互換性の問題だと思われます。

Desktop の場合はほぼ 2強 ( + 1 ) しかなかったので 互換性が問題になることは稀でした。 2種類のビデオカードを用意して動作確認することは簡単だし、 NVIDIA/AMD の両対応は Direct3D でも同様に必要なことなので負担にはなっていませんでした。 ドライバの問題があった場合でも、直接 GPU メーカーの最新ドライバが使えるため、自分で更新すれば解決です。

ところが Mobile の世界では簡単には行きません。まず Desktop よりも GPU core の種類が多くなっています。代表的なものでも

  • Qualcomm Adreno シリーズ
  • Imagination PowerVR シリーズ
  • NVIDIA Tegra (ULP GeForce) シリーズ
  • ARM Mali シリーズ

など

同じ GPU core であったとしても SoC も違えば機能が本当に同一かどうかはわかりません。 PowerVR や Mali は様々な組み合わせで使われています。

スマートフォンやタブレットなど、それを採用するメーカーもまたばらばらです。 それぞれどのバージョンのドライバが使われているかわかりませんし、自由にドライバを入れ替えることはできません。 OS のバージョンも複数混在しており種類は膨大な数になります。

このように数多く存在している端末を全部集めてテストすることは非常に困難です。

注意すべきこと

OpenGL ES の GLSL では互換性を重視した書き方が求められることになります。

できるだけ多くのハードウエアで問題なく動作する事が重要なので、 たとえ GLSL の文法上正しかったとしても、動かないハードが一つでもあるならそれは避けなければなりません。

バグだったとしてもそれにパッチが当たる可能性は低く、修正されたとしても更新まで非常に時間がかかるからです。

GLSL の互換性を保つための工夫

GPU で端末を選ぶ

できるだけ 多くのハードウエアでテストする必要があります。

端末を買うならデザインや使い勝手とか二の次で、 内部構成を見てできるだけバリエーション違いのハードを手に入れるようにします。

例えば 2010 年の日本メーカー製端末はほとんど全部が Qualcomm QSD8250/8650 で Adreno200 なので、 GLSL プログラマ的には複数手に入れてもあまり意味がありません。 その中で唯一 Galaxy S は PowerVR SGX540 なので、もし選ぶならこのように GPU の違いを意識しておきます。

GPU Emulator を活用する

各 GPU メーカーは PC 上で動作する GPU Emulator (OpenGL ES 2.0 Emulator) をツールとして提供しています。 できるだけ多くの GPU の GLSL をパスするためにはこれを活用しない手はありません。 実端末の入手は難しいですが、 これなら PC 上である程度の互換性テストができるようになります。

ただし問題もあります。 HOST PC の GPU の種類や OpenGL のドライバにかなり依存します。 NVIDIA / AMD でそれぞれ正しく動かない場合があるので注意が必要です。

GLSL ES の問題情報共有用

OpenGL ES 2.0 GLSL 構文

コンストラクタ

GPU によって vecN( scalar ) がエラーになる。

例えば vec3 なら vec3( 0.0 ) ではなく vec3( 0.0, 0.0, 0.0 ) のように記述します。

PowerVR SGX540 の古いドライバで発生

chototsu さんの情報

Vertex Shader の precision

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 では可能です。

GLSL ES 1.1 を返す

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

GL_ES が宣言されない

OpenGL 4.1 の OpenGL ES 2.0 Profile では、 シェーダーバージョン 1.0 を宣言しても GLSL の定数 “GL_ES” が定義され無い場合があります。

シェーダーコードを OpenGL と共有している場合に #ifdef GL_ES 等を使っていると問題になります。

version GLES 2.0

#version 100

#version はシェーダーコードの 1行目になければなりません。 GPU によっては 1行目以外でもエラーにならないものがあります。

プログラム側で glShaderSource() を使い、 include のために複数のソースを渡す場合があります。 この場合シェーダーソースの前に別のコードが挿入されることになり、 結局 #version 行を 1行目に書くことができません。 よって宣言できないか、もし必要ならプログラム側で挿入する必要があります。

Adreno 205 で discard が使えない

Fragment Shader で discard 命令があると GPU が固まります。 OS ごと再起動するか無反応となります。

chototsu さんの情報

discard を使っていても落ちないケースもあります。

その後調査した結果

  • Texture を使わないシェーダーでは問題ない
  • Texture を使うシェーダーでは discard の位置によって症状が変わる
    • 無条件 discard はどこに入れても固まる
    • texture 依存 discard は shader の一番最後なら固まらない。(すべての texture フェッチが終わったあと)
    • varying 依存 discard は texture fetch の前に入れても固まらない。(単にコンパイル時の最適化で texture 命令のあとに配置されたためだと考えられる)

よって、テクスチャが 1枚だけの単純なシェーダーで、Alpha Test に discard を使っている場合は動きます。

依存がない命令はコンパイラによって最配置されることがあるため確実ではないようです。

確実なのは discard を使わないことです。 多くの GPU では discard 命令を使うと速度が落ちる可能性があります。

例えば PowerVR では TBDR との相性が極めて悪いので、できるだけ discard を使わないで済む方が望ましいと言えます。 特に複数のテクスチャを用いた複雑なシェーダーほど影響が出るため、可能ならテクスチャではなく形状で対応した方が良いでしょう。 Adreno も discard はあまり推奨されていません。

OpenGL ES 2.0 API

glGetActiveUniform の違い

glGetActiveUniform で配列の場合に返す名前が GPU によって異なります。 具体的には、変数名のあとに ”[0]” が付く場合とつかない場合があります。

  • [0]あり
    • PowerVR SGX
    • Adreno 205
    • Mali-400MP
    • Desktop Tegra Emulator
    • Desktop GeForce GTX460 OpenGL4.2
    • Desktop Angle Project (D3D9)
  • [0]なし
    • Tegra2
    • Vivante GC860
    • Desktop RADEON HD 6750 OpenGL 4.1

Uniform 配列の Location offset が使えない

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) では動作しますが非推奨な方法だと思われます。

Adreno 200 / Z430 でフレームバッファを 8888 にすると落ちる

EGL で列挙された情報に従いフレームバッファとして 8888 を選ぶと機種によっては OS ごと落ちます。

対処方法としては、Adreno 200 や Z430 の場合はかならずフレームバッファに 565 を選択します。

他の GPU では 8888 を優先し、Adreno 200/Z430 だけ 565 を選択したい場合は GPU 名を判定して処理を分けなければなりません。 GL_RENDERER が返す値として、下記の 3種類存在していることが判明しています。他にもあるかもしれません。

"Adreno"
"Adreno 200"
"AMD Z430"

OpenGL ES 2.0 GLSL 実行結果

演算精度が異なる (1)

GPU によって演算精度が異なります。

lowp/mediump/highp はコンパイラに対する演算精度のヒントを与えるだけで、必ずしも宣言した通りの精度が用いられるわけではありません。

例えば Adreno 系、Mali-400系、Vivante は精度宣言はすべて無視されます。 Adreno は Vertex も Fragment もすべて highp で演算します。

  • Mali-400 の Fragment は highp がなく、どの宣言をしていても常に mediump になります。
  • Vertex で precision 指定が有効なのは本来 PowerVR だけです。それ以外の GPU はすべて highp だけで演算しているはずですが、Tegra は仕様と異なる動きをします。
  • ULP GeForce (Tegra)、Mali-400 は Fragment で highp 演算ができません。

特に Fragment Shader で lowp を指定した場合に GPU によって演算精度の違いがかなり大きいようです。

  • PowerVR/Tegra は 8bit、Mali-400 は 16bit、Adreno/Vivante は 32bit

詳しくは下記の表を参照してください。

GPU 毎の演算精度まとめ

演算精度が異なる (2)

Tegra2 / Tegar3 では VertexShader の glGetShaderPrecisionFormat() の結果が highp, mediump, lowp すべて 32bit FLOAT 精度を返します。 ところが実際にシェーダーを走らせると mediump, lowp 宣言で結果が大きく変わり、演算精度が落ちています。

また同じように Mali-400 も VertexShader ではすべて 32bit FLOAT の精度を返しますが、mediump 宣言するとわずかに演算結果の精度が落ちます。 lowp では変わりません。

詳しくは下記の表を参照してください。

GPU 毎の演算精度まとめ

演算精度が異なる (3)

Fragment Shader で lowp を使用した場合、GPU によって演算精度が大きく異なります。

  • lowp の精度
    • Adreno / GC800 : 32bit float
    • Mali-400 : 16bit float
    • Tegra / PowerVR SGX : 8~9bit fixed

カラー演算の最適化のために lowp を多用した場合、これらの精度の違いが問題になることがあります。

  • 16~32bit float 系 : ほぼ記述通りの結果が得られる。
  • 8~bit fixed : 単なる乗算であっても演算の順番によって結果が異なる。a*b*c と c*b*a が異なる。毎回 clamp 相当の処理が入るため。

Tegra2 だけ Fragment Shader が動かない

Fragment Shader が複雑になると、Tegra2 だけシェーダーが走らなくなることがあります。 シェーダーを読み込んだあとにクラッシュします。

命令を削ると動作するため、シェーダーの最大長制限に引っかかっているものと思われます。

Tegra2 以外は動作しています。

  • Mali-400MP
  • Adreno 200/205
  • PowerVR SGX535/540/543MP
  • GC860
  • Tegra3

Tegra3 は Fragment Shader Unit が増えて速くなっています。 比較的長い Shader も実用的案速度で走るようになったため、命令容量も増えている可能性があります。

Android OS 2.2 の Tegra2 では動作することが判明しました。 ハードウエアではなく、ドライバなど他に原因が有りそうです。

opengl/glsl.txt · 最終更新: 2012/02/26 02:25 by oga
 
特に明示されていない限り、本Wikiの内容は次のライセンスに従います: CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki