日別アーカイブ: 2012年2月24日

OpenGL ES 2.0 GLSL precision 宣言と GPU 毎の演算精度を調べる

OpenGL ES 2.0 の GLSL では highp, mediump, lowp と 3種類の
演算精度の宣言ができます。
C言語の float と double のようなもので、浮動小数点演算の場合
highp が 24~32bit、mediump が 16bit (half)、lowp はそれ
以下となります。
実際に用いられる演算精度は実装に依存しており GPU によって異なります。

一般的に演算精度を下げることによってレジスタの消費量を抑える
ことができ、並列実行可能なスレッド数の低下を防ぐことができます。
また純粋に ALU の演算能力不足を補うために低精度宣言が効果的な
GPU もあります。
逆にこれらの宣言が全く意味を持っていない GPU もあります。
現在の Mobile 向け GPU はどれも設計思想が異なっており
その特性は千差万別です。

最適化するためにはどこまで演算できるか限界をある程度把握して
おく必要があります。
OpenGL ES の命令 glGetShaderPrecisionFormat() を使うと
演算精度のより詳しい情報が得られます。
各 GPU 毎に調べた値を下記のページに加えました。

Mobile GPU の Extension

// 例 PowerVR SGX
Precision
 0: [1 1] 8
 1: [14 14] 10
 2: [126 126] 23
 3: [8 8] 0
 4: [11 11] 0
 5: [24 24] 0
 6: [1 1] 8
 7: [14 14] 10
 8: [126 126] 23
 9: [8 8] 0
10: [11 11] 0
11: [24 24] 0

[range-min range-max] precision
上から順に vertex float lowp~highp, int lowp~highp,
fragment float lowp~highp, int lowp~highp

この情報をもとにまとめたのが下記ページの表です。

GPU Precision まとめ

以下は FLOAT 部分のみの抜粋です。

                        Vertex Shader        Fragment Shader
FLOAT                   high medium low      high medium low
--------------------------------------------------------------
Adreno 200系 unified    fp32  fp32  fp32     fp32  fp32  fp32
GC860        unified    fp32  fp32  fp32     fp32  fp32  fp32
PowerVR SGX  unified    fp32  fp16  fix10    fp32  fp16  fix10
Mali-400MP   discrete   fp32  fp32  fp32     ----  fp16  fp16
Tegra2/3     discrete   fp32  fp32  fp32     ----  fp20? fix10

まず大きく分けて Unified タイプと Discrete の GPU があります。
Unified は Vertex と Fragment の演算機能が同一です。
また Discrete タイプの Fragment Shader はどれも highp 未対応です。

Unified : Adreno/GC800/PowerVR  Vertex Fragment同一
Discrete: Mali-400/Tegra2/3     Fragment に highp無し

常に精度一定な GPU と precision 系(lowp等)の宣言が有効な GPU があります。

無効: Adreno 200系/GC860/Mali-400?
有効: PowerVR SGX/Tegra2/3

また Vertex Shader の場合、PowerVR SGX 以外はどれも fp32 固定で
highp だけが用いられているように見えます。
ですが、実際にシェーダーを走らせて演算範囲を検証すると、上記の
結果と異なっていることがわかりました。

例えば Vertex Shader で下記のようにわざと演算精度を落とすような
演算を加えてみます。

// Vertex Shader
vec2  uv= TEXCOORD0.xy * 0.01;
uv+= 1.0;
~
vTexcoord.xy= uv * 100.0 - 100.0;

Tegra では明らかに mediump, lowp 宣言の影響を受けます。
また Mali-400MP もわずかに違いが見られます。

わかりやすくするためカラーを用いました。
Fragment Shader なら下記のようにして SCALE の値がどの範囲まで
正しく描画できるか調べます。

// Fragment Shader (Pixel Shader)
precision mediump float;
#define SCALE  100.0
#define PREC   lowp
varying mediump vec2 vTexcoord;
varying PREC vec4 vColor;   // == vec4(1.0)
uniform lowp sampler2D ColorMap;
void main()
{
    PREC vec4  color= texture2D( ColorMap, vTexcoord );
    color*= vColor.x * SCALE;         // -- (A)
    color*= vColor.y * (1.0/SCALE);   // -- (B)
    gl_FragColor= color;
}

例えば PowerVR や Tegra の lowp なら SCALE が 2 を超えると
色が暗くなり、大きく範囲を外れると color が 0 になります。
上限 2.0 で clamp されていることがわかります。

反対に highp 固定の Adreno/GC860 の場合は lowp と書いてあっても
1.0e37 まで正しい色で描画できます。

(A) と (B) を入れ替えたり SCALE を負数にしても試します。
Vertex Shader でも頂点カラーを使い同様に調べられます。
値の範囲から実際の演算精度を予想し、highp, mediump, lowp 毎に
まとめ直したのが下記の表です。

                           Vertex Shader        Fragment Shader
                           high medium low      high medium low
------------------------------------------------------------------
PVR SGX535/540  unified    fp32  fp16  fix10    fp32  fp16  fix10
PVR SGX543MP    unified    fp32  fp16  fix10    fp32  fp16  fix16?
Tegra2          discrete   fp32  fp16  fix10?   ----  fp16  fix10
Mali-400MP      discrete   fp32  fp32  fp32     ----  fp16  fp16
Adreno 200系    unified    fp32  fp32  fp32     fp32  fp32  fp32
GC860           unified    fp32  fp32  fp32     fp32  fp32  fp32

exp 範囲から推測しているので間違っている可能性があります。

●PowerVR SGX

ほぼ仕様通りで Vertex/Fragment 共に 3段階、明確な違いがあります。
lowp は -2.0~2.0 の範囲のみとなります。
ただ GPU によって Fragment mediump の範囲にばらつきが生じます。
543MP は Fragment lowp で mediump 相当の値を示していますが、
計測に問題があった可能性があります。

●Tegra2

Vertex Shader は PowerVR SGX 同様 3段階の違いがはっきり現れます。
ただし Vertex lowp は -2.0~2048 の範囲が有効だったので、計測に
間違いがなければ正の方向にオフセットが加えられているようです。
より精度が高い可能性があります。
Fragment mediump は普通の fp16 でした。

●Mali-400MP

Vertex Shader の演算範囲は mediump/lowp の影響を受けず
fp32 固定でした。
ただし precision で default 宣言を行うと mediump で描画に差が生じます。
レジスタと ALU が fp32 固定でも uniform / varying が fp16 に
切り替わっている可能性があります。lowp はありません。
Fragment は fp16 固定でした。

●Adreno 200/205/220 / Vivante GC860

すべて fp32 固定でした。
mediump / lowp の影響を受けません。

これらの実際に計測した結果も下記のページにまとめています。
整数は調べていません。

GPU Precision まとめ

(2012/03/03 修正: fix8 を fix10 に訂正させて頂きます。コメントによる指摘ありがとうございました。)

関連エントリ
OpenGL ES 2.0 shader の演算精度