Archives

May 2014 の記事

実際に C/C++ で書かれたゲームを Emscripten で移植してみました。
プラグインなしにブラウザだけ動きます。

↓Windows Firefox
Windows Firefox の画面

ChiRaKS (Emscripten) ここから直接実行できます


● お勧めブラウザと端末

PC      : Chrome / Firefox / IE11 / Safari どれでも OK
Android : Chrome / Firefox どちらか (Krait , Cortex-A9 以降推奨)
iOS     : Safari (iOS7以降: iPhone 5s, iPad Air, iPad mini retina,
                  iOS8以降: iPhone5/5c, iPad4)

未確認ですがおそらく iPad Air, iPad mini retina でも動作します。
(2014/05/30 追記: 動作確認取れました)
(2014/09/19 追記: iOS8 で対応機種が増えました)


● CPU レンダリング

このゲームは CPU だけですべて描画しています。
つまり JavaScript で 3D 演算やライティング、ポリゴンのラスタライズも行っており、
それなりに演算負荷が高いアプリケーションとなっています。

PC 上では Chrome/Firefox/IE11/Safari いずれもフレームレート上限となり
高速に動作しておりレスポンスも十分。

Android 端末でも、最近のハイエンドな機種ではブラウザと思えないほど
快適にプレイできています。


● iOS でも動く

WebGL を使用していないので、これまで動かなかった OS やブラウザでも
走らせられるようになりました。

特に iPhone 5s の Safari はかなり高速に動作しています。
WebGL が使えないものの、Emscripten による iOS アプリの作成も
可能なことがわかりました。

↓iPhone 5s Safari
iOS 7.1 iPhone 5s

なお手持ちの iPad4/iPad3/iPhone5 では動かなかったので注意が必要です。
今のところ iOS で正しく動作するのは 64bit CPU (Apple A7) 搭載端末だけかもしれません。
よって iPad Air, iPad mini retina では動作するのではないかと思われます。
iPad Air / iPad mini retina でも動作することを確認しました。


● 動作テスト

下記は動作テストした環境一覧です。より詳しい表はこちら。

【おすすめ】         Chrome   Firefox   IE11    Safari    独自
----------------------------------------------------------------
Deskttop Windows8     30fps     30fps  30fps       --       --
Deskttop Ubuntu14     30fps     30fps     --       --       --
iPhone 5s iOS7.1       4fps        --     --    30fps       -- 
Kindle HDX 7          30fps     30fps     --       --    30fps (silk)
Tegra Note 7          30fps     30fps     --       --       --
Nexus 10              29fps     29fps     --       --       --
Nexus 7 (2013)        29fps     29fps     --       --       --
HTC J Butterfly     B 29fps   B 29fps     --       --       --
Nexus 7 (2012)      B 29fps   B 29fps     --       --       --
dtab 01             B 29fps   画面乱れ    --       --       --


【非推奨】           Chrome   Firefox   IE11    Safari    独自
----------------------------------------------------------------
HTC EVO 3D          D 10fps   C 20fps     --       --       --
Xperia acro              --   E  8fps     --       --    動作しない
iPad 4              E 1.8fps       --     --   動作しない   --
iPhone 5            E 1.7fps       --     --   動作しない   --
iPad 3              E 1.0fps       --     --   動作しない   --
PS Vita                  --        --     --       --   E 0.1fps

・fps値が大きい方が高速、ただし 30fps が上限
・B = 若干 FPS 値の変動あり。ほぼ 30fps で動作。
・C~D = 変動が大きく安定しない
・E = 低速で動作に支障あり

ページを読み込んだあと、速度が安定するまで多少時間がかかる場合があります。
おそらく JIT Compiler か GC と思われます。

ほぼ 100% CPU 性能に依存するため、
GPU が非力な Tegra 3 (Nexus 7 2012) でも結構良いスコアになっています。


● PlayStation Vita でも一応動く

PS Vita の内蔵ブラウザでも一応動くことがわかりました。
ただし非常に低速で 0.1fps。 ほぼ止まっているに等しい状態です。

↓PS Vita 内蔵ブラウザ
PS Vita

iOS 版 Chrome でも動きますが低速です。
PS Vita ブラウザ や iOS 版 Chrome が極端に遅いのは、
Native コンパイラが組み込まれていないためだと考えられます。


● Native アプリ

ブラウザではあまり速度が出ていない古い Android 端末でも、
こちらの Android Native アプリ版 では十分高速に動作していました。

アプリの方は画面サイズに応じてレンダリング解像度を
160x160 ~ 540x540 まで可変する構造になっているので、
画面が小さい機種ほど正確な比較にならないかもしれません。

速度差は生じているものの、常に 512x512 固定でレンダリングしている
Emscripten 版は、予想以上に頑張っているといえます。


● プログラムについて

ゲーム本体はすべて C/C++ で書かれており、ライブラリを抜いて 100 file ほど。
コンパイル後の js は約 900KB で、Data 込でも合計 1MB 弱です。

スコアの保存は Emscripten の IDBFS を使用しています。
IDBFS の読み書き (FS.syncfs) とレンダリング結果の画面転送だけ
JavaScript で記述しています。

ゲーム中は描画エリア (Canvas) の外部もタッチエリアとして使うことができます。
ゲームの状態を見てタッチイベントの動作を変えています。

ChiRaKS (Emscripten) 解説ページ


● まとめ

WebGL 無しなら iOS でも高速に動作する (iPhone 5s のみ) iOS8 以降は iOS も WebGL に対応しており iPhone 5s 以外でも動きます
・C/C++ で書かれた資産を活用可能
・PC, Android, iOS 全てに対応したアプリ開発へ応用可能


関連エントリ
Emscripten C++/OpenGL ES 2.0 (7) Emscripten の OpenGL API と WebGL
Emscripten C++/OpenGL ES 2.0 (6) Chrome の速度と IE11/Safari
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (5)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧


Emscripten の OpenGL API には 3 つの動作モードがあります。

(1) defaut (WebGL-friendly subset of OpenGL)
(2) -s FULL_ES2=1 (OpenGL ES 2.0 emulation)
(3) -s LEGACY_GL_EMULATION=1 (OpenGL emulation of older desktop and mobile versions)

(1) は WebGL 命令をほぼそのまま OpenGL ES 2.0 API にコンバートしたものです。

(2) は (1) に加えて OpenGL ES 2.0 の足りない機能を補っています。
WebGL は VertexBuffer を経由しない描画をサポートしていないためです。

(3) は WebGL 上で OpenGL 1.x の固定パイプラインをエミュレートしています。
各種レンダーステートにより、動的にシェーダーの生成も行っているようです。

通常は (1) のままで十分ですが、もし正しく描画されない要素がある場合は
(2) を試すことが可能です。
具体的には glVertexAttribPointer() や glDrawElements() の最後の引数に、
直接データのポインタを渡している場合が相当します。
もともと GPU Hardware 的にも望ましくない機能であること、
また Emscripten でのエミュレーションもオーバーヘッドが生じるため
Client Side Arrays は出来る限り使わないことが推奨されています。


● GPU/Browser 毎の Extension

前回のデータを Android 端末含めてさらに集めてみました。
詳細は下記ページへ

WebGL Extensions (Emscripten)

テクスチャ

                                   TS RTS CTex Depth ANI Float Half
W8  Intel HD G 4000   Firefox 29   8K  8K  DXT   Y    Y   Y-L   Y
W8  Intel HD G 4000   Chrome 35    8K  8K  DXT   Y    Y   Y-L   Y-L
W8  Intel HD G 4000   IE 11       16K 16K  DXT   N    Y   Y-L   N
W8  RADEON HD 6750M   Firefox 29  16K 16K  DXT   Y    Y   Y-L   Y
W8  RADEON HD 6750M   Chrome 35   16K 16K  DXT   Y    Y   Y-L   Y-L
W8  RADEON HD 6750M   IE 11       16K 16K  DXT   N    N   Y-L   N
W7  GeForce GTX650    Firefox 29  16K 16K  DXT   Y    Y   Y-L   Y
W7  GeForce GTX650    Chrome 35   16K 16K  DXT   Y    Y   Y-L   Y-L
W7  GeForce GTX650    IE 11       16K 16K  DXT   N    N   Y-L   N
OSX RADEON HD 6750M   Firefox 29  16K 16K  DXT   Y    Y   Y-L   Y
OSX RADEON HD 6750M   Chrome 35    4K 16K  DXT   Y    Y   Y-L   Y-L
OSX RADEON HD 6750M   Safari 7    16K 16K  DXT   Y    Y   Y     N
OSX Intel HD G 4000   Firefox 29   4K  4K  DXT   Y    Y   Y-L   Y
OSX Intel HD G 4000   Chrome 35    4K 16K  DXT   Y    Y   Y-L   Y-L
OSX Intel HD G 4000   Safari 7    16K 16K  DXT   Y    Y   Y     N
U14 RADEON HD 6750M   Firefox 29  16K 16K  DXT   Y    Y   Y-L   Y
U14 RADEON HD 6750M   Chrome 35   16K 16K  DXT   Y    Y   Y-L   Y-L
U14 RADEON R3         Firefox 29  16K 16K  DXT   Y    Y   Y-L   Y
U14 RADEON R3         Chrome 35   16K 16K  DXT   Y    Y   Y-L   Y-L
U14 Intel HD G        Firefox 29   8K  8K  DXT   Y    Y   Y-L   Y

                                   TS RTS CTex Depth ANI Float Half
A42 S80 Adreno 330    Firefox 29   4K  4K  ATC   Y    Y   Y     Y
A42 S80 Adreno 330    Chrome 35    4K  4K  ---   N    Y   Y     Y-L
A44 S4P Adreno 320    Firefox 29   4K  4K  ATC   Y    Y   Y     Y
A44 S4P Adreno 320    Chrome 35    4K  4K  ---   N    Y   Y     Y-L
A44 T4  ULP GeForce72 Firefox 29   4K  4K  DXT   N    Y   N     Y
A44 T4  ULP GeForce72 Chrome 35    4K  4K  DXT   N    Y   N     Y-L
A44 E5  Mali-T604     Firefox 29   8K  8K  ---   Y    N   Y-L   Y
A41 K3  VivanteGC4000 Firefox 29   8K  8K  ---   Y    Y   Y     Y
A44 T3  ULP GeForce12 Firefox 29   2K  2K  DXT   N    Y   N     Y
A44 T3  ULP GeForce12 Chrome 35    2K  2K  DXT   N    Y   N     Y
A42 OM4 PVR SGX540    Firefox 29   2K  2K  PVR   Y    N   Y     Y
A41 R66 Mali-400MP4   Firefox 29   4K  4K  ---   Y    N   N     N
A40 S3  Adreno 220    Firefox 29   4K  4K  ATC   Y    Y   Y     Y
A23 S2  Adreno 205    Firefox 29   4K  4K  ATC   Y    Y   Y     Y

TS   = GL_MAX_TEXTURE_SIZE
RTS  = GL_MAX_RENDERBUFFER_SIZE
CTex = 対応している圧縮テクスチャフォーマット
Depth= WEBGL_depth_texture
ANI  = EXT_texture_filter_anisotropic
Float= OES_texture_float (Y-L は OES_texture_float_linear)
Half = OES_texture_half_float (Y-L は OES_texture_half_float_linear)

W8=Windows8.1, W7=Windows7, OSX=10.9, U14=Ubuntu14.04, A44= Android 4.4

圧縮テクスチャは、Desktop では 100% DXT1~DXT5 (S3TC) が利用可能となっています。
Android は通常の OpenGL ES 2.0 と同じく GPU によって DXT/ATC/PVR とばらばら。
Android が共通でサポートしているはずの ETC1 が列挙されていませんが、
本当に使えないのかどうかは未確認。

DXT に対応しているはずの Vivante GC4000 が未対応となっているのは、
ブラウザの WebGL Layer が GL_COMPRESSED_TEXTURE_FORMSTS ではなく、
GL_EXTENSIONS だけチェックしているからではないかと思われます。

テクスチャ以外

                                   Uniform  In/Out EInt VAO Ins DB sRGB
W8  Intel HD G 4000   Firefox 29   254/ 221 16/10   Y    N   N   N   N
W8  Intel HD G 4000   Chrome 35    254/ 221 16/10   Y    Y   Y   N   N
W8  Intel HD G 4000   IE 11        512/ 512 16/14   N    N   N   N   N
W8  RADEON HD 6750M   Firefox 29   254/ 221 16/10   Y    N   N   N   N
W8  RADEON HD 6750M   Chrome 35    254/ 221 16/10   Y    Y   Y   N   N
W8  RADEON HD 6750M   IE 11        512/ 512 16/14   N    N   N   N   N
W7  GeForce GTX650    Firefox 29   254/ 221 16/10   Y    N   N   N   N
W7  GeForce GTX650    Chrome 35    254/ 221 16/10   Y    Y   Y   N   N
W7  GeForce GTX650    IE 11        512/ 512 16/14   N    N   N   N   N
OSX RADEON HD 6750M   Firefox 29   768/ 768 16/16   Y    Y   N   Y   Y
OSX RADEON HD 6750M   Chrome 35    768/ 768 16/32   Y    Y   Y   Y   N
OSX RADEON HD 6750M   Safari 7     768/ 768 16/32   Y    Y   N   N   N
OSX Intel HD G 4000   Firefox 29  1024/1024 16/16   Y    Y   Y   Y   Y
OSX Intel HD G 4000   Chrome 35   1024/1024 16/15   Y    Y   Y   Y   N
OSX Intel HD G 4000   Safari 7    1024/1024 16/15   Y    Y   N   N   N
U14 RADEON HD 6750M   Firefox 29  4096/4096 29/32   Y    Y   Y   Y   Y
U14 RADEON HD 6750M   Chrome 35   4096/4096 29/32   Y    Y   Y   Y   N
U14 RADEON R3         Firefox 29   256/ 256 29/32   Y    Y   Y   Y   Y
U14 RADEON R3         Chrome 35    256/ 256 29/32   Y    Y   Y   Y   N
U14 Intel HD Graphics Firefox 29  4096/4096 16/32   Y    Y   Y   Y   Y

                                   Uniform  In/Out EInt VAO Ins DB sRGB
A42 S80 Adreno 330    Firefox 29   256/ 224 16/16   Y    Y   N   N   N
A42 S80 Adreno 330    Chrome 35    256/ 224 16/16   Y    Y   N   N   N
A44 S4P Adreno 320    Firefox 29   256/ 224 16/16   Y    Y   Y   Y   N
A44 S4P Adreno 320    Chrome 35    256/ 224 16/16   Y    Y   N   N   N
A44 T4  ULP GeForce72 Firefox 29   280/1024 16/15   N    Y   N   N   N
A44 T4  ULP GeForce72 Chrome 35    280/1024 16/15   N    Y   N   N   N
A44 E5  Mali-T604     Firefox 29  1024/1024 16/15   Y    Y   Y   Y   N
A41 K3  VivanteGC4000 Firefox 29   568/ 568 16/12   Y    N   N   N   N
A44 T3  ULP GeForce12 Firefox 29   256/1024 16/15   N    N   N   N   N
A44 T3  ULP GeForce12 Chrome 35    256/1024 16/15   N    Y   N   N   N
A42 OM4 PVR SGX540    Firefox 29   128/  64  8/ 8   Y    Y   N   N   N
A41 R66 Mali-400MP4   Firefox 29   256/ 256 16/12   N    N   N   N   N
A40 S3  Adreno 220    Firefox 29   251/ 221 16/ 8   Y    Y   N   N   N
A23 S2  Adreno 205    Firefox 29   251/ 222 16  8   Y    N   N   N   N

Uniform = Vsh/Fsh
In/Out  = Attribute/Varying
EInt    = OES_element_index_uint (32bit index)
VAO     = OES_vertex_array_object
Ins     = ANGLE_instanced_array
DB      = WEBGL_draw_buffers
sRGB    = EXT_sRGB

基本的には GPU に依存しますが、さらにブラウザごとにフィルタがかかっている印象です。
Desktop Native では対応してるはずの機能もブラウザによっては無効となっています。
Mobile GPU はもともとこんな感じだったので、それほど驚きはないでしょう。

対応度のばらつきが大きく、最適化以外ではあまり積極的に利用しない方が良いことがわかります。
OpenGL ES 2.0 の要求レベルが低いためで、WebGL 2.0 が OpenGL ES 3.x
ベースになるならもう少し統一されるのではないでしょうか。

動作は遅いものの Firefox + WebGL は Android 2.3 時代の端末でも動作しました。


関連エントリ
Emscripten C++/OpenGL ES 2.0 (6) Chrome の速度と IE11/Safari
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (5)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧


以前のテストで Chrome の場合だけ極端に遅かった原因が判明しました。
Chrome の WebGL getError() 呼び出しが非常に重かったためです。
getError() 無しのビルドでは 60fps を超える速度となっています。
JavaScript が遅いわけではありませんでした。

Windows 8.1 Chrome 35 (60fps)
emscripten_windows_chrome.png

InternetExplorer でも動くようになったので比較してみます。

Core i7-3615QM / Intel HD Graphics 4000
----------------------------------------------------------
Windows 8.1  Firefox 29         60fps 以上
Windows 8.1  Chrome 35          60fps 以上 (glGetError無し)
Windows 8.1  IE 11           34-60fps

いずれも fps 値が大きい方が高速です。
fps に幅があるものは、前回のテストに従い描画負荷を変更しているため。
「34-60fps」なら一番重い 1. で 34fps 前後、一番軽い 3. で 60fps 以上
出ていることを意味しています。

以下別の PC のテスト

Core i7-2720QM / RADEON HD 6750M
----------------------------------------------------------
Mac OSX 10.9  Firefox 29        60fps 以上
Mac OSX 10.9  Chrome 35         60fps 以上 (glGetError無し)
Mac OSX 10.9  Safari 7          60fps 以上

Windows 8.1   Firefox 29        60fps 以上
Windows 8.1   Chrome 35         60fps 以上 (glGetError無し)
Windows 8.1   IE 11             60fps 以上

AMD Athlon 5350 / GeForce GTX 650
----------------------------------------------------------
Windows 7     Firefox 29        60fps 以上
Windows 7     Chrome 35         60fps 以上 (glGetError無し)
Windows 7     IE 11          50-60fps

Tegra 4 Cortex-A15 / ULP GeForce 72 (Tegra Note 7)
----------------------------------------------------------
Android 4.4   Firefox 29        60fps 以上
Android 4.4   Chrome 35         60fps 以上 (glGetError無し)

↓ Chrome 向け 修正の例: Emscripten から出力された *.js の _glGetError() 内にある
return GLctx.getError() を return 0 に変更。

function _glGetError() {
      // First return any GL error generated by the emscripten library_gl.js interop layer.
      if (GL.lastError) {
        var error = GL.lastError;
        GL.lastError = 0/*GL_NO_ERROR*/;
        return error;
      } else { // If there were none, return the GL error from the browser GL context.
        //return GLctx.getError();  // ← 削除
        return  0;
      }
    }

Chrome 以外では glGetError() (WebGL の getError()) があってもなくても
速度には影響がないようです。
IE/Safari 共にかなり高速に動作しています。

これまで IE で動かなかった原因は、オプティマイザが
小さい描画の Index を BYTE Size に変換してしまっていたためでした。

また IE は WebGL Extension の対応度があまり高くないようです。
DXT, float texture はすべてのブラウザが対応していますが、
depth_texture, element_index_uint 未対応は IE だけとなっています。

上の Chrome のキャプチャ画面では depth_texture が用いられています。
↓ IE は depth_texture がないので、Tegra2/3 と同じく ShadowMap に
Color Buffer を使用しています。

Windows 8.1 Internet Explorer 11
emscripten_windows_ie

// IE11
GL_VERSION: WebGL 0.93
GL_RENDERER: Internet Explorer
GL_VENDOR: Microsoft
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL 1.00 (WebGL)

Extension:
GL_WEBGL_compressed_texture_s3tc
GL_OES_texture_float
GL_OES_texture_float_linear
GL_EXT_texture_filter_anisotropic
GL_OES_standard_derivatives

// Firefox 29
GL_VERSION: WebGL 1.0
GL_RENDERER: Mozilla
GL_VENDOR: Mozilla
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL 1.00 (WebGL)

Extension:
GL_EXT_texture_filter_anisotropic
GL_OES_element_index_uint
GL_OES_standard_derivatives
GL_OES_texture_float
GL_OES_texture_float_linear
GL_OES_texture_half_float
GL_WEBGL_compressed_texture_s3tc
GL_WEBGL_depth_texture
GL_WEBGL_lose_context
GL_MOZ_WEBGL_lose_context
GL_MOZ_WEBGL_compressed_texture_s3tc
GL_MOZ_WEBGL_depth_texture

// Chrome
GL_VERSION: WebGL 1.0 (OpenGL ES 2.0 Chromium)
GL_RENDERER: WebKit WebGL
GL_VENDOR: WebKit
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL 1.00 (WebGL)

Extension:
GL_ANGLE_instanced_arrays
GL_EXT_texture_filter_anisotropic
GL_WEBKIT_EXT_texture_filter_anisotropic
GL_OES_element_index_uint
GL_OES_standard_derivatives
GL_OES_texture_float
GL_OES_texture_float_linear
GL_OES_texture_half_float
GL_OES_texture_half_float_linear
GL_OES_vertex_array_object
GL_WEBGL_compressed_texture_s3tc
GL_WEBKIT_WEBGL_compressed_texture_s3tc
GL_WEBGL_depth_texture
GL_WEBKIT_WEBGL_depth_texture
GL_WEBGL_lose_context
GL_WEBKIT_WEBGL_lose_context
GL_WEBGL_debug_renderer_info

// Safari 7
GL_VERSION: WebGL 1.0 (2.1 ATI-1.22.25)
GL_RENDERER: WebKit WebGL
GL_VENDOR: WebKit
GL_SHADING_LANGUAGE_VERSION: OpenGL ES GLSL 1.00 (WebGL)

Extension:
GL_OES_texture_float
GL_OES_standard_derivatives
GL_WEBKIT_EXT_texture_filter_anisotropic
GL_OES_vertex_array_object
GL_OES_element_index_uint
GL_WEBGL_lose_context
GL_WEBKIT_WEBGL_compressed_texture_s3tc
GL_WEBKIT_WEBGL_depth_texture

HOST GPU によって多少違いがあります。
OS 毎、GPU 毎のより詳しいデータは下記に載せました。

WebGL Extensions

次回: Emscripten C++/OpenGL ES 2.0 (7) Emscripten の OpenGL API と WebGL


関連エントリ
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (5)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧


● JSライブラリ

JavaScript で書かれたライブラリと直接リンクすることができます。

// lib.js
mergeInto( LibraryManager.library, {
    print: function(x){
        console.log( x );
    }
});


// main.cpp
extern "C" {
    void print( int x );
}

int main()
{
    print( 12345 );     // これ
    return  0;
}

ビルド手順

emcc main.cpp --js-library lib.js -o main.html

C言語のリンケージに含まれるので、extern 宣言するだけで呼び出せます。

Emscription の library*.js を見ると、標準で用意された関数も
JavaScript で書かれていることがわかります。

利点としては Native な JavaScript の方が自由度が高いことが挙げられます。
ただ C/C++ で書いた場合は asm.js 対応コードに変換してくれるため、
どちらが効率がよく動作できるのかは処理内容に依存すると思われます。


● 引数

// lib.js
mergeInto( LibraryManager.library, {
    print_char : function(x){
        console.log( x );
    },
    print_short : function(x){
        console.log( x );
    },
    print_int : function(x){
        console.log( x );
    },
    print_float : function(x){
        console.log( x );
    },
    print_double : function(x){
        console.log( x );
    },
    print_llong : function(x,y){
        console.log( y * 4294967296 + x );
    },
    print_string : function(x){
        console.log( Pointer_stringify(x) );
    }
});


// main.cpp
extern "C" {
    void print_char( char x );
    void print_short( short x );
    void print_int( int x );
    void print_float( float x );
    void print_double( double x );
    void print_llong( long long x );
    void print_string( const char* x );
};

int main()
{
    print_char( 123 );
    print_short( 12345 );
    print_int( 123456789 );
    print_float( 1.2345f );
    print_double( 1.2345678901234 );
    print_llong( 12345678901234ll );
    print_string( "ABCDEFG" );
    return  0;
}


// output
123
12345
123456789
1.2345000505447388
1.2345678901234
12345678901234
ABCDEFG

ポインタを受け取る場合はメモリアクセスが必要です。
型に応じて HEAP8,HEAP16,HEAP32,HEAP64 を用いるか
setValue()/getValue() 命令を使うことができます。
文字列の Object 変換は Pointer_stringify() で可能。

double に収まらない long long (64bit int) 型は、
2個の整数 (low, high) に分解されていることもわかります。


● 戻り値

// lib.js
mergeInto( LibraryManager.library, {
    ret_llong : function(x,y){
        asm.setTempRet0( 1 );   // high
        return  0>>>0;  // low
    },
});


// main.cpp
#include <stdio.h>

extern "C" {
    long long ret_llong( long long x );
}

int main()
{
    printf( "%lld\n", ret_llong( 0 ) );
    return  0;
}


// output
4294967296

64bit 型は global な戻り値レジスタ tempRet0~ を併用しています。
他の関数呼び出しで破壊される可能性があるので atomic ではない点に注意。

文字列定数など static なポインタを返すのは簡単ではないようです。
メインメモリ空間に配置されていなければならないためで、
static な data 領域はコンパイル時に作られます。
ライブラリ初期化時に HEAP から確保して代用する形になるでしょう。

C言語に返すことが可能な heap memory の確保は、
Module._malloc() / Module._free() を使用します。
C言語の runtime に含まれるので、コンパイル時にこれらの関数への参照がなければ
正しいコードが生成されない点に注意が必要です。

// lib.js
mergeInto( LibraryManager.library, {
    alloc_int : function(size){
        return  Module._malloc( size * 4 );
    },
    dump_int : function(addr, size){
        for( i= 0 ; i< size ; i++ ){
            console.log( Module.HEAP32[(addr>>2) + i] ); // memory アクセス
        }
    },
});


// main.cpp
#include <stdlib.h>

extern "C" {
    int*  alloc_int( int size );
    void  dump_int( const int* buffer, int size );
}

int main()
{
    int*  buffer= alloc_int( 100 );
    for( int i= 0 ; i< 100 ; i++ ){
        buffer[i]= i * i;
    }
    dump_int( buffer, 100 );
    free( buffer );   // -- (1)
    return  0;
}

↑(1) の free が無ければ C言語で malloc()/free() が生成されないため、
JavaScript 側の Module._malloc() は正しい挙動になりません。

例えば下記のようにアロケータを完全に置き換えようとしても、
C言語から参照がないので _malloc/_free がダミーコードになります。
一見正しく動作しますがメモリリークします。

// lib.js
mergeInto( LibraryManager.library, {
    my_alloc : function(byte_size){
        return  Module._malloc( byte_size );
    },
    my_free : function(addr){
        Module._free( addr );
    },
});


// main.cpp
extern "C" {
    void* my_alloc( int byte_size );
    void  my_free( void* addr );
}

int main()
{
    int*  buffer= (int*)my_alloc( 100 * sizeof(int) );
    for( int i= 0 ; i< 100 ; i++ ){
        buffer[i]= i * i;
    }
    my_free( buffer );
    return  0;
}


● メモリマップ

0~7               NULL trap 領域
STATIC_BASE~      data/bss segument (8~)
STACK_BASE~       stack 領域
DYNAMIC_BASE~     heap 領域
~TOTAL_MEMORY-1

NULL アクセスはコンパイラが検出できる場合だけ例外が発生します。
動的な NULL アクセスはデフォルトではエラーになりませんが、
-s SAFE_HEAP=1 を付けることでメモリアクセス時のアドレスチェックが可能です。
その分動作速度は低下します。

stack は前方から後方に向かって確保されています。

文字列定数や static/global に確保したメモリは STATIC エリアに格納します。
初期値を持たない配列も含まれるため、下記のようなケースでは初期化データに
大量の 0 が並ぶことになります。
ファイルサイズが増加するので、HEAP から確保した方が無駄が少なく済むようです。

// main.cpp
int buffer[1024];


/* memory initializer */ allocate([0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

次回: Emscripten C++/OpenGL ES 2.0 (6) Chrome の速度と IE11/Safari


関連エントリ
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧


Emscripten のメモリ空間は固定の配列上に確保されており、
デフォルトの USE_TYPED_ARRAYS=2 では TypedArray によって
任意の型でアクセスできるようになっています。
USE_TYPED_ARRAYS の指定により他のメモリモードを用いることもできます。

関数は JavaScript の function がそのまま用いられており、
呼び出しや引数の受け渡しは普通の JavaScript のコードになっていました。
よって明示的な CallStack は存在せず、引数は Stack を消費しません。
Stack は基本的には auto 変数のために用いられています。

可変引数は特別で、stack 上に格納したのち va_list 渡しになっています。
関数の途中では動的に stack pointer は動かないので、可変引数領域も
prologue 時に確保されているようです。

var i=env.STACKTOP|0;
var c=new global.Int32Array(buffer);

function Ra(){
    var a=0,b=0;
    a=i;        // a が frame pointer
    i=i+16|0;   // stack 確保
    b=a;        // b= local 変数 (address)
    ~
    c[b>>2]=12345;      // intアクセス, bへの代入
    ~
    i=a;        // stack pointer の復帰
    return 0
}

関数呼び出しが通常の function なので、関数ポインタは HEAP や STACK と別空間にあります。
callback などの関数では「関数ポインタ用に設けられた関数のテーブル」
に対する index 値が渡されています。
引数など型に応じてテーブルは複数存在するので、関数ポインタを別の関数型に
cast するようなコードは正しく動かないと公式サイトにも書かれています。

var _=env.abort;
var ua=env._emscripten_set_main_loop_arg;

function mb(a){ // NULL pointer 用
    a=a|0;
    _(0)        // env.abort 呼び出し
}

function fb(){  // main()
    ~

    // emscripten_set_main_loop_arg() の呼び出し
    // 最初の引数に、関数ポインタとして 1 ( Qa[1] ) が渡っている
    ua(1,0,0,1);

    i=a;
    return 0
}

// EMSCRIPTEN_END_FUNCS
var Qa=[mb,gb];  // 関数ポインタ用配列 (void (*)(int) 用)

コード中たまに見かける dynCall() が関数ポインタを使った呼び出しを行っています。

あとから気がついたのですが、emcc に --js-opts 0 を指定すると
もっと読みやすい出力が得られるようです。

function _main() {
 var $0 = 0, $ABCDEFG = 0, $vararg_buffer7 = 0, label = 0, sp = 0;
 sp = STACKTOP;
 STACKTOP = STACKTOP + 16|0;
 $vararg_buffer7 = sp;
 $ABCDEFG = sp + 4|0;
 HEAP32[$vararg_buffer7>>2] = $ABCDEFG;
 (_printf((72|0),($vararg_buffer7|0))|0);
 $0 = (_func01(123)|0);
 HEAP32[$vararg_buffer7>>2] = $0;
 (_printf((8|0),($vararg_buffer7|0))|0);
 HEAP32[$vararg_buffer7>>2] = $ABCDEFG;
 (_printf((16|0),($vararg_buffer7|0))|0);
 _emscripten_set_main_loop_arg((1|0),($ABCDEFG|0),0,1);
 (_puts((96|0))|0);
 HEAP32[$vararg_buffer7>>2] = $ABCDEFG;
 (_printf((24|0),($vararg_buffer7|0))|0);
 STACKTOP = sp;
 return 0;
}

可変引数領域が stack 上に確保されていることがわかります。


JavaScript のコードとして動くため、block するような I/O 命令は
本来うまく動かないことになります。
callback が発生しないためで、一旦 event driven のための
メインループに return しなければ完了通知を受け取ることができません。

Emscripten は memory 上に仮想的な File System を作っているため、
ファイルアクセスでは何も問題が起こらず C言語コードのまま動きます。
互換性が高く、アプリケーションを比較的簡単に移植できる要因として
この仮想 FileSystem の果たす役割は非常に大きいのではないかと思います。


emscripten_set_main_loop() は一見その場で block しているように
見えますが、見た目とは全く異なる挙動をしていました。

static void ems_loop( void* arg )
{
   ...
}

int main()
{
    Initialize();
    emscripten_set_main_loop( ems_loop, 0, true );
    Finalize();
    return  0;
}

emscripten_set_main_loop() の最後の引数が ture の場合無限 Loop をシミュレートします。
実行はその場で停止し、以後毎フレーム ems_loop() が呼ばれます。

これだと event loop に return していないように見えるのですが、
JavaScript のコード上では emscripten_set_main_loop() の最後で例外を throw していました。
つまり emscripten_set_main_loop() が呼ばれた時点で main() 関数
そのものが終了してしまいます。
emscripten_set_main_loop() 以後の Finalize() 等のコードが呼ばれることはありません。

では下記のようなコードはどうでしょうか。

class AppModule {
public:
};

static void ems_loop( void* arg )
{
    AppModule* app= reinterpret_cast<AppModule*>( arg );
    ~
}

int main()
{
    AppModule  app;
    emscripten_set_main_loop_arg( ems_loop, &app, 0, true );
    return  0;
}

例外はあくまで JavaScript の throw なので、main() が終了しても
AppModule の destructor が呼ばれるわけではありません。

また main() が終了するなら ems_loop() で stack 上の app に対する
不正アクセスが行われてしまうようにも見えますが、こちらも大丈夫でした。
main() の epilogue を通らないので、中で確保した stack 領域は
そのまま保持されたままとなっているようです。


以上より、関数呼び出しは仮想化されていないので、
現在の実行コンテキストを保存したまま一時的に Event Loop に戻るような処理は
やはり簡単には実現できないのではないかと思われます。
Thread で block することを想定した同期 I/O まわりなど。

例えば socket を使った通信は

connect( sock, &addr, sizeof(addr) );
recv( sock, buffer, size, 0 );

close( sock );

connect() が必ず EINPROGRESS を返すので、継続するには Event Loop に
戻らなければならないようです。
↓こんな感じで使えないかと思ったのですが簡単にはいきませんでした。

connect( sock, &addr, sizeof(addr) );
emscripten_push_main_loop_blocker( recv_loop, sock );

close( sock );

もし CallStack など関数呼び出しも仮想化されているならば、
自前で Context 切換するなど何らかの手段が取れるのではないかと思ったからです。


関連エントリ
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧


プロジェクトが巨大な場合メモリ不足で起動できないことがあります。
Android のブラウザではロード中のまま止まってしまうので、
読み込みが遅いだけなのかエラーなのかわからない場合があります。
原因を調べるにはリモートデバッガが便利です。

MDN Android 版 Firefox のリモートデバッグ

(1) Android Firefox
・Menu → 設定 → デベロッパーツール → リモートデバッグ を ON

(2) HOST PC
・adb で接続したあと下記のコマンドを実行

adb forward tcp:6000 tcp:6000

(3) HOST PC Firefox
・Menu → 開発ツール → 開発ツールを表示 → 開発ツールパネルの左上の歯車アイコン → 「リモートデバッガを有効」にチェックを入れる (2014/05/26 追加)
・Menu → 開発ツール → 接続 → [接続] ボタン

(4) Android
・接続許可を求めるダイアログが出るので [OK]

(5) HOST PC Firefox
・どのタブに接続するか選択

↓エラーの例 (Android Firefox)

Successfully compiled asm.js code (total compilation time 3120ms; stored in cache) flview_jsr.js
uncaught exception: out of memory


● Library 化

bc file は merge できるので library として利用できます。

emcc src1.cpp -o src1.bc
emcc src2.cpp -o src2.bc
emcc src1.bc src1.bc -o libfile.bc

Lib 利用時

emcc file.cpp libfile.bc -o file.html

拡張子の対応付

PC          Emscripten
---------------------------------
.exe        .js/.html    実行可能
.obj/.o     .bc          object
.lib/.a     .bc          library

コマンドの対応付け

PC           Emscripten
---------------------------------
cl/clang     emcc -o *.bc
link/ld      emcc -o *.js/*.html
lib/ar       emcc -o *.bc

Emscripten はリンク時に未定義シンボルがあっても実行できます。
未定義の関数を呼び出さない限り動作に支障はないようです。


● データ型とサイズ

32bit, ILP32 です。

bool=1/1       char=1/1      short=2/2    int=4/4     long=4/4
long long=8/8  float=4/4     double=8/8
void*=4/4      intptr_t=4/4  size_t=4/4   off_t=4/4   wchar_t=4/4

(byte size / alignment size)


● EGL と OpenGL ES 2.0

3D の描画は EGL + OpenGL ES 2.0 の組み合わせを用います。
他にも初期化手順を省いてくれる便利なライブラリがいろいろあるようです。
とりあえずできるだけ低レベルと思われる手順を使っています。

enum {
    WINDOW_DEFAULT_WIDTH   =  960,
    WINDOW_DEFAULT_HEIGHT  =  640,
};
emscripten_set_canvas_size( WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT );

Canvas サイズを指定したら、あとは EGL の初期化を行うだけ。

EGLDisplay  egl_display= eglGetDisplay( EGL_DEFAULT_DISPLAY );
EGLint  ver0, ver1;
eglInitialize( egl_display, &ver0, &ver1 );
eglBindAPI( EGL_OPENGL_ES_API );


● X11

Sample コードを見ていると Xlib の API も使えることがわかります。

XSetWindowAttributes  swa;
Display*  disp= XOpenDisplay( NULL );
Window  root= DefaultRootWindow( disp );
Window  win= XCreateWindow(
    disp, root, 0,0,
    WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT, 0,
    CopyFromParent, InputOutput, CopyFromParent, 0, &swa );

内部コードを確認すると、XCreateWindow() で canvas size の設定を行っているだけのようです。
それ以外のコードは特に意味はありませんでした。
emscripten_set_canvas_size() を指定しているなら必要無いでしょう。


● 入力イベント

Xlib の Event API は実装されていないようなので、
Emscripten の callback を利用しています。

#include <emscripten.h>
#include <emscripten/html5.h>


static EM_BOOL KeyCallback( int event_type, const EmscriptenKeyboardEvent* event, void* user_data )
{
    AppModule*  _This= reinterpret_cast<AppModule*>(user_data);
    switch( event_type ){
    case EMSCRIPTEN_EVENT_KEYDOWN:
        _This->SendKeyEvent( event->keyCode, event::ACTION_DOWN );
        break;
    case EMSCRIPTEN_EVENT_KEYUP:
        _This->SendKeyEvent( event->keyCode, event::ACTION_UP );
        break;
    }
    return  TRUE;
}


static EM_BOOL MouseCallback( int event_type, const EmscriptenMouseEvent* event, void* user_data )
{
    AppModule*  _This= reinterpret_cast<AppModule*>(user_data);
    int mouse_pos_x= event->canvasX;
    int mouse_pos_y= event->canvasY;
    ~
    return  TRUE;
}

void AppModule::Initialize()
{
    emscripten_set_mousedown_callback( NULL, this, TRUE, MouseCallback );
    emscripten_set_mouseup_callback(   NULL, this, TRUE, MouseCallback );
    emscripten_set_mousemove_callback( NULL, this, TRUE, MouseCallback );
    emscripten_set_wheel_callback(  NULL, this, TRUE, WheelCallback );

    emscripten_set_keydown_callback( NULL, this, TRUE, KeyCallback );
    emscripten_set_keyup_callback(   NULL, this, TRUE, KeyCallback );

    emscripten_set_touchstart_callback(  NULL, this, TRUE, TouchCallback );
    emscripten_set_touchend_callback(    NULL, this, TRUE, TouchCallback );
    emscripten_set_touchmove_callback(   NULL, this, TRUE, TouchCallback );

    emscripten_set_gamepadconnected_callback( this, TRUE, GamepadCallback );
    emscripten_set_gamepaddisconnected_callback( this, TRUE, GamepadCallback );
}

Mouse, Wheel, Keyboard, Gamepad は PC 上で確認。
Android のブラウザ上では Touch Event が有効でした。

Firefox の場合は EmscriptenMouseEvent の buttons に押されているボタンの
状態が入るので、MouseMove でも buttons を見るだけで判断できます。
Chrome では buttons が常に 0 だったので、ボタンが押されているかどうかは
MouseDown/MouseUp 時に自分で保持しておく必要があります。


● 残っている問題等

pthread が動作していないため、それに関連する Module をポーリングなど
他の手段に置き換える必要が生じています。
スレッドを使用していないため Atomic 系のコードも未実装です。

サウンド API は OpenAL が利用できるので、
OSX/iOS や Linux とコードを共通化できそうです。
ビルドは通っているのですがスレッドの関係もありまだテストできていません。
Socket API も実験中です。

次回: Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)


関連エントリ
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧


前回 Cortex-A15 の動作だけ極端に低速で少々疑問が残る結果となりました。
RAM 1MB の端末でも動くようになったのでさらに調べてみました。

Tegra4 Cortex-A15   Firefox     60fps 以上  (Tegra Note 7)
Tegra3 Cortex-A9    Firefox     20~47fps   (Nexus 7 2012)

今回の結果を見る限り、Cortex-A15 でも Krait 並に高速に動作していることがわかります。
また Cortex-A9 も予想よりも速い印象です。

上の両機種とも画面解像度が低く 1280x800 dot です。
前回速度が出なかった Nexus 10 は 2560x1600 と非常に解像度が高いので、
ブラウザが何らかの解像度に応じた CPU 転送を行っている可能性もあります。
なお内部のレンダリング解像度は 960x640 固定です。

以下詳細データ

1. 960x640 63万Tri+Animation

Tegra4 Cortex-A15   Firefox     60fps 以上   (Tegra Note 7)
Tegra3 Cortex-A9    Firefox     20fps 前後   (Nexus 7 2012)

2. 960x640 63万Triangles

Tegra4 Cortex-A15   Firefox     60fps 以上   (Tegra Note 7)
Tegra3 Cortex-A9    Firefox     25fps 前後   (Nexus 7 2012)

3. 960x640 6万Triangles

Tegra4 Cortex-A15   Firefox     60fps 以上   (Tegra Note 7)
Tegra4 Cortex-A15   Chrome      12fps 以上   (Tegra Note 7)
Tegra3 Cortex-A9    Firefox     47fps 前後   (Nexus 7 2012)
Tegra3 Cortex-A9    Chrome       8fps 前後   (Nexus 7 2012)
K3V2   Cortex-A9    Firefox       描画崩れ   (dtab)
K3V2   Cortex-A9    Chrome        動作せず   (dtab)

・fps が大きい方が高速

Tegra3 は JavaScript の動作にはまだ余裕があるものの、
明らかに GPU 性能が足りずに速度が落ちているようです。

K3V2 は Vivante GC4000 による描画が崩れて正しく表示されませんでした。
ログを見る限りアプリケーション自体は動いているようです。


● Emscripten のビルド

Emscripten のビルドは gcc/clang の代わりに emcc コマンドを用います。
obj (.o) の代わりが .bc (bitcode) です。

emcc file1.cpp -o file1.bc

実際に実行できるよう JavaScript に変換 (ld/link) するには下記のようにします。

emcc file1.bc -o file1.js

または

emcc file1.bc -o file1.html

実行形式は js だけの場合と、html 含めたブラウザ向けの 2種類あります。

ローカルで実行する場合は node コマンド (Node.js) を使うことが可能で、
この場合は js だけで OK です。
WebGL の描画や preload 指定した追加ファイルを読み込む場合は html で出力し、
Web サーバー経由でブラウザから読み込みます。

実行形式   実行環境   バンドル
-----------------------------------------------------
 .js       node       --embed-file
 .html     Browser    --emded-file or --preload-file


● 実行手順の例

ローカルで実行する場合 (node)

emcc file1.cpp -o file1.bc
emcc file2.cpp -o file2.bc
emcc file1.bc file2.bc -o file.js

node file.js

ローカルサーバーを経由する場合 (Browser)

emcc file1.cpp -o file1.bc
emcc file2.cpp -o file2.bc
emcc file1.bc file2.bc -o file.html

python -m SimpleHTTPServer 8888

ブラウザで http://localhost:8888/file.html を開く


● ファイルシステム

Emscripten の実行環境は仮想 FileSystem を持っており、
内部的に Unix FileSystem をエミュレートします。
実行時に読み込むファイル群は、一旦この仮想 FileSystem 内に読み込んでおく必要があります。

必要なファイルを実行ファイルに埋め込む方法は下記 2種類あります。

--preload-file    外部ファイル(*.data)にアーカイブし起動時に読み込む
--embed-file      js に埋め込む

--embed-file の例

emcc file1.cpp -o file1.bc
emcc file2.cpp -o file2.bc
emcc file1.bc file2.bc -o file.js --embed-file root/shaders

↑この場合 root/shaders フォルダ以下のファイルをすべて js の中に埋め込みます。
仮想 FileSystem 内のパスも /root/shaders/* とみなします。

もし実際のデータ置き場と、仮想 FileSystem 内のパスが異なる場合は
'@' マークを使います。

emcc file1.cpp -o file1.bc
emcc file2.cpp -o file2.bc
emcc file1.bc file2.bc -o file.js --embed-file root/shaders@/data/shaders

↑ '@' 以降が内部の仮想パスの指定になります。
実際のデータは root/shaders 以下に存在しており、
実行時は /data/shaders にあるとみなされます。

--embed-file は間違いが少なく node でも利用できて便利なのですが、
ファイル数が増えるとビルドが極端に遅くなる問題があります。
バンドルが 200MB くらいあるプロジェクトではコマンドが返ってきませんでした。

--preload-file の場合はデータを外部ファイルにまとめるため、
データ容量が増えても比較的時間がかからずにビルドできます。

emcc file1.cpp -o file1.bc
emcc file2.cpp -o file2.bc
emcc file1.bc file2.bc -o file.html --embed-file root/shaders@/data/shaders

python -m SimpleHTTPServer 8888

ブラウザで http://localhost:8888/file.html を開く

どちらにせよ、これらの方法はブラウザを開いたあと長いロードが入るので
その間何もできなくなります。
現実的には、動的な逐次読み込みを実現する必要があるでしょう。

またメモリ上の仮想 FileSystem はデータの保存ができません。
必要ならば Indexed Databased を FileSystem に mount して
利用することができるようです。


● Inline JavaScript

仮想マシンが JavaScript となる Emscripten では、Inline Assembler ではなく
Inline JavaScript を利用することができます。

#include <emscripten.h>


int main()
{
    EM_ASM(
       ~
       JavaScript code
    );

    return  0;
}

EM_ASM() の中に JavaScript code を記述します。
仮想ファイルシステムへの動的な読み込みなど色々コードを埋め込むことができるようです。


● Main Loop

Emscripten では callback を使って Game の Main Loop の代わりにします。
例えば AppModule::Render() を毎フレーム呼び出したい場合は下記のような使い方になります。

#include <emscripten.h>

class AppModule {
public:
   void Render();
};

// 毎フレーム呼ばれる
static void ems_loop( void* arg )
{
   AppModule*  iApp= reinterpret_cast<AppModule>( arg );
   iApp->Render();
}

int main()
{
    AppModule* iApp= new AppModule();

    // callback の登録
    emscripten_set_main_loop_arg( ems_loop, iApp, 0, TRUE );

    delete iApp;
    return  0;
}


● HeapSize

実行環境のメモリが足りずに、アプリケーションを起動できないことがあります。
下記のようにアプリケーションが使用する HEAP SIZE を指定することができます。

emcc  file.cpp -o file.bc
emcc  file.bc -o file.html  -s TOTAL_MEMORY=134217728

あまり大きな値を指定し過ぎると、今度はブラウザ側のメモリが足りず
Out of Memory でロードが止まってしまうことがあるようです。


● まとめ

・Android Cortex-A15/A9 + Firefox は遅くなかった
・Emscripten のビルドと実行手順
・仮想ファイルシステムとバンドル
・MainLoop と HeapSize

次回 : Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)


関連エントリ
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧



C++ で開発していた OpenGL/OpenGL ES の Project を Emscripten でビルドしてみました。
Native Code 向けのプログラムがそのままブラウザ内で動いています。

・Windows Firefox (29.0.1)

Emscripten Windows

・Android Firefox (29.0.1)

Emscripten AndroidEmscripten Android + animation

Chrome では 7 fps 程度なので asm.js (Firefox) の効果は絶大でした。
Android でも十分な速度で動いています。

追記 2014/05/24: その後 Chrome でも 60fps 以上出るようになりました。詳しくはこちら

Windows 8.1 Firefox 29    60fps 以上   (Core i7-3615QM)
FireOS 3.0  Firefox 29    60fps 以上   (Kindle Fire HDX7 MSM8974)
Android 4.4 Firefox 29    34fps 前後   (Nexus 7 2013 APQ8064)

Windows 8.1 Chrome 34      7fps 前後   (Core i7-3615QM)
FireOS 3.0  Silk           3fps 前後   (Kindle Fire HDX7 MSM8974)
Android 4.4 Chrome 34      3fps 前後   (Nexus 7 2013 APQ8064)

・fps の値が大きい方が高速
・FireOS 3.0 = Android 4.2.2 相当

もともと OpenGL ES 2.0 / EGL に対応していたこともあり、
修正箇所はごく僅かで済んでいます。
使えなかった API は pthread だけで、
追加したのは Mouse/Touch/Keyboard などの Event 周りです。
Network (socket系) とサウンドは未着手。
ソースコード数は 600 file, 26万行ほど。

Native を想定して書いたコードが、拍子抜けするほどあっさりと
ブラウザ内で動いています。

以前から何度か NativeClient (NaCl) への対応化にも取り組んでいたのですが、
あまり本質的でない部分で躓いていました。
同期型 FileIO や Thread 周りといった API 制限だけでなく、
gcc (4.4) が古くて C++11 のコードが通らなかったためです。
その後確認したところ、ARM や pnacl では比較的新しいコンパイラに
置き換わってるようです。

今回使用した Emscripten では何も問題はなく Native 向け API もそのままです。
詳しい説明は次回。


● Emscripten

emscripten wiki

Android では Java, iOS では Objective-C が使われているように、
プラットフォームによってアプリケーション記述言語はまちまちです。
ただ多くの場合 C/C++ も併用できることが多く、
C/C++ 言語 + OpenGL ES 2.0 の組み合わせは、
移植性の高い共通言語&API としての役割も担っています。
主にゲームで。

Web Browser も OpenGL ES 2.0 (WebGL) に対応しており、
NativeClient (NaCl) や Emscripten といった C/C++ のコードを
走らせる仕組みも登場しています。
C/C++ が使えるようになったことで、同じソースコードのまま
さらに多くの環境にアプリケーションを移植できるようになりました。

NaCl はコンパイルしたバイナリをブラウザ内で走らせますが、
Emscripten は仮想マシンとして JavaScript を利用しています。
ポインタを駆使した C Native なコードが JavaScript に変換されると聞いても
少々奇妙な印象を受けるかもしれません。
しかしながら JavaScript の性能向上は著しく、モバイル含めて
十分な速度で動いているこれらの結果にはやはり驚きを隠せません。


● 速度比較

1. 960x640 63万Tri/191万Vertices, 一部 Bone Animation あり

CPU                  GPU            OS           Browser      fps
------------------------------------------------------------------------
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Win+OpenGL  100fps 前後
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Firefox      60fps 以上*1
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Chrome        7fps 前後
BayTrail-D J1900     Intel HD       Ubuntu14.04  X11+OpenGL    8fps 前後
BayTrail-D J1900     Intel HD       Ubuntu14.04  Firefox       5fps 前後
BayTrail-D J1900     Intel HD       Ubuntu14.04  Chrome         動作せず
Kabini Athlon5350    GeForce GTX650 Ubuntu14.04  X11+OpenGL  880fps 前後
Kabini Athlon5350    GeForce GTX650 Ubuntu14.04  Firefox      30fps 前後
Kabini Athlon5350    GeForce GTX650 Ubuntu14.04  Chrome        4fps 前後
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   NDK+ES3.0    50fps 前後
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   Firefox      34fps 前後
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   Chrome        3fps 前後
Exynos5D Cortex-A15  Mali-T604      Android4.4   NDK+ES3.0    60fps 前後
Exynos5D Cortex-A15  Mali-T604      Android4.4   Firefox       8fps 前後
Exynos5D Cortex-A15  Mali-T604      Android4.4   Chrome         動作せず
MSM8974 Krait 400    Adreno 330     FireOS 3.0   Firefox      60fps 以上*1
MSM8974 Krait 400    Adreno 330     FireOS 3.0   Silk           fps 前後

・fps の値が大きい方が高速
・*1 : VSyncの上限
・Firefox 29, Chrome 34
・FireOS 3.0 = Android 4.2.2 相当
・APQ8064 = Nexus 7(2013), Exynos5D = Nexus 10, MSM8974 = Kindle Fire HDX7

2. 960x640 63万Tri/191万Vertices

CPU                  GPU            OS           Browser      fps
------------------------------------------------------------------------
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Win+OpenGL  110fps 前後
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Firefox      60fps 以上*1
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Chrome       10fps 前後
BayTrail-D J1900     Intel HD       Ubuntu14.04  X11+OpenGL    8fps 前後
BayTrail-D J1900     Intel HD       Ubuntu14.04  Firefox       5fps 前後
BayTrail-D J1900     Intel HD       Ubuntu14.04  Chrome         動作せず
Kabini Athlon 5350   GeForce GTX650 Ubuntu14.04  X11+OpenGL  900fps 前後
Kabini Athlon 5350   GeForce GTX650 Ubuntu14.04  Firefox      30fps 前後
Kabini Athlon 5350   GeForce GTX650 Ubuntu14.04  Chrome        6fps 前後
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   NDK+ES3.0    50fps 前後
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   Firefox      30fps 前後
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   Chrome        5fps 前後
Exynos5D Cortex-A15  Mali-T604      Android4.4   NDK+ES3.0    60fps 以上*1
Exynos5D Cortex-A15  Mali-T604      Android4.4   Firefox       8fps 前後
Exynos5D Cortex-A15  Mali-T604      Android4.4   Chrome         動作せず
MSM8974 Krait 400    Adreno 330     FireOS 3.0   Firefox      60fps 以上*1
MSM8974 Krait 400    Adreno 330     FireOS 3.0   Silk          5fps 前後

・fps の値が大きい方が高速

3. 960x640 6万Tri/19万Vertices

CPU                  GPU            OS           Browser      fps
------------------------------------------------------------------------
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Win+OpenGL  520fps 前後
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Firefox      60fps 以上*1
Ivy Core i7-3615QM   Intel HD 4000  Win8.1 x64   Chrome       15fps 前後
BayTrail-D J1900     Intel HD       Ubuntu14.04  X11+OpenGL   60fps 以上*1
BayTrail-D J1900     Intel HD       Ubuntu14.04  Firefox       5fps 前後
BayTrail-D J1900     Intel HD       Ubuntu14.04  Chrome         動作せず
Kabini Athlon 5350   GeForce GTX650 Ubuntu14.04  X11+OpenGL 1020fps 前後
Kabini Athlon 5350   GeForce GTX650 Ubuntu14.04  Firefox      38fps 前後
Kabini Athlon 5350   GeForce GTX650 Ubuntu14.04  Chrome        9fps 前後
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   NDK+ES3.0    60fps 以上*1
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   Firefox      60fps 以上*1
APQ8064 Krait 1.5GHz Adreno 320     Android4.4   Chrome        8fps 前後
Exynos5D Cortex-A15  Mali-T604      Android4.4   NDK+ES3.0    60fps 以上*1
Exynos5D Cortex-A15  Mali-T604      Android4.4   Firefox       9fps 前後
Exynos5D Cortex-A15  Mali-T604      Android4.4   Chrome         動作せず
MSM8974 Krait 400    Adreno 330     FireOS 3.0   Firefox      60fps 以上*1
MSM8974 Krait 400    Adreno 330     FireOS 3.0   Silk          8fps 前後

・fps の値が大きい方が高速

描画負荷を変えても速度が大きく変わらないものは CPU (JavaScript) 側の
限界と思われます。
Chrome, BayTrail-D, Kabini, Exynos 5D(Nexus10) が相当します。
また Native との差が少ないものは GPU 側も上限に近いことを意味しています。

Animation ありの場合骨の計算分 JS の負担が増えます。(頂点blendは GPU)
GPU に余裕があるのに 1. と 2. で大きく差が生じる場合は、
JavaScript の演算能力にも余裕がないことを示しています。

BayTrail-D は描画と CPU (JS) 両方共限界に達しているようです。
Native (X11+OpenGL) の 1./2. のテストでは、画面拡大時に速度が上がり、
オブジェクトが画面に全体が収まる場合は Firefox に近い速度まで下がります。
おそらく頂点性能が足りていないと思われます。

また同時に BayTraild の Firefox では拡大しても速度が上がらず一定なので、
JavaScript の動作速度も制限となっていることがわかります。
倍精度浮動小数点演算が苦手なことも影響しているかもしれません。

Kabini は外付けの GeForce がオーバースペック過ぎて CPU が追いついていません。
CPU (JS) 自体は BayTrail よりはかなり速いようです。

Android の Native (NDK+ES) は Fullscreen 動作となり、1920x1200, 2560x1600 で
レンダリングしているため他と条件が異なっています。

JavaScript の動作は Cortex-A15 よりも Krait/Krait 400 の方が高速でした。
Krait (APQ8064) は 3. のテストで制限がかかっていないため、
JavaScript 自体は余裕があるものの GPU で落ちているようです。
GPU の演算能力に余裕がある Krait 400 (MSM8974) はどのテストでも 60fps
超えています。

Desktop の Kabini よりも Krait/Krait 400 の方が JavaScript の
動作速度が速いこともわかります。

Cortex-A15 の速度が全然出ていませんが、VFP Benchmark の結果でも
倍精度演算は Krait の方が良い結果を出しています。
ただそれ以上に大きく差が開いているので、他にも何らかの要因があるのでは
ないかと思われます。

同じ Cortex-A15 のTegra Note 7 (Tegra4) でも試したかったのですが
RAM 容量が足りず動きませんでした。
今のところ Android + Firefox の組み合わせは RAM 2GB でぎりぎりです。
まだビルドを通したばかりでプロジェクトが巨大なせいもあります。


● まとめ

・Emscripten で C/C++ と OpenGL ES 2.0 のコードがブラウザ上でそのまま走る
・Firefox なら速度も速い (asm.js のおかげ)
・Android でも Firefox + Krait は高速動作 (Cortex-A15 が遅い原因は不明)

追記 (2014/05/20) : Tegra4 (Cortex-A15) では高速に動作することを確認しました

追記 (2014/05/24) : Chrome で速度が遅かった原因がわかりました。修正により 60fps 以上出ています。

続きます:「Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)」


関連エントリ
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧



低消費電力の Desktop PC 向け CPU として Intel からは BeyTrail-D、
AMD からは Kabini が登場しています。
BayTrail-D Celeron J1900 と Kabini Athlon 5350 は、
どちらも 4 core CPU + マザーボードでちょうど 1万円。
価格帯もスペックも良く似ているので比べてみました。

                    BayTrail-D              Kabini
                    Celeron J1900         Athlon 5350
-----------------------------------------------------
CPU core             Silvermont             Jaguar
CPU cores                4                    4
CPU clock            2.0-2.41GHz           2.05GHz
RAM                DDR3-1333 dual         DDR3-1600
MEM BW                21.3GB/s             12.8GB/s
L2                      2MB                  2MB
SSE                    SSE4.2               SSE4.2
AVX                      --                  AVX
AES                      --                 AES-NI
CPU SP              24 fop/clock         32 fop/clock
CPU SP FLOPS        57.81 GFLOPS          65.6 GFLOPS
CPU DP               6 fop/clock         12 fop/clock
CPU DP FLOPS        14.46 GFLOPS          24.6 GFLOPS
GPU core         Intel HD Graphics 3G    RADEON R3 (GCN)
GPU clock            688-854MHz             600MHz
GPU SP              64 fop/clock         256 fop/clock
GPU SP FLOPS         54.7 GFLOPS         153.6 GFLOPS
OpenGL Windows       OpenGL 4.0           OpenGL 4.3
OpenGL Linux         OpenGL 3.3           OpenGL 4.3
TDP                     10W                  25W

Intel Celeron Processor J1900
AMD Athlon

浮動小数点演算能力

VFP Benchmark     Celeron J1900    Athlon 5350
-------------------------------------------------
SingleT SP max:   14.477 GFLOPS    15.943 GFLOPS
SingleT DP max:    3.619 GFLOPS     6.127 GFLOPS
MultiT  SP max:   57.902 GFLOPS    63.737 GFLOPS
MultiT  DP max:   14.471 GFLOPS    24.504 GFLOPS

・値が大きいほうが高速

前前回の予想通り浮動小数点演算能力は Jaguar (Kabini/Athlon) の方が高くなっています。
J1900 (BayTrail) は動作クロックの高さで補っている形です。

演算能力/clock    Single FP   Double FP
-----------------------------------------------
Celeron J1900         6          1.5
Athlon 5350           8            3

再測定して気が付きましたが、以前のエントリで J1900 の倍精度演算の
性能評価が間違っていました。下記訂正しましたので申し訳ありませんでした。
Atom Bay Trail の浮動小数点演算能力


前回のコンパイル速度比較を Kabini でも試してみました。
驚くほど拮抗しています。

flatlib3 Linux       clock  core  RAM   OS   arch compiler    time sec
-------------------------------------------------------------------------
Kabini Athlon 5350  2.05GHz  x4   8GB  14.04  x64  clang-3.5    54.8
BayTrail-D J1900    2.41GHz  x4   8GB  14.04  x64  clang-3.5    54.6

・time が小さい方が速い

テストした環境は下記の通り。

Test 環境
Celeron J1900 (Q1900B-ITX DDR3-1333 dual   8GB  21.3GB/s)
Athlon 5350   (AM1l       DDR3-1333 single 8GB  10.7GB/s)

Kabini は DDR3-1600 が使えますが、テスト環境では手持ちの DDR3-1333 を使用しています。
本来の能力よりもスコアが低くなっていると考えられますので予めご了承ください。


AES 変換テスト

AES CTR 599MByte  Celeron J1900    Athlon 5350
-------------------------------------------------
Table1               18.708          18.964
Table2               15.409          14.600
Table3               14.902          12.374
AES-NI                   --           4.238

・単位は秒、値が小さい方が速い, Single Thread

AES-NI が使えるため Jaguar (Athlon/Kabini) の方が高速です。
同じアルゴリズム同士でもわずかに Jaguar の方が速いようです。
メモリ速度、CPU の動作クロックともに J1900 (BayTrail) の方が上なので、
Jaguar (Athlon/Kabini) は Core 性能そのものが高いのだと思われます。


簡単なシーンのレンダリング速度の比較

Ubuntu 14.04   GPU                    API            fps
----------------------------------------------------------
Celeron J1900  Intel HD Graphics 3G   OpenGL 3.3     17fps
Athlon 5350    RADEON R3              OpenGL 4.3     89fps

・fps が大きい方が速い

比べるまでもなく内蔵 GPU の性能では圧倒的な差があります。
RADEON は OpenGL で新しい API が使える点もポイントが高いです。
J1900 の GPU は使用していて少々性能不足を感じます。


CPU core の基本性能は Jaguar (Athlon/Kabini) の方が上。
メモリ速度や動作クロックを加味すると両者かなり近い性能になっています。

GPU は当然 RADEON (Athlon/Kabini) の方が速く、
性能差には数倍の開きがあります。

● BayTrail-D (Celeron J1900/Silvermont)
・消費電力が低くファンレス
・メモリ帯域が広い

● Kabini (Athlon 5350/Jaguar)
・浮動小数点演算能力が高い
・AVX/AES 命令に対応している
・GPU 性能が非常に高い


関連エントリ
コンパイル時間の比較 BayTrail
Atom Bay Trail の浮動小数点演算能力


BayTrail-D Celeron J1900 の PC でコンパイル時間を比べてみました。

flatlib3 Linux       clock  core  RAM   OS   arch compiler   time sec
-------------------------------------------------------------------------
Raspberry Pi ARM11   0.7GHz  x1 0.5GB wheezy arm6 gcc-4.7    2276.8 (38m)
Atom Z540            1.9GHz  x1   2GB 14.04  x86  clang-3.5   446.9  (7m)
Atom Z540            1.9GHz  x1   2GB 14.04  x86  gcc-4.8     369.4  (6m)
Tegra3 Cortex-A9     1.2GHz  x4   1GB 13.04  arm7 gcc-4.8     247.4  (4m)
BayTrail-D J1900     2.0GHz  x4   8GB 14.04  x64  gcc-4.8      72.1
BayTrail-D J1900     2.0GHz  x4   8GB 14.04  x64  clang-3.5    53.2
Core i7-2720QM Sandy 2.2GHz  x4  16GB 14.04  x64  gcc-4.8      26.6
Core i7-2720QM Sandy 2.2GHz  x4  16GB 14.04  x64  clang-3.5    20.2

・time が少ない方が高速

Linux 向け build は ARM 上でも走ります。
4core BayTrail で 1分弱。
Mobile 向け i7 で 20秒なので速度差は 2.6倍ほど。
Desktop PC だと 3倍以上差がつくと思われます。
i7 と ARM11 との差は 100倍以上。

下記は Android NDK を使った build 時間です。
armeabi, armeab-v7a, mips, x86 の 4種類、さらに OpenGL ES2/ES3 の
2種類生成しているため、上の Linux build よりも数倍時間がかかっています。

flatlib3 AndroidNDK  clock  core  RAM  OS           arch   time sec
-------------------------------------------------------------------------
Core 2 Duo P7350     2.0GHz  x2   8GB  MacOSX 10.9   x64   482.1  (8m2s)
BayTrail-D J1900     2.0GHz  x4   8GB  Windows 7     x64   424.1  (7m4s)
BayTrail-D J1900     2.0GHz  x4   8GB  Ubuntu 14.04  x64   277.2  (4m37s)
Core i5-3210M Ivy    2.5GHz  x2   8GB  MacOSX 10.9   x64   219.0  (3m39s)
Core i7-2720QM Sandy 2.2GHz  x4  16GB  Windows 8.1   x64   157.7  (2m37s)
Core i7-3615QM Ivy   2.3GHz  x4  16GB  Windows 8.1   x64   146.5  (2m26s)
Core i7-2720QM Sandy 2.2GHz  x4  16GB  MacOSX 10.9   x64   142.7  (2m22s)
Core i7-3615QM Ivy   2.3GHz  x4  16GB  MacOSX 10.9   x64   114.1  (1m54s)
Core i7-2720QM Sandy 2.2GHz  x4  16GB  Ubuntu 14.04  x64   102.9  (1m42s)

・time が少ない方が高速
・Android NDK (r9d) gcc 4.8
・armeabi, armeabi-v7a, mips, x86

↑こちらは ARM 上で走らない代わりに OS に依存せず比較できる利点があります。
ただ OS 環境による差が予想以上に大きいので
プロセッサの性能を見るならば同一 OS 上で比較した方がよさそうです。
Cygwin を経由せず直接 gcc を呼び出しているのですが、
それでも Windows 上のコンパイルは低速でした。

下記は OSX target での比較。

flatlib3 Mac OSX     clock  core  RAM   OS   arch compiler    time sec
-----------------------------------------------------------------------
Core 2 Duo P7350     2.0GHz  x2    8GB  10.9 x64  clang-3.4    69.0
Core i5-3210M Ivy    2.5GHz  x2    8GB  10.9 x64  clang-3.4    38.8
Core i7-2720QM Sandy 2.2GHz  x4   16GB  10.9 x64  clang-3.4    26.1
Core i7-3615QM Ivy   2.3GHz  x4   16GB  10.9 x64  clang-3.4    21.8

・time が少ない方が高速
・x86_64 のみ

i7-2720QM と Core 2 Duo との時間差は、OSX 向けで 2.64倍、
OSX 上の Android build で 3.37倍 になっています。
Ubuntu 上 Android build で比べると i7-2720QM と BayTrail は 2.2倍。
よって Coe 2 Duo よりも BayTrail 4core の方が速いと言えます。

下記は Windows target の比較です。
Windows 向けは x86/x64 両バイナリを生成していることと、
OS によってコードの量が異なるのでより時間がかかっています。

flatlib3 Windows     clock  core RAM   OS    arch  compiler    time sec
------------------------------------------------------------------------
BayTrail-D J1900     2.0GHz  x4   8GB  Win7   x64  VS2013TCP    402.1
Core i7-2720QM Sandy 2.2GHz  x4  16GB  Win8.1 x64  VS2013TCP    137.3
Core i7-3615QM Ivy   2.3GHz  x4  16GB  Win8.1 x64  VS2013TCP    113.4

・time が少ない方が高速
・x86, x64

↑こちらのデータを見ると 2.9倍と差が開いており、
比率としては Core 2 Duo との差が少なくなっています。

最後はデータとしては意味が無いけど念のため iOS 向け build です。
5種類分の Fat Binary を生成するため時間がかかっています。
OSX と比べるとほぼ 5倍なので計算通りです。

flatlib3 iOS         clock  core  RAM   OS   arch compiler    time sec
---------------------------------------------------------------------------
Core 2 Duo P7350     2.0GHz  x2    8GB  10.9 x64  clang-3.4   350.6  (5m51s)
Core i5-3210M Ivy    2.5GHz  x2    8GB  10.9 x64  clang-3.4   189.1  (3m9s)
Core i7-2720QM Sandy 2.2GHz  x4   16GB  10.9 x64  clang-3.4   128.5  (2m8s)
Core i7-3615QM Ivy   2.3GHz  x4   16GB  10.9 x64  clang-3.4   107.6  (1m48s)

・time が少ない方が高速
・armv7, armv7s, arm64, x86, x86_64

Desktop PC として使っているとさすがに遅さを感じますが、
CPU core 数が多いためそれなりにパフォーマンスが出ている印象です。

8inch クラスの軽量な Tablet PC として携帯しつつ、コンパイル時間が
ノート PC の数倍の範囲に収まるなら十分使えそうだと感じました。
ただし実際の Tablet では動作クロックも搭載 RAM 容量も大きく減るので
その点は検証が必要かと思います。


関連エントリ
Atom Bay Trail の浮動小数点演算能力


最近の Windows Tablet 等に使われている Bay Trail は、
新しい世代の Atom CPU core (Silvermont) を搭載しています。
HT 無しの 2/4 core で Out-of-Order となり、
旧 Atom と比べて実行性能が大きく向上しています。

Bay Trail の浮動小数点演算能力を調べてみました。
テスト環境は Bay Trail-D (Celeron J1900) なので厳密には Celeron となります。

結果、単精度の浮動小数点演算能力は 旧 Atom と変わらず、
1 core あたり 6 fop (add 4 + mul 2) / clock であることがわかりました。
旧 Atom 同様 add, mul の非対称な interleave が良い結果となっています。
その代わり倍精度演算は強化されており、旧 Atom の 2倍に相当します。

VFP Benchmark の結果から求めた cycle あたりの演算 (1coreあたり)

                       Single FP   Double FP
---------------------------------------------------------
Atom Bonnell (旧Atom)     6          1.5
Atom Silvermont (新)      6            3 1.5     (Bay Trail)
Core 2 Duo                8            4
Core i7 Sandy Bridge     16            8
Core i7 Ivy Bridge       16            8
Core i7 Haswell          32           16     (未計測,予想値)

Cortex-A9                 4            1
Cortex-A15                8          1.4
Krait                     8            2     (Snapdragon 800)
Swift                     8            1     (iPhone 5)
Cyclone ARM64            16            8     (iPhone 5s)

演算内容の内訳は次の通り

                       Single FP         Double FP
SIMD(Vector)           mul  add  mad     mul  add  mad
-------------------------------------------------------
Atom Bonnell (旧Atom)   2    4   (6)     0.4  0.5    ?
Atom Silvermont (新)    2    4   (6)       1    2   (3)  0.5  1.0  (1.5)
Core 2 Duo              4    4   (8)       2    2  (3?)
Core i7 Sandy Bridge    8    8  (16)       4    4   (8)
Core i7 Ivy Bridge      8    8  (16)       4    4   (8)

Cortex-A9               2    2    4       --   --   --
Cortex-A15              4    4    8       --   --   --
Krait                   4    4    8       --   --   --
Swift                   4    4    8       --   --   --
Cyclone ARM64           8   12   16        4    6    8

Scalar 時の結果など、より詳しくまとめた表を下記に載せています。

cycle あたりの演算命令の詳細

以下は実際の J1900 の VFP Benchmark の結果です。
演算命令単位など、より詳細な結果をご覧になりたい方は こちら よりどうぞ。
各種 CPU のログを載せています。

Bay Traild-D Celeron J1900 2.0GHz (TB:2.5GHz 2.41GHz)

ARCH: x64
FPU: SSSE3 SSE4.1 SSE4.2
SingleT SP max: 14.477 GFLOPS
SingleT DP max:  3.619 GFLOPS
MultiT  SP max: 57.902 GFLOPS
MultiT  DP max: 14.471 GFLOPS
CPU core: 4
SSE: yes
AVX: no
FMA: no
~

理論値は 2GHz 4core で 48 GFLOPS なので、計測結果はより高い数値が出ています。
Turbo Boost が効いているためで、57.902 / 24 = 2.41 から
Multi Thread 時におよそ 2.4GHz で動作していることがわかります。

他の CPU との比較。

VFP Benchmark 実測値        clock core    Single FP     Double FP
-------------------------------------------------------------------
Bay Trail-D  J1900           2.0GHz x4    57.9 GFLOPS   14.5 GFLOPS
Menlow       Atom Z540       1.9GHz x1    10.9 GFLOPS    1.9 GFLOPS
Core 2 Duo   P7350           2.0GHz x2    31.7 GFLOPS   12.7 GFLOPS
Ivy Birdge   Core i5-3210M   2.5GHz x2    90.2 GFLOPS   45.2 GFLOPS
Sandy Bridge Core i7-2720QM  2.2GHz x4   162.3 GFLOPS   74.0 GFLOPS

Kindle HDX 7 Krait 400       2.2GHz x4    67.5 GFLOPS   16.9 GFLOPS
Tegra Note 7 Cortex-A15      1.8GHz x4    51.3 GFLOPS    9.8 GFLOPS
iPhone 5s    Cyclone         1.3GHz x2    40.9 GFLOPS   20.5 GFLOPS

・ピーク値による比較、GFLOPS が大きい方が速い

↑ Multi Thread 時の比較なので、Core 数が多く Clock が高い方が良い結果になります。

Mobile 向け CPU の性能向上が著しく、旧 Atom (Bonnell/Saltwell) では
ハイエンドの Quad core ARM に太刀打ちできませんでした。
新しい Atom Silvermont は十分な性能を有しています。
ただ浮動小数点演算はそれほど得意ではないようです。
おそらく AVX にも対応している Jaguar の方が上でしょう。

なお Tablet 向け Bay Trail-T は動作クロックが下がるため、
上記の表よりも低い値になると考えられます。

また、あくまで浮動小数点演算に特化した数値なので、
実際のアプリケーションの動作速度とは異なる点にご注意ください。
当 blog が浮動小数点演算能力のデータを集めているのは、ゲーム開発時の最適化が目的となります。

2014/05/15 訂正:
 ・Celeron J1900 の TB Clock の間違いを訂正いたしました 2.5GHz → 2.41GHz
 ・倍精度演算で旧 Atom の 2倍は間違いでした。旧 Atom と同等の性能と思われます。
申し訳ありませんでした。


関連ページ
VFP Benchmark
VFP Benchmark の計測結果
CPU FLOPS 理論値と、cycle ごとの演算数まとめ

関連エントリ
VFP Benchmark v1.1 浮動小数点演算命令の速度 (NEON/SSE/AVX)