Archives

March 2014 の記事

Android NDK r9d の GLES2/gl2ext.h には、本来無いはずの "include gl2.h" が
追加されているために、OpenGL ES 3.0 と組み合わせて利用できなくなっています。
r9d で追加されたようです。

#include <GLES2/gl2.h>

GLES3/gl3ext.h は中身が空なので、例えば GPU 固有のテクスチャフォーマット
PVR, ATC, DXT 等のシンボルを用いるには gl2ext.h を include する必要がありました。

とりあえずの対処としては、
Android の場合だけ __gl2_h_ を定義してしまう方法があります。

#define __gl2_h_   // 追加
#include <GLES3/gl3.h>
#include <GLES2/gl2ext.h>

Android 以外や Khronos のヘッダファイルでは問題ありません。


関連エントリ
Android NDK r9d の armeabi-v7a-hard と ABI
iPhone 5s の Apple A7 GPU
Nexus 7 (2013) の Adreno 320 と OpenGL ES 3.0 (Android 4.3)
OpenGL ES 3.0/OpenGL 4.x Uniform Block


OpenGL ES 3.1 の仕様が公開されました。
バージョン番号は 3 のままですが、内容は OpenGL 4.x の機能を
取り込んだ大幅なアップデートとなっています。
最近のハイエンド Mobile GPU は、Windows Tablet を想定して Direct3D 11
API も積極的にサポートしつつあります。
OpenGL ES 3.1 にはこれらの GPU の進化がそのまま反映されていると言えます。

Khronos OpenGL ES Registry

世代毎の API
OpenGL ES        OpenGL         Direct3D      ShaderModel
-------------------------------------------------------------
OpenGL ES 2.0    OpenGL 2.1     Direct3D9     ShaderModel 3.0
OpenGL ES 3.0    OpenGL 3.3     Direct3D10.1  ShaderModel 4.1
OpenGL ES 3.1    OpenGL 4.4     Direct3D11.2  ShaderModel 5.0

ユニークなのは、OpenGL ES が GeometryShader や Tessellator 等の
組み込みのシェーダーパイプラインを採用しなかったことです。
その代わり柔軟で扱いやすい ComputeShader に仕様を絞り込んでおり、
機能と自由度のバランスを上手く保っている印象です。

機能の比較      Shader                      Version
------------------------------------------------------------
OpenGL ES 2.0   vsh fsh ---  ---  --- ---   #version 100
OpenGL ES 3.0   vsh fsh ---  ---  --- ---   #version 300 es
OpenGL ES 3.1   vsh fsh ---  ---  --- csh   #version 310 es
OpenGL 2.1      vsh fsh ---  ---  --- ---   #version 120
OpenGL 3.3      vsh fsh gsh  ---  --- ---   #version 330
OpenGL 4.4      vsh fsh gsh tcsh tesh csh   #version 440
Direct3D 9      vsh psh ---  ---  --- ---   3.0
Direct3D 10     vsh psh gsh  ---  --- ---   4.0
Direct3D 11     vsh psh gsh  hsh  dsh csh   5.0

より詳しい表はこちら↓

GLSL Version

ComputeShader をサポートしたことで、同時にバッファのランダムアクセスや
読み書き、Atomic なオペレーション命令などもひと通り入りました。
どれも OpenGL 4.x 相当です。Indirect Draw もあります。

Texture Image Load and Store   OpenGL 4.2
Atomic Counter Buffer          OpenGL 4.2
Shader Storage Buffer          OpenGL 4.3
Shared Memory                  OpenGL 4.3

バッファアクセスについて詳しくは下記のエントリを参照してください。

OpenGL 3.x/4.x のシェーダー GLSL とメモリアクセス命令

buffer 命令が増えたことで GLSL の layout() 構文も賑やかになりました。
これまで Uniform や Attribute のバインドには、OpenGL API を使って
Location を参照したり値を書き換える必要がありました。
GLES 3.1 (と OpenGL 4.3以降) は GLSL 内に直接数値で宣言できます。

また Direct3D のように実行時にシェーダーのステージを組み合わせられる
ようになります。詳細は下記をどうぞ。

OpenGL 4.x Program Pipeline Object (Separate Shader Object)

Shader Stage は少ないものの、OpenGL ES 3.1 は OpenGL 4 の特徴を取り入れており
Direct3D との親和性(移植しやすさ) が向上していることがわかります。

OpenGL 4.x とのコード共有もしやすくなるのですが、
Desktop PC では Intel GPU が OpenGL 4.0~4.1 に留まっており、
上記の機能の多くを使うことができません。
OpenGL で ComputeShader を当たり前に使えるようになるには
まだ時間がかかりそうです。

OpenGL ES の GLSL が暗黙の型変換をサポートしないのは 3.1 でも同様で、
仕様となっています。
Uniform Block (Interface Block) の input/output はサポートされませんでした。

GLES3.1 は OpenGL ES 2.0/3.0 との上位互換性を保っています。
Mobile GPU が活発になってからは、バージョンが進むほど下位互換性が復活する
傾向があります。
OpenGL 3~4 もそうですし、Direct3D 10~11 も同様でした。

Mobile GPU が高機能になるにつれて、Desktop GPU 向け API と区別する
必要性はなくなりつつあります。
OpenGL を使う側の立場としても、最終的には統合して欲しい願っています。
ですが、WebGL のように軽量な API として用途が広がる可能性を考えると
ComputeShader に絞った仕様は良い選択かも知れません。



関連エントリ
iPhone 5s の Apple A7 GPU
Nexus 7 (2013) の Adreno 320 と OpenGL ES 3.0 (Android 4.3)
OpenGL 3.x/4.x のシェーダー GLSL とメモリアクセス命令
OpenGL 4.x Program Pipeline Object (Separate Shader Object)
OpenGL 4.2/4.3 Shader Resource と Buffer API
OpenGL ES 3.0/OpenGL 4.x Uniform Block
OpenGL の各バージョンと GLSL の互換性
OpenGL のエラー判定と OpenGL 4.3 Debug Output
OpenGL ES 3.0/OpenGL 4.4 Texture Object と Sampler Object
OpenGL ES 3.0 と Vertex Array Object


Android NDK r9d では、新しく APP_ABI に armeabi-v7a-hard が追加されました。
とはいえ端末側の ABI の種類が増えたわけではなく、hard-float を指定した
バイナリのビルドがより簡単に行えるようになります。

Android NDK

r9b/r9c では hard-float を使うために jni/Android.mk に直接オプションを
記述していました。

# android-ndk-r9b/r9c
# jni/Android.mk
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_CFLAGS+= -mhard-float -D_NDK_MATH_NO_SOFTFP=1
LOCAL_LDLAGS+= -lm_hard -Wl,--no-warn-mismatch
endif

r9d からは上記のような Android.mk の修正が不要になります。
APP_ABI に armeabi-v7a-hard を指定するだけで hard-float に対応します。
例えば ndk-build コマンドの引数で与えるなら下記の通り。

ndk-build APP_ABI=armeabi-v7a-hard

jni/Application.mk に書いておくなら下記の通り。

# jni/Application.mk
APP_ABI:= armeabi-v7a-hard armeabi x86 mips

APP_ABI:=all ではデフォルトの armeabi-v7a とみなされるので、
hard-float を使う場合は上記のように全部列挙しておく必要があります。


Android OS 側は softfp のままなので、hard-float が有効なのはあくまで
アプリケーション内の関数呼び出しに限定されます。
例えば OpenGL ES のような浮動小数点パラメータを持つ System API の
呼び出しは、これまでどおり softfp が用いられます。
hard-float といいつつ厳密には softfp も混在していることになります。

そのため OS から見れば従来のアプリケーションと全く同じなので、
バイナリを区別する必要がありません。

armeabi-v7a-hard でビルドしたバイナリも libs/armaebi-v7a に入ります。
他の APP_ABI と違い、armeabi-v7a と armeabi-v7a-hard の 2種類生成する
わけではないのでご注意ください。
どちら片方を選択します。

互換性が問題になるケースはおそらく外部ライブラリ利用時でしょう。
リンクするライブラリも下記いずれかの方法で hard-float に対応している
必要があります。


ライブラリの hard-float 対応手段は二通りあります。

 (1) ライブラリも hard-float でビルドする
 (2) ヘッダファイルに softfp 指定 __NDK_FPABI__ を追加する

(1) の場合は spftfp 版と hard-float 版ライブラリの両方を用意しておく
必要があります。

(2) はヘッダの関数宣言時に softfp 呼び出しを意味する __NDK_FPABI__ を
記述しておきます。(中身は __attribute__((pcs("aapcs"))) )
この場合ライブラリは softfp 一種類だけです。

float fpfunc( float a, float b ) __NDK_FPABI__;

NDK の hard-float について詳しくは下記も参照してください。

Android NDK r9b と ARMv7A の hard-float


Android NDK で使用可能な ABI 指定のまとめ。

armeabi           ARMv5TE   FPU 無し        soft-float
armeabi-v7a       ARMv7A    VFPv3-D16       softfp
armeabi-v7a-hard  ARMv7A    VFPv3-D16       hard-float (softfp混在)
x86               x86       SSSE3           hard-float
mips              MIPS32R1  FPU             hard-float

ARM 以外は最初から FPU 必須で hard-float 相当です。
ちなみに ios は下記の通り。

armv6             ARMv6     VFPv2-D16       softfp
armv7             ARMv7A    VFPv3-D32 NEON  softfp
armv7s            ARMv7A    VFPv4-D32 NEON  spftfp
arm64             ARMv8     AArch64 NEON    hard-float

soft-float, softfp, hard-float の違い。

             FPU       浮動小数点演算          関数パラメータ
---------------------------------------------------------------------
soft-float   FPU無し   soft/ライブラリ呼び出し 整数レジスタ経由
softfp       FPUあり   hard/FPU が行う         整数レジスタ経由
hard-float   FPUあり   hard/FPU が行う         浮動小数点レジスタ直接


関連エントリ
Android NDK r9b と ARMv7A の hard-float
iPhone 5s A7 64bit CPU と AArch64 (arm64)
Nexus 7 の Ubuntu で ARM の abi softfp と hard-float を比べる


Desktop GPU で Vertex Shader と Pixel Shader の仕様が完全に
統一されたのは Direct3D 10 の ShaderModel 4.0 からです。
それまでは使える命令や Constant 領域、レジスタ数、プログラムサイズ、
テクスチャ命令など様々な違いが残っていました。

Hardware の Unified Shader 化は ATI の Xbox360 GPU が先行しています。
これが今の Qualcomm Adreno に繋がっていきます。
その後一般の Desktop PC 向けにも NVIDIA GeForce 8800 (G80) が登場し、
以後 Unified Shader model が当たり前となっています。

OpenGL ES 2.0 の Shader 世代は Direct3D 9 の Shader Model 3.0 に相当
するのですが、Mobile GPU の多くはすでに Unified Shader です。
そのためあまり Vertex と Fragment Shader の機能差を意識する必要が
ありませんでした。

そんな中、Tegra2/3/4 は G70 ベースの GPU であり、Unified Shader 化する前の
制限が残っている珍しいケースとなっています。

コメントで Tegra の Fragment Shader は動的ループが使えないとの
指摘を頂いたので試してみました。

// (1) 動的ループ
int loop= int( uniform_value );
for( int i= 0 ; i < loop ; i++ ){
   ...
}

↑ループ回数を動的に求めた場合、Vertex Shader は通りますが
Fragment Shader ではコンパイル時にエラーが発生します。

以前書いたように Uniform 配列の index アクセス ↓(2) も
Tegra の Fragment Shader ではエラーとなります。
どちらも Direct3D 9 世代の制限と考えられます。

// (2) Uniform 配列
uniform vec4 src_color[30];
~
int index= int(tex_color.x);
vec4 color= src_color[ index ]; // Tegra の Fragment Shader では Error

Tegra 4 でも同様なので、大幅に機能拡張されていても GPU のベースは
同じであることがわかります。
なお Tegra と同じように Discrete Type の Shader Unit を備える
Mai-400MP では動きました。

	     Vertex Shader            Fragment Shader
	     動的Loop  Uniform配列    動的Loop  Uniform配列
-----------------------------------------------------------
Unified      ◯         ◯              ◯         ◯
Mali-400MP   ◯         ◯              ◯         ◯
Tegra2/3/4   ◯         ◯              ERROR     ERROR

ただし Tegra でも動的分岐はできるので、実行時にループ回数が変動する
場合でも同等の処理を実現することは可能です。

// (3) Tegra向け 動的分岐によるループ
for( int i= 0 ; i< 100 ; i++ ){
    if( i >= Loop ){
        break;
    }
    ~ 動的ループ相当
}

以下まとめ (いずれも Fragment Shader の場合)

コンパイル時にループ回数が定まらない場合はエラー。

// (4) ループ回数不定  -- Tegra で ERROR
for( int i= start ; i< end ; i++ ){
    ~
}

ループの範囲が決まっていればコンパイル可能。

// (5) 上限が決まってる場合 -- Tegra でも OK
for( int i= 0 ; i< 100 ; i++ ){
    if( i >= start && i < end ){
        ~ 動的ループ相当
    }
}

おそらく下記のように展開されているものと考えられます。

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

~

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

Tegra K1 以降は ShaderModel 5.0 世代の GPU core になるので
このようなハードウエア的な制限はなくなるでしょう。

GPU 互換性など気軽に相談してください。


関連エントリ
OpenGL ES 2.0 Adreno 330, Tegra 4 の GPU 速度
OpenGL ES 2.0 で迷路シェーダー
GLSL互換性