Archives

November 2013 の記事

Android 4.3 で OpenGL ES 3.0 が導入されましたが、Adreno 320 では
一部動作に問題がありました。
具体的には Uniform Block を使用すると glLinkProgram でエラーが発生します。
Android 4.4 ではこの問題が直っており、正しく動作することが確認できました。
GPU のドライバが更新されたためだと考えられます。

以前の症状について詳しくは こちら

# Nexus 7 (2013) Android 4.3
GL_VERSION: OpenGL ES 3.0 V@14.0
GL_RENDERER: Adreno (TM) 320
GL_VENDOR: Qualcomm
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL ES 3.00


# Nexus 7 (2013) Android 4.4
GL_VERSION: OpenGL ES 3.0 V@53.0 AU@  (CL@3776187)
GL_RENDERER: Adreno (TM) 320
GL_VENDOR: Qualcomm
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL ES 3.00

他にも glIntergerv() (Query) の値など修正が入っているようです。

CPU/GPU OpenGL ES Extension (Mobile GPU)

OpenGL ES 3.0 が動くデバイスもかなり増えてきました。
Android 4.4 なら Nexus 7 (2013) が使えるので、動作確認は比較的容易に
なったのではないかと思います。

ただ実際のアプリケーションでは、確実に OS が更新されるとわかっている
Nexus 以外で利用するのは正直厳しいと言えます。

他にも OpenGL ES 3.0 が使えるかどうかの判定がうまくいかないことがあります。

OpenGL ES : Checking OpenGL ES Version

GPU によっては 3.0 を渡して Context を作ってもエラーにならず、
OpenGL ES 1.1 の Context を返すものがあります。
(おそらくドライバが 2.0 かそれ以外で判別している)

2番目の Version 番号を使う方法は Android ではうまくいきます。
Nexus 10, Nexus 7 (2013) ともに 2.0 context を作っても GL_VERSION は
3.0 を返すためです。
ただし iOS や PC では作成した context と同じバージョン文字列を返すので
少々不自然な気もします。


関連エントリ
iPhone 5s の Apple A7 GPU
Adreno 320 の OpenGL ES 3.0 と Uniform Block
Nexus 7 (2013) の Adreno 320 と OpenGL ES 3.0 (Android 4.3)


要するに新しい Android NDK r9b に入れ替えると
実行が速くなってプログラムサイズも小さくなるということです。
Android.mk に下記の行を追加してコンパイルするだけ。

ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_CFLAGS += -mhard-float
LOCAL_LDFLAGS += -Wl,--no-warn-mismatch
endif

より詳しくはこちら


関連エントリ
Android NDK r9b と ARMv7A の hard-float


Android 4.4 (KitKat) とともに NDK r9b がリリースされています。
RenderScript 対応などいくつか新しい機能がありますが、
その中に ARMv7A の hard-float 対応が含まれています。
ちょうど NDK を使った関数電卓アプリを作っているところだったので試してみました。

Android NDK

ちょっと電卓

Android/iOS など ARM デバイスではこれまで float ABI として softfp が用いられていました。
関数呼び出しなどの引数は、浮動小数点数であっても必ず整数レジスタ r を経由しており、
FPU の有無にかかわらず共通化出来るようになっています。

その代わり VFP が搭載されているデバイスでの実行効率とコード効率がわずかに犠牲になっています。
これまでの iOS/Android スマートフォンで VFP が搭載されていないのは
MSM7225 など一部の ARM11 (ARMv6, Android では ARMv5TE) に限られていました。
ARMv7A では存在しておらず softfp を用いる必要はありませんでした。

Android NDK r9b で hard-float に対応したので VFP/NEON レジスタを直接用いた
関数呼び出しが可能となっています。


●コンパイル方法

NDK で hard-float を指定する手順は下記の通り。
Android.mk に追加します。

LOCAL_CFLAGS += -mhard-float
LOCAL_LDFLAGS += -Wl,--no-warn-mismatch

ただし対応しているコンパイラは gcc のみで clang ではまだ使用できません。
このオプションを指定できるのは ARMv7A ( TARGET_ARCH_ABI = armeabi-v7a ) の場合だけです。


●外部ライブラリの宣言

システムや外部ライブラリは softfp でコンパイルされているので、
コンパイラに ABI の違いを正しく認識させる必要があります。
NDK r9b 付属のヘッダでは、各関数に下記の宣言が追加されています。
これは softfp 関数の呼び出しであることを意味しています。

__attribute__((pcs("aapcs")))

例えば NDK 付属の math.h ヘッダを見ると下記のように宣言されています。

// math.h より抜粋
double	acos(double) __NDK_FPABI_MATH__;
double	asin(double) __NDK_FPABI_MATH__;

__NDK_FPABI_MATH__ や __NDK_FPABI__ は sys/cdefs.h で定義されています。

// sys/cdefs.h より一部抜粋
#define __NDK_FPABI__ __attribute__((pcs("aapcs")))
#define __NDK_FPABI_MATH__ __NDK_FPABI__


●コンパイル結果と libm の hard_float

実際に hard-float でコンパイルした結果は下記の通り。
ローカル関数呼び出しは直接 d0 レジスタが用いられていることがわかります。

// hard-float
// t_value f_bittof( t_value val )

   vcvt.u32.f64 s0, d0
   vcvt.f64.f32 d0, s0
   bx           lr

↓これまでの softfp でコンパイルすると下記の通り。
64bit 倍精度浮動小数点数は r0/r1 と 2つの 32bit 整数レジスタを使って
受け渡しが行われています。

// softfp
// t_value f_bittof( t_value val )

    vmov            d6, r0, r1
    vcvt.u32.f64    s15, d6
    vcvt.f64.f32    d6, s15
    vmov            r0, r1, d6
    bx              lr

hard-float でコンパイルした場合も、外部のライブラリ呼び出しでは
下記のように r0/r1 レジスタへのへのコピーが発生します。

// hard-float (-lm)

    vmov    r0, r1, d0
    bl      0 <floor>
    vmov    d0, r0, r1

ただし libm は hard-float でコンパイルした static ライブラリが付属しているので、
直接 VFP/NEON レジスタによる呼び出しも出来ます。

LOCAL_CFLAGS += -mhard-float -D_NDK_MATH_NO_SOFTFP=1
LOCAL_LDFLAGS += -Wl,--no-warn-mismatch -lm_hard

-lm (libm) の代わりに -lm_hard (libm_hard) を指定します。
この場合ヘッダの attribute 宣言を外す必要があるので -D_NDK_MATH_NO_SOFTFP=1 も
必要です。
↓ libm 呼び出しでもレジスタ転送が無くなりました。

// hard-float (-lm_hard)

    b       0 <floor>

アプリケーションコードは小さくなりますが libm_hard は static なので
プログラムコード全体は増えます。


● libm 以外のライブラリ呼び出し

浮動小数点数を用いるライブラリは libm 以外にもあります。
例えば OpenGL ES 2.0 関数にも attribute 宣言が追加されています。

// GLES2/gl2.h より
GL_APICALL void         GL_APIENTRY glClearColor (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);

// GLES2/gl2platform.h
#define GL_APICALL  KHRONOS_APICALL

// KHR/khrplatform.h より抜粋
#   define KHRONOS_APICALL __attribute__((visibility("default"))) __NDK_FPABI__

実際に glClearColor() を呼び出してみると、hard-float でも r0-r3 へのコピーが
行われていることがわかります。

// hard-float (debug build)

    vstr    s0, [fp, #-8]
    vstr    s1, [fp, #-12]
    vstr    s2, [fp, #-16]
    vstr    s3, [fp, #-20]	; 0xffffffec
    ldr     r0, [fp, #-8]
    ldr     r1, [fp, #-12]
    ldr     r2, [fp, #-16]
    ldr     r3, [fp, #-20]
    bl      0 <glClearColor>

libm 以外では特に hard-float 版が用意されているわけではないので
softfp 同様の転送が行われます。

実際のアプリケーションでどの程度の差が出るかわかりませんが、
NDK でさらに最適化出来る余地ができました。
もっとも、iOS の方はすでに ARMv8 に移行しつつあります。
Android も 64bit 化が行われればこのような違いを意識する必要は無くなるでしょう。


関連エントリ
Nexus 7 の Ubuntu で ARM の abi softfp と hard-float を比べる