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

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 の演算精度

OpenGL ES 2.0 Adreno 205 と discard 命令の問題

GPU Adreno 205 は Fragment Shader で discard 命令を使うと
フリーズし、OS ごと再起動することがあるようです。
コメントchototsu さんより情報を頂きました。
ありがとうございました。

試したらあっさり再現してしまいました。
ドライバレベルで停止しているらしく発生すると何もできなくなります。
デバッガで強制的にプロセスを削除すると OS ごと再起動します。
またはシェーダー実行直後に何もしなくても OS がリブートします。

試した機種
Xperia Ray SO-03C Android 2.3.3
Qualcomm MSM8255 1.0GHz Adreno 205

Adreno 200/220 では発生しません。205 だけです。
ただ 205 でもフリーズしないケースもあったので、条件を変えて
症状を調べてみました。

結論としては
FragmentShader で Texture を使用している場合に、
discard 命令より後で Texture フェッチが発生すると固まります。

Alpha Test や Clip Plane 等で discard を使う場合は
十分注意した方がよさそうです。

以下検証した結果

●Texture を使っていないシェーダーの場合

(1) 無条件 discard は固まらない
(2) varying 依存 discard も固まらない

// (1)  問題なし
precision mediump float;
void main()
{
    discard;
}
// (2)  問題なし
precision mediump float;
varying vec2 vTexcoord;
void main()
{
    if( vTexcoord.x < 0.5 ){
        discard;
    }
    gl_FragColor= vec4(1.0, 1.0, 0.0, 1.0);
}

●Texture を使っているシェーダーの場合

(3) 無条件 discard はどこに挿入しても固まる。
(4) texture 依存 discard は shader の一番最後なら固まらない。
  ・すべての texture 読み込み命令のあとなら固まらない。
(5) varying 依存 discard も (4) と同じだが、texture フェッチの
  前でも固まらない場合があった。

// (3)  freeze
precision mediump float;
varying vec2 vTexcoord;
uniform lowp sampler2D ColorMap;
void main()
{
    // discard; ここでも同じ
    gl_FragColor= texture2D( ColorMap, vTexcoord );
    discard;
}
// (4) - A  問題なし
precision mediump float;
varying vec2 vTexcoord;
uniform lowp sampler2D ColorMap;
void main()
{
    lowp vec4  color= texture2D( ColorMap, vTexcoord );
    if( color.w < 0.7 ){
        discard;
    }
    gl_FragColor= color;
}
// (4) - B  freeze
precision mediump float;
varying vec2 vTexcoord;
uniform lowp sampler2D ColorMap;
void main()
{
    lowp vec4  color= texture2D( ColorMap, vTexcoord );
    if( color.w < 0.7 ){
        discard;
    }
    color+= texture2D( ColorMap, vTexcoord + vec2(0.1,0.1) );
    gl_FragColor= color;
}
// (4) - C  問題なし
precision mediump float;
varying vec2 vTexcoord;
uniform lowp sampler2D ColorMap;
void main()
{
    lowp vec4  color= texture2D( ColorMap, vTexcoord );
    color+= texture2D( ColorMap, vTexcoord + vec2(0.1,0.1) );
    if( color.w < 0.7 ){
        discard;
    }
    gl_FragColor= color;
}
// (5) - A  freeze
precision mediump float;
varying vec2 vTexcoord;
uniform lowp sampler2D ColorMap;
void main()
{
    if( vTexcoord.x < 0.5 ){
        discard;
    }
    gl_FragColor= texture2D( ColorMap, vTexcoord );
}
// (5) - B  問題なし
precision mediump float;
varying vec2 vTexcoord;
uniform lowp sampler2D ColorMap;
void main()
{
    gl_FragColor= texture2D( ColorMap, vTexcoord );
    if( vTexcoord.x < 0.5 ){
        discard;
    }
}

テクスチャ命令との順番が関係しています。(3),(5) に関しては
例外もありますが discard が Texture と依存関係が無いので、
コンパイラによって命令の実行順が変更されたためだと考えられます。

対策としては discard を使わないか、すべてのテクスチャ命令のあと
出来るだけ最後に記述するようにします。

ただ幸いなことに、discard は半透明と同じように TBDR の PowerVR
と大変相性が悪い命令です。さまざまな GPU への対応を考えると、
おそらくテクスチャを多数読み込むような複雑なシェーダーでは、
discard があまり積極的に用いられていないのではないかと思います。
(Adreno でもあまり推奨されていません。)

また discard の用途は多くが Alpha Test だと思うので、この場合
必ず Texture 命令のあとに用いられます。
上と同じ理由でテクスチャ一枚きりの単純なものが多く、今まで
あまり問題になっていなかったのではないかと考えられます。

関連エントリ
さらに OpenGL ES 2.0 Mobile GPU の速度比較

Android 4.0 MIPS で RenderScript, ainol Novo 7 Paladin の浮動小数点演算速度

Novo 7 Paladin は MIPS CPU で Android 4.0 を搭載した端末です。
RenderScript を試してみました。

前回の記事で触れたように、mips 対応版 NDK を入れると対応
アーキテクチャが一気に 6種類に増えます。
さらに armeabi-v7a や mips-r2 の NDK では、ハードウエアの性能を
引き出すために CPU のオプション機能を利用することができます。
ただし搭載していないハードもあるため事前に CPU Features を
チェックしなければなりません。
下記は NDK の cpu-features が返す値です。

armeabi-v7a:
 ANDROID_CPU_ARM_FEATURE_ARMv7
 ANDROID_CPU_ARM_FEATURE_VFPv3
 ANDROID_CPU_ARM_FEATURE_NEON

mips-r2:
 ANDROID_CPU_MIPS_FEATURE_R2
 ANDROID_CPU_MIPS_FEATURE_DSP
 ANDROID_CPU_MIPS_FEATURE_DSP_R2

種類が増えてくると個別最適化は困難となりますし、ビルドや
テストの手間もかかります。
こんな場合 CPU 非依存で Native 相当の実力を持つ RenderScript は
検討する価値がありそうです。

Android 4.0 対応、RenderScript のサンプル

上の記事に掲載したサンプルは MIPS の Paladin でも動作しました。
ただし一部修正が必要でした。
Android 3.x (API 11~13) をターゲットにビルドした RenderScript の
アプリケーションは実行時に下記のようなエラーが出ます。

Could not parse bitcode file
Failed to translate bitcode from version: 11

RenderScript の byte code (bitcode) は Android 3.x (API 11~13)
と Android 4.0 以上 (API 14 以降) で互換性が無いようです。
そのため API Level 14 以上を target にして再コンパイルする
必要があります。

(1) SdkVersion を 14 以上にする

AndroidManifest.xml の SdkVersion を 11 から 14 に修正します。

    

 ↓

    

もし同じフォルダに project.properties ファイルが作られているなら
target=android-15 (または 14) に書き換えます。

(2) rsForEach() を API 14 以降の書式に変更

上のページで紹介したプログラムでは必要ありませんが、Android
SDK 付属の RenderScript サンプルを走らせる場合は必要です。
API 14 以降は rsForEach() の引数が書式が変更されているので、
rsForEach() でコンパイルエラーが出たら引数最後の 0 を削除します。

(3) bc ファイルを作りなおす

bc ファイルを作りなおすためにプロジェクトを一旦 Clean してから
ビルドし直します。
Eclipse の場合はプロジェクトを選択して Menu Project → Clean…

これで Paladin (MIPS + Android 4.0) 上で RenderScript の
プログラムが動くようになります。
描画はそこそこですが CL 系は遅いです。
CPU が Single core であることに加え、浮動小数点演算もあまり
速くないようです。

●実行速度比較(1)

まずは Linpack for Android v1.2.8 で比較してみました

Single   Multi
MFLOPS  MFLOPS   OS   CPU
----------------------------------------------------------------------
18.091  --       2.3  Cortex-A8    1.0GHz S5PC110    Galaxy S SC-02B
18.592  --       4.0  MIPS XBurst  1.0GHz JZ4770     Novo7 Paladin
35.628  --       2.2  Scorpion     1.0GHz QSD8250    Desire X06HT
30.33   57.233   3.1  Cortex-A9 x2 1.0GHz Tegra2     Optimus Pad L-06C
46.164  74.664   2.3  Scorpion  x2 1.2GHz MSM8660    EVO 3D ISW12HT
57.342  92.981   2.3  Cortex-A9 x2 1.2GHz Exynos4210 Galaxy S2 SC-02C
47.071 140.908   3.2  Cortex-A9 x4 1.3GHz Tegra3     EeePad TF201

 * MFLOS の数値が大きい方が速い。
 * Single = Single-Thread
 * Multi = Multi-Thread

Multi Thread はタイミングによるばらつきが大きいため何度か
実行し最速の数値を取っています。あまり正確でないかもしれませんし、
単精度ではまた違った結果になると思います。
取り敢えず Paladin の fpu は激遅の Cortex-A8 VFP に近い速度
ということがわかりました。

●実行速度比較(2)

RenderScript, Java, NDK C, NDK asm の演算比較。
4×4 Matrix * Vector の 50万回。(1)~(13) すべて同じ演算。

                   Paladin     GS   Desire  Tegra2   GS2     GN   Tegra3
                    XBurst     A8  Scorpion  A9x2    A9x2   A9x2   A9x4
                    1.0GHz  1.0GHz  1.0GHz  1.0GHz  1.2GHz 1.2GHz 1.3GHz
------------------------------------------------------------------------
(1) RenderScript A     290      --     --      52      --     29     57
(2) RenderScript B     314      --     --      69      --     44     62
(3) RenderScript C     310      --     --      51      --     31     37
(4) Java Inline        482     698    418     244     179    174    167
(5) Java Matrix      11763    1012   1906    1232    1127   4785    840
(6) NDK (C func)       158     166     60      79      39     39     43
(7) NDK C inline1      267     288     49      66      43     33     41
(8) NDK C inline2      243     289     54      65      46     32     41
(9) NDK asm VFP         --     139     35      46      22     19     35
(10)NDK asm VFP MT2     --     139     37      26      14     15     48
(11)NDK C NEON Intrin   --      26     33      --      26     21     34
(12)NDK asm NEON        --      20     23      --      38     18     32
(13)NDK asm NEON MT2    --      22     29      --      26     17     17

実行時間(msec) 数値が小さいほうが速い。
  * GS=Galaxy S SC-02B,  GS2=Galaxy S2 SC-02C,  GN=Galaxy Nexus SC-04D
  * Tegra2= Optimus Pad L-06C, Tegra3= EeePad TF201
  * A8= Cortex-A8,  A9= Cortex-A9
  * Intrin= Intrinsic (Builtin Function)
  * MT2= Multi thread x2

速いデバイスほど実行時間が短く誤差が出ます。
また演算命令が高速になるほどメモリアクセス速度の影響が増えます。
マルチスレッド実行の場合スレッドの起動や同期によるばらつきが大きく
あまり正確ではありません。

RenderScript の演算では Paladin は A9 dual と 5~10倍くらいの
差が生じていることがわかります。

Paladin の fpu 自体は Cortex-A8 VFP より若干高速です。
ただし、Cortex-A8 も NEON を使うと数倍高速であり、A9 の速度に
ほとんど追い付いています。
Scorpion/A9 になると VFP/NEON 共に安定して速度が出ています。

注意点としてはJava の実行時間には GC が含まれている可能性があります。
また最高でも 2 Thread しか使っていないので Tegra3 は最速ケースでは
ありません。

関連エントリ
Android 4.0 ainol Novo 7 Paladin、MIPS CPU の NDK と Vivante GPU
Android 4.0 対応、RenderScript のサンプル
Android 3.x RenderScript (1)
Snapdragon の本当の浮動小数点演算能力
ARM Cortex-A8 の NEON と浮動小数演算最適化

Android 4.0 ainol Novo 7 Paladin、MIPS CPU の NDK と Vivante GPU

ARM でも x86 でもない、MIPS の CPU を搭載した
Android 端末 ainol Novo 7 Paladin を使ってみました。

Ingenic JZ4770
CPU core: XBurst (MIPS32)
GPU core: Vivante GC860

CPU/GPU (OpenGL ES Extension 等) は下記に追加

CPU/GPU: Novo7 Paladin Android 4.0 JZ4770 MIPS XBurst GC860

以下抜粋

processor		: MIPS-compatible processor JZ4770 
cpu model		: Ingenic Xburst 
ASEs implemented	: mxu
Features		: fpu mxu dsp lowpower 

GL_VERSION: OpenGL ES 2.0
GL_RENDERER: GC860 core
GL_VENDOR: Vivante Corporation
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL ES 1.00

下記の GPU 機能比較表にも追加しました

Mobile GPU の比較

GPU は Unified Shader で FragmentShader も highp 対応。
テクスチャ圧縮フォーマットは DXT1/3/5 (S3TC)、
24bit depth に対応しており depth_texture もあります。
Vertex の Stream 入力が若干少なめ。
レンダリングは 2048 までですが使えるテクスチャサイズが
8192 と突出しています。

NDK を使って実際にシェーダーを走らせてみました。
下記のベンチマーク結果の表に追加しています。

Mobile GPU bench mark

公式スペックもフィルレートがあまり高くなかったので、
GC800 の世代的には PVR SGX530/535 あたりと同等なのかもしれません。
より高速な GPU として GC1000/GC2000/GC4000 も存在しています。

Vivante Graphics Processor IP

Freescale i.MX5 系の GPU は AMD Z430 ですが、すでに Qualcomm
に売却されており Adreno と名称が変わっています。
i.MX6 では Vivante の新しい GPU core が採用されるようです。

●MIPS 版 NDK

Google の Android NDK には MIPS が含まれていませんが、
下記の MIPS サイトからダウンロードすることができます。

MIPS Developers: Download MIPS Android NDK

既存の NDK (ARM/x86) をすべて含んでいるため、同バージョンの
NDK と置き換えるだけで mips 対応になります。
例えばすでに C:/android/android-ndk-r7 というフォルダがあるなら

1. c:/android/android-ndk-r7 を削除
1. android-ndk-r7m-windows-20120103.zip を展開
2. android-ndk-r7m を android-ndk-r7 にリネームして C:/android に入れる

これだけです。ARM, x86, MIPS すべてに対応します。

MIPS 対応 NDK では新しく3つのアーキテクチャが追加されます。
倍になりました。既存のものと合わせてまとめると下記の通り。

Android NDK  IA         GCC                              fpu simd
---------------------------------------------------------------------------
armeabi      ARMv5TE    -march=armv5te                   --  --
armeabi-v7a  ARMv7A     -march=armv7-a -mfpu=vfp         VFP (NEON)
x86          IA-32      -march=i686 -mfpmath=sse -msse3  SSE SSE3
mips         MIPS32 R1  -mips32  -mhard-float            FPU --
mips-r2      MIPS32 R2  -mips32r2 -mhard-float           FPU (DSP ASE)
mips-r2-sf   MIPS32 R2  -mips32r2 -msoft-float           --  --

XBurst は ase もあり mips-r2 のように見えますが Paladin は mips でした。
(2012/02/14 追記: NDK では mips ですが ROM は mips-r2 で build されているそうです)
NDK の Application.mk 等に下記のように記述すれば全対応になります。

APP_ABI	:= armeabi armeabi-v7a x86 mips mips-r2 mips-r2-sf

バイナリ容量はかなり増えそうです。
RenderScript はまだ試していません。

(2012/02/14 追記: NDK r7 からは 「APP_ABI := all」と記述するだけで全対応になります。)

(2012/04/17 追記: NDK r7bm から mips-r2, mips-r2-sf が無くなりました)