ユーザ用ツール

サイト用ツール


opengl:glsl

目次

GLSL

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

HLSL との違い

DirectX HLSL

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

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

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

利点

互換性が非常に高い

  • offline コンパイラが時間をかけて最適化できます。
  • offline コンパイラなのでエラー出力がしっかりしており、原因がわかりやすくなっています。
  • コンパイル時に共通の最適化が施されており、ランタイム時の変換時間を短縮できます。アプリケーションの起動が速くなります。
  • GPU に依存せず常に単一のコンパイラなので、構文解析や動作が明確です。構文の差など基礎的な部分で詰まることがありません。
  • GPU ドライバが独自の機能を追加することが出来ないため、ソースコード互換性が保たれます。

欠点

GPUの個性を活かせない

  • GPU に新しい機能が追加されてもすぐに利用できません。メジャーアップデートを待つ必要があります。
    • アップデートは年単位で時間がかかり、また共通仕様として採用されなければ使うことができません。
  • ハードウエアの性能が高くても中間バイトコードの制限を受けることがあります。
    • 例えばハードウエア的にはより多くのメモリにアクセスできる場合でも、バイトコードのレジスタフィールド幅が決められていればシェーダープログラムはそれ以上のメモリにアクセスすることができません。
  • コンパイル済みのシェーダーバイトコードを使用するので、コンパイラの性能が上がっても既存のアプリケーションでは恩恵が受けられないことがあります。

OpenGL GLSL

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

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

コンパイラ時間短縮のため、OpenGL ES では Native のバイトコードを出力&生成できる拡張機能を持っています。 ただし実装はドライバ依存であり、ハードウエア毎にバイトコードの互換性がありません。

利点

  • ソースコードなので機能拡張の制限がありません。extension の追加が容易です。
  • 中間バイトコードの制限を受けません。
  • 動的にコンパイルするため、コンパイラ(ドライバ)の改良の恩恵を受けやすくなります。
    • 例えばアプリケーションに一切手を加えなくても、ドライバに含まれるコンパイラが改良されると、シェーダーがさらに最適化される可能性があります。
  • GLSL のための特殊なツールが不要です。ドライバのほか既存の OpenGL lib とヘッダファイルだけで開発できます。
  • シェーダーはソースコードのまま組み込むことが前提なので、アプリケーションが動的にソースコードを生成することができます。

欠点

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

GLSL の問題

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

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

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

  • GLES 2.0
    • Qualcomm Adreno 200 シリーズ
    • Imagination PowerVR SGX シリーズ
    • NVIDIA Tegra2/3/4 (ULP GeForce) シリーズ
    • ARM Mali-200/400 シリーズ
    • Vivante GC シリーズ
  • GLES 3.0
    • Qualcomm Adreno 300 シリーズ
    • Imagination PowerVR G6000 シリーズ
    • ARM Mali-T600 シリーズ

など

同じ GPU core であったとしても SoC が違えば異なる構成になっている可能性があります。 PowerVR や Mali、Vivante は様々なメーカーに採用されており、組み合わせの種類も膨大です。

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

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

注意すべきこと

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

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

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

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

GPU で端末を選ぶ

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

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

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

GPU Emulator を活用する

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

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

また Mobile と同じシェーダーを走らせられるよう、PC (Windows/Mac OSX/Linux 等) 上にも実行環境を作っておく必要があります。

GLSL ES の問題情報共有用

OpenGL ES 3.0

Android 4.3 + Adreno 320 (2013/10/09 追加)

Android 4.3 + Adreno 320 の組み合わせは OpenGL ES 3.0 が利用可能ですが、 OpenGL ES 3.0 を使うとシェーダーで UniformBlock を使うと落ちる。

UniformBlock を使用していない場合は OpenGL ES 3.0 でも問題なく動きます。 もともと OpenGL ES 2.0 では UniformBlock が無いので問題ありません。

(追記) Android 4.4 では修正されており、正しく動作するようになりました。 ただし世の中には Android 4.3 のまま更新されていないデバイスも多いので、 なんらかの対策は必要です。

OpenGL ES 2.0 GLSL 構文

Tegra 2 の古い GLSL コンパイラ (2013/10/09 追加)

lowp, mediump, highp 等の記述位置によってエラーになります。 Android 3.0 など初期の Tegra2 ドライバだけです。 正しい構文は float , vec4 等の型名の直前なので、Tegra ドライバのバグと考えられます。 Android 4.0 など、新しいバージョンではでていません。 OpenGL ES 2.0 では in/out は関数の引数で用いられる可能性があります。

Android 3.0 Tegra2

GL_VERSION: OpenGL ES 2.0
GL_RENDERER: NVIDIA Tegra
GL_VENDOR: NVIDIA Corporation
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL 1.00
error C7538: OpenGL does not allow 'varying' after 'lowp'
error C7538: OpenGL does not allow 'varying' after 'mediump'
error C7538: OpenGL does not allow 'lowp' after 'inout'
error C7538: OpenGL does not allow 'mediump' after 'inout'

コンストラクタ

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 以外を宣言するとエラーになります。 デフォルトの宣言が問題なだけで、変数毎の個別の精度宣言は利用できます。

定数の自動型変換

OpenGL ES 2.0 / OpenGL ES 3.0 の仕様では定数の自動型変換は行われません。 エラーが出る方が正解です。

ですが、Desktop 版 GLSL では型変換が行われますし、同様に Mobile 版でもこの書き方でコンパイルできる GPU があるので注意が必要です。

float v= 0.0;
float t= v + 1; // ← エラーが正しい

float の値数は必ず 1.0 のように小数点 '.' が必要です。

エラーが発生するのが正しい挙動ですが、一部 GPU ではエラーにならないので注意。 A で動いて B で動かないといった非互換性が生じます。

定数の型指定

OpenGL ES 2.0 の GLSL では 0.0f のような 'f' をつけた C言語表記は使えません。 エラーが出る方が正解です。

自動型変換と同じで、一部の OpenGL ES 2.0 Emulator では 'f' をつけてもエラーにならないので注意が必要です。

float a= 1.0f;  // ← エラーが正解

Tegra Emulator が 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
  • OpenGL ES 2.0 の GLSL ES は version 1.0
  • OpenGL ES 3.0 の GLSL ES は version 3.0

GLSL ES version 1.1 は本来存在していません。

GL_ES が宣言されない

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

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

対策方法

GLES 2.0 モードで起動しているとわかっているなら、コンパイル時に下記宣言をソースコードに挿入します。

#ifndef GL_ES
# define GL_ES
#endif

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 はあまり推奨されていません。

Adreno 220 で分岐ジャンプが多いとエラーになる (2013/10/14 追加)

Fragment Shader でシェーダー内の分岐ジャンプが多数存在する場合に、 コンパイルは通るもののリンクエラーになります。

// 例(1)
vec4 func( vec4 color, vec4 color1, vec4 color2, vec4 color3 )
{
 
    if( color.x >= 0.5 && color.y >= 0.5 && color.z >= 0.5 ){
        return  vec4( 1.0, 1.0, 1.0, 1.0 );
    }
    if( color1.x >= 0.5 && color1.y >= 0.5 && color1.z >= 0.5 ){
        return  vec4( 1.0, 1.0, 1.0, 1.0 );
    }
    if( color2.x >= 0.5 && color2.y >= 0.5 && color2.z >= 0.5 ){
        return  vec4( 1.0, 1.0, 1.0, 1.0 );
    }
    if( color3.x >= 0.5 && color3.y >= 0.5 && color3.z >= 0.5 ){
        return  vec4( 1.0, 1.0, 1.0, 1.0 );
    }
 
    return  vec4( 0.0, 0.0, 0.0, 1.0 );
}

例(1) のように途中で return 文で抜けるとグローバルな分岐が発生します。 このようなケースでリンクできないことがあります。

下記の 例(2) のように分岐ジャンプを使わずに、できるだけ出口を統一して 条件付き代入命令の連続に展開すると Adreno 220 でも正常に動くようになります。

// 例(2)
vec4 func( vec4 color, vec4 color1, vec4 color2, vec4 color3 )
{
    vec4  result= vec4( 0.0, 0.0, 0.0, 1.0 );
 
    if( color.x >= 0.5 && color.y >= 0.5 && color.z >= 0.5 ){
        result= vec4( 1.0, 1.0, 1.0, 1.0 );
    }
    if( color1.x >= 0.5 && color1.y >= 0.5 && color1.z >= 0.5 ){
        result= vec4( 1.0, 1.0, 1.0, 1.0 );
    }
    if( color2.x >= 0.5 && color2.y >= 0.5 && color2.z >= 0.5 ){
        result= vec4( 1.0, 1.0, 1.0, 1.0 );
    }
    if( color3.x >= 0.5 && color3.y >= 0.5 && color3.z >= 0.5 ){
        result= vec4( 1.0, 1.0, 1.0, 1.0 );
    }
 
    return  result;
}

コンパイルは正常に行われており、リンク時にシェーダーのエラーとなります。 そのため、 分岐が大量に存在する場合に、インストラクションエリア、またはレジスタ等何らかのハードウエアリソースを使い尽くしてしまった可能性があります。

Adreno 320 + OpenGL ES 2.0 では正しく動くので、 アーキテクチャが違うせいかまたは、拡張されているものと考えられます。

Adreno 220 以外の Tegra3/PowerVR SGX540/Adreno 320/Mali-T604 では 例(1) のまま正しく動作しています。

また PowerVR SGX540 のみ、例(2) に変更時に演算精度によって結果が異なってしまう症状が発生しています。例(1) の方が正しく動作しています。

Tegra2/3/4 では Fragment Shader の Uniform 配列が使えない (2013/10/14 追加,2014/03/01 Tegra4 追加)

Tegra2/3/4 では、Fragment Shader の Uniform 配列に動的な index を与えることが出来ません。 コンパイル時に求まる定数のみとなります。 Vertex Shader ではこのような制限はありません。

// Fragment Shader
varying vec4  index;
uniform vec4  param[100];
 
void main()
{
    float  a= param[int(index.x)];  // Tegra2/3/4 では error、それ以外は正しく動くgl_FragColor= color;
}

ただし、OpenGL ES 2.0 の仕様上は動的な index を用いることが出来なくても不正とはいえません。 よって Tegra2/3/4 に何らかのバグが存在しているわけではないので注意してください。

下記の歴史的理由によるものです。

もともと Direct3D 9 の仕様上、PixelShader の Constant Register (Uniform) に動的な配列を用いることが出来ませんでした。 同時に、当時の GeForce (6800/7800等) は、PixelShader の長さ制限が緩く、かつ PixelShader で利用可能な Constant Register 数も制限がありませんでした。

これは当時の GeForce のアーキテクチャには、Constant Regsiter 用のハードウエア的なバッファが用意されておらず、 Shader Program 内の命令フィールドの定数部分を直接書き換える仕様だったことが原因です。

そのため Constant Register は仮想的なものでしか無く個数制限はありません。 反面、index を用いた配列アクセスができなくなります。

OpenGL 2.0 は Direct3D 9 と同世代であり、かつ Tegra2/3/4 自体も GeForce 6800/7800 世代のハードエアと同じ仕組を採用しているといわれています。 実際に Tegra2/3/4 は Fragment Shader で利用可能な Uniform 数に唯一 1024 と巨大な値を返しており、他の GPU にはない特徴となっています。

なお、Tegra2/3/4 以外の GPU では、今のところこのような制限に遭遇していません。 Adreno 220/320, PowerVR SGX540, Mali-T604 ではいずれも正しく動作しており、 Fragment Shader でも Uniform に対する配列アクセスが可能です。

というのも、Tegra2/3/4 以外は Unified Shader を採用し値ているものがほとんどです。 この場合 Vertex, Fragment の機能差は無いに等しく、Vertex でできていることを Fragment で制限する理由がないからです。

なお Tegra2/3/4 の場合、配列の代わりにテクスチャを使うことが出来ます。

Tegra2/3/4 では Fragment Shader で動的なループがコンパイルエラーになる (2014/03/01 追加)

Tegra2/3/4 では Fragment Shader でループ回数を動的に求めるとコンパイルが通りません。

// ERROR
int loop= int( uniform_value * varying_value );
for( int i= 0 ; i< loop ; i++ ){
    ...
}

Fragment Shader の Uniform 配列と同じで、OpenGL ES 2.0 (ShaderModel3.0) の仕様上必須ではないことが原因です。

Unified Shader 系 GPU では問題なく動きます。 同じ Discrete Shader の Mali-200/400 でも問題ないようです。

ただし Tegra2/3/4 でも動的な分岐はできるので、下記のようにループ回数に上限があれば代用することができます。

// OK
for( int i= 0 ; i< 100 ; i++ ){
    if( i >= loop ){
        break;
    }
    .... 動的なループ相当
}

まとめると

  • Tegra2/3/4 の Fragment Shader は動的なループに対応していない。シェーダー実行時にループ回数が変動する場合はコンパイルできない。
  • ループ回数が静的に求まる場合はコンパイルできる。
  • 動的分岐はできるので、静的ループと組み合わせて擬似的に動的ループ相当を再現できる。

開始と終端を指定する例

// (1) コンパイル時にループ回数が決まらないので Tegra2/3/4 で ERROR
for( int i= start ; i < end ; i++ ){
    ..
}
// (2) コンパイル時にループ範囲が求まるので Tegra2/3/4 でも OK
for( int i= 0 ; i< 100 ; i++ ){
    if( i >= start && i < end ){
        .. 動的ループ相当
    }
}

これはループ回数が定数ならば、Unroll できるためです。 おそらく (2) は Tegra2/3/4 でコンパイルすると下記のように展開されていると考えられます。

i= 0;
if( i >= start && i < end ){
    ..
}
i= 1;
if( i >= start && i < end ){
    ..
}
~
i= 99;
if( i >= start && i < end ){
    ..
}

Adreno 320/Adreno 330 OpenGL ES 2.0 の undef (2014/01/13 追加)

  • Adreno 320 + Android 4.4 + OpenGL ES 2.0 で発生
  • Adreno 330 + Android 4.2 + OpenGL ES 2.0 で発生
  • Adreno 320 + Android 4.4 + OpenGL ES 3.0 では発生しない

shader 内で #undef を使うとそのシンボルを再定義することができない。 再定義しても参照エラーが発生する。

#define SYMBOL_A   1
 
#if SYMBOL_A == 1     // ここで一度参照#endif
 
#ifdef SYMBOL_A
# undef SYMBOL_A
#endif
 
#define SYMBOL_A   1  // 再定義
 
 
#if SYMBOL_A == 1    // ← ここでエラー
 
  ~
 
#endif

再定義したシンボルが無効になっています。 Adreno のみ発生します。

Android PowerVR SGX GLSL ES 2.0 関数パラメータ inout が無効になる (2014/03/16 追加)

  • PowerVR SGX540 + Android 4.1 + OpenGL ES 2.0 で再現
  • PowerVR SGX543MP3 + iOS7.1 + OpenGL ES 2.0 では問題ない

Android PowerVR SGX GLSL で、inout を使って関数から値を返すことができない。 2つ以上 inout 宣言を行った場合に発生。

// Fragment Shader
void Lighting( in lowp vec3 color, in vec4 light, in vec3 normal, inout lowp vec3 diffuse, inout lowp vec3 specular )
{
   ~
   diffuse+= clamp( dot( light, normal ), 0.0, 1.0 ) * color;
   specular+= ...
}
 
 
void main()
{
    lowp vec3   diffuse= vec3( 0.0, 0.0, 0.0 );
    lowp vec3   specular= vec3( 0.0, 0.0, 0.0 );
    ~
 
    Lighting( LightColor, LightVect, normal, diffuse, specular );
 
    ~
}

上記のようなケースで Android 版 PowerVR SGX 540 の GLSL では diffuse, specular にライティング結果が入りません。 常に main() 内で定義した diffuse, specular の値がそのまま用いられており、inout 宣言が有効に機能していないことがわかります。

GLSL は inout 宣言を行うと変数渡しが可能です。 本来は Lighting() 関数内の演算結果が main() 側の diffuse, specular にも反映されます。

PowerVR 以外の Adreno, Vivante, GeForce (desktop) などでは問題なく動作しています。 また iOS の PowerVR では他の GPU と同じように正しく動いています。

対策方法としては、hlsl でよく用いられるように構造体を使う方法があります。

struct RETVAL {
   lowp vec3 diffuse;
   lowp vec3 specular;
};
 
RETVAL Lighting( in lowp vec3 color, in vec4 light, in vec3 normal, lowp vec3 diffuse, lowp vec3 specular )
{
    ~
    RETVAL  ret;
    ret.diffuse= diffuse;
    ret.specular= specular;
    return  ret;
}

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 ごと落ちます。 Desire X06HT / ZEN Touch 2 で確認。

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

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

"Adreno"
"Adreno 200"
"Adreno (TM) 200"
"AMD Z430"

正しく動くケースもあるので、VRAM 消費量など何らかの原因があるものと考えられます。

OpenGL ES 2.0 GLSL 実行結果

Adreno 320 で gl_FragCoord が 1 pixel ずれている (2013/10/14 追加)

Adreno 320 で OpenGL ES 2.0 を使用した場合に FragmentShader の gl_FragCoord が、他の GPU と 1pixel ずれることがあります。 発生原因は特定できていません。 Android 4.1/Android 4.3 で症状を確認。

gl_FragCoord を使用せずに varying を使って Vertex Shader から値を渡すと正しい結果となります。

演算精度が異なる (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 相当の処理が入るため。

Mali-400MP の FragmentShader で演算精度が足りない (2014/03/17 追加)

OpenGL ES 2.0 系 GPU の中で、FragmetnShader の演算精度は Mali-400MP が最も低精度になります。 同じ mediump でも Tegra2/3/4 の方が精度が高いためです。

GPU VertexShader 最大 VertexShader 最小 FragmentShader 最大 FragmentShader 最小
Adreno 200/300 fp32 (highp) fp32 (highp) fp32 (highp) fp32 (highp)
Vivante GC fp32 (highp) fp32 (highp) fp32 (highp) fp32 (highp)
Mali-T604 fp32 (highp) fp16 (mediump) fp32 (highp) fp16 (mediump)
PowerVR SGX fp32 (highp) fix10 (lowp) fp32 (highp) fix10 (lowp)
Tegra2/3/4 fp32 (highp) fix10 (lowp) fp20? (mediump) fix10 (lowp)
Mali-400MP fp32 (highp) fp32 (highp) fp16 (mediump) fp16 (mediump)

Tegra/2/3/4, Mali-400MP は highp に対応していません。

高精度の depth buffer 、Per Pixel 単位の点光源座標、大きなサイズの texture UV 計算等で、 Mali-400MP の FragmentShader は演算精度が足りなくなる可能性があります。

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 では動作することが判明しました。 ハードウエアではなく、ドライバなど他に原因が有りそうです。

Tegra3 で OffScreen へのレンダリング時に Blend が有効にならない (2015/09/17)

Android 4.0 の古いドライバで発生します。 OS 更新で新しいドライバが適用されると直ります。

Vivante GC4000 (K3V2) で動的に生成した Mipmap が有効にならない (2015/09/17)

詳細は未確認です。使い方に問題がある可能性があります。

opengl/glsl.txt · 最終更新: 2015/09/17 01:40 by oga