2014/05/29
Emscripten C++ のアプリをブラウザで動かす (8) iOS でも動く
実際に C/C++ で書かれたゲームを Emscripten で移植してみました。
プラグインなしにブラウザだけ動きます。
↓Windows Firefox

・ChiRaKS (Emscripten) ここから直接実行できます
● お勧めブラウザと端末
未確認ですがおそらく 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

なお手持ちの iPad4/iPad3/iPhone5 では動かなかったので注意が必要です。
今のところ iOS で正しく動作するのは 64bit CPU (Apple A7) 搭載端末だけかもしれません。
よって iPad Air, iPad mini retina では動作するのではないかと思われます。
iPad Air / iPad mini retina でも動作することを確認しました。
● 動作テスト
下記は動作テストした環境一覧です。より詳しい表はこちら。
ページを読み込んだあと、速度が安定するまで多少時間がかかる場合があります。
おそらく JIT Compiler か GC と思われます。
ほぼ 100% CPU 性能に依存するため、
GPU が非力な Tegra 3 (Nexus 7 2012) でも結構良いスコアになっています。
● PlayStation Vita でも一応動く
PS Vita の内蔵ブラウザでも一応動くことがわかりました。
ただし非常に低速で 0.1fps。 ほぼ止まっているに等しい状態です。
↓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 のアプリケーションをブラウザで動かす 一覧
プラグインなしにブラウザだけ動きます。
↓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)
(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

なお手持ちの iPad4/iPad3/iPhone5 では動かなかったので注意が必要です。
今のところ iOS で正しく動作するのは 64bit CPU (Apple A7) 搭載端末だけかもしれません。
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 内蔵ブラウザ

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) 解説ページ
● まとめ
・
・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)
テクスチャ
圧縮テクスチャは、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 だけチェックしているからではないかと思われます。
テクスチャ以外
基本的には 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 のアプリケーションをブラウザで動かす 一覧
(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)

InternetExplorer でも動くようになったので比較してみます。
いずれも fps 値が大きい方が高速です。
fps に幅があるものは、前回のテストに従い描画負荷を変更しているため。
「34-60fps」なら一番重い 1. で 34fps 前後、一番軽い 3. で 60fps 以上
出ていることを意味しています。
以下別の PC のテスト
↓ Chrome 向け 修正の例: Emscripten から出力された *.js の _glGetError() 内にある
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

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 のアプリケーションをブラウザで動かす 一覧
Chrome の WebGL getError() 呼び出しが非常に重かったためです。
getError() 無しのビルドでは 60fps を超える速度となっています。
JavaScript が遅いわけではありませんでした。
Windows 8.1 Chrome 35 (60fps)

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;
}
}
// 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

// 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 で書かれたライブラリと直接リンクすることができます。
ビルド手順
C言語のリンケージに含まれるので、extern 宣言するだけで呼び出せます。
Emscription の library*.js を見ると、標準で用意された関数も
JavaScript で書かれていることがわかります。
利点としては Native な JavaScript の方が自由度が高いことが挙げられます。
ただ C/C++ で書いた場合は asm.js 対応コードに変換してくれるため、
どちらが効率がよく動作できるのかは処理内容に依存すると思われます。
● 引数
ポインタを受け取る場合はメモリアクセスが必要です。
型に応じて HEAP8,HEAP16,HEAP32,HEAP64 を用いるか
setValue()/getValue() 命令を使うことができます。
文字列の Object 変換は Pointer_stringify() で可能。
double に収まらない long long (64bit int) 型は、
2個の整数 (low, high) に分解されていることもわかります。
● 戻り値
64bit 型は global な戻り値レジスタ tempRet0~ を併用しています。
他の関数呼び出しで破壊される可能性があるので atomic ではない点に注意。
文字列定数など static なポインタを返すのは簡単ではないようです。
メインメモリ空間に配置されていなければならないためで、
static な data 領域はコンパイル時に作られます。
ライブラリ初期化時に HEAP から確保して代用する形になるでしょう。
C言語に返すことが可能な heap memory の確保は、
Module._malloc() / Module._free() を使用します。
C言語の runtime に含まれるので、コンパイル時にこれらの関数への参照がなければ
正しいコードが生成されない点に注意が必要です。
↑(1) の free が無ければ C言語で malloc()/free() が生成されないため、
JavaScript 側の Module._malloc() は正しい挙動になりません。
例えば下記のようにアロケータを完全に置き換えようとしても、
C言語から参照がないので _malloc/_free がダミーコードになります。
一見正しく動作しますがメモリリークします。
● メモリマップ
NULL アクセスはコンパイラが検出できる場合だけ例外が発生します。
動的な NULL アクセスはデフォルトではエラーになりませんが、
-s SAFE_HEAP=1 を付けることでメモリアクセス時のアドレスチェックが可能です。
その分動作速度は低下します。
stack は前方から後方に向かって確保されています。
文字列定数や static/global に確保したメモリは STATIC エリアに格納します。
初期値を持たない配列も含まれるため、下記のようなケースでは初期化データに
大量の 0 が並ぶことになります。
ファイルサイズが増加するので、HEAP から確保した方が無駄が少なく済むようです。
↓
次回: 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 のアプリケーションをブラウザで動かす 一覧
JavaScript で書かれたライブラリと直接リンクすることができます。
// lib.js
mergeInto( LibraryManager.library, {
print: function(x){
console.log( x );
}
});
mergeInto( LibraryManager.library, {
print: function(x){
console.log( x );
}
});
// main.cpp
extern "C" {
void print( int x );
}
int main()
{
print( 12345 ); // これ
return 0;
}
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) );
}
});
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;
}
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
},
});
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;
}
#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 アクセス
}
},
});
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;
}
#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 );
},
});
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;
}
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];
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,
~
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 時に確保されているようです。
関数呼び出しが通常の function なので、関数ポインタは HEAP や STACK と別空間にあります。
callback などの関数では「関数ポインタ用に設けられた関数のテーブル」
に対する index 値が渡されています。
引数など型に応じてテーブルは複数存在するので、関数ポインタを別の関数型に
cast するようなコードは正しく動かないと公式サイトにも書かれています。
コード中たまに見かける dynCall() が関数ポインタを使った呼び出しを行っています。
あとから気がついたのですが、emcc に --js-opts 0 を指定すると
もっと読みやすい出力が得られるようです。
可変引数領域が stack 上に確保されていることがわかります。
JavaScript のコードとして動くため、block するような I/O 命令は
本来うまく動かないことになります。
callback が発生しないためで、一旦 event driven のための
メインループに return しなければ完了通知を受け取ることができません。
Emscripten は memory 上に仮想的な File System を作っているため、
ファイルアクセスでは何も問題が起こらず C言語コードのまま動きます。
互換性が高く、アプリケーションを比較的簡単に移植できる要因として
この仮想 FileSystem の果たす役割は非常に大きいのではないかと思います。
emscripten_set_main_loop() は一見その場で block しているように
見えますが、見た目とは全く異なる挙動をしていました。
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() 等のコードが呼ばれることはありません。
では下記のようなコードはどうでしょうか。
例外はあくまで JavaScript の throw なので、main() が終了しても
AppModule の destructor が呼ばれるわけではありません。
また main() が終了するなら ems_loop() で stack 上の app に対する
不正アクセスが行われてしまうようにも見えますが、こちらも大丈夫でした。
main() の epilogue を通らないので、中で確保した stack 領域は
そのまま保持されたままとなっているようです。
以上より、関数呼び出しは仮想化されていないので、
現在の実行コンテキストを保存したまま一時的に Event Loop に戻るような処理は
やはり簡単には実現できないのではないかと思われます。
Thread で block することを想定した同期 I/O まわりなど。
例えば socket を使った通信は
connect() が必ず EINPROGRESS を返すので、継続するには Event Loop に
戻らなければならないようです。
↓こんな感じで使えないかと思ったのですが簡単にはいきませんでした。
もし 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 のアプリケーションをブラウザで動かす 一覧
デフォルトの 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
}
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) 用)
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;
}
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;
}
{
...
}
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;
}
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 );
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 );
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 で接続したあと下記のコマンドを実行
(3) HOST PC Firefox
・Menu → 開発ツール → 開発ツールを表示 → 開発ツールパネルの左上の歯車アイコン → 「リモートデバッガを有効」にチェックを入れる (2014/05/26 追加)
・Menu → 開発ツール → 接続 → [接続] ボタン
(4) Android
・接続許可を求めるダイアログが出るので [OK]
(5) HOST PC Firefox
・どのタブに接続するか選択
↓エラーの例 (Android Firefox)
● Library 化
bc file は merge できるので library として利用できます。
Lib 利用時
拡張子の対応付
コマンドの対応付け
Emscripten はリンク時に未定義シンボルがあっても実行できます。
未定義の関数を呼び出さない限り動作に支障はないようです。
● データ型とサイズ
32bit, ILP32 です。
● EGL と OpenGL ES 2.0
3D の描画は EGL + OpenGL ES 2.0 の組み合わせを用います。
他にも初期化手順を省いてくれる便利なライブラリがいろいろあるようです。
とりあえずできるだけ低レベルと思われる手順を使っています。
Canvas サイズを指定したら、あとは EGL の初期化を行うだけ。
● X11
Sample コードを見ていると Xlib の API も使えることがわかります。
内部コードを確認すると、XCreateWindow() で canvas size の設定を行っているだけのようです。
それ以外のコードは特に意味はありませんでした。
emscripten_set_canvas_size() を指定しているなら必要無いでしょう。
● 入力イベント
Xlib の Event API は実装されていないようなので、
Emscripten の callback を利用しています。
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 のアプリケーションをブラウザで動かす 一覧
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 );
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 );
~
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 );
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 );
}
#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 の端末でも動くようになったのでさらに調べてみました。
今回の結果を見る限り、Cortex-A15 でも Krait 並に高速に動作していることがわかります。
また Cortex-A9 も予想よりも速い印象です。
上の両機種とも画面解像度が低く 1280x800 dot です。
前回速度が出なかった Nexus 10 は 2560x1600 と非常に解像度が高いので、
ブラウザが何らかの解像度に応じた CPU 転送を行っている可能性もあります。
なお内部のレンダリング解像度は 960x640 固定です。
以下詳細データ
Tegra3 は JavaScript の動作にはまだ余裕があるものの、
明らかに GPU 性能が足りずに速度が落ちているようです。
K3V2 は Vivante GC4000 による描画が崩れて正しく表示されませんでした。
ログを見る限りアプリケーション自体は動いているようです。
● Emscripten のビルド
Emscripten のビルドは gcc/clang の代わりに emcc コマンドを用います。
obj (.o) の代わりが .bc (bitcode) です。
実際に実行できるよう JavaScript に変換 (ld/link) するには下記のようにします。
または
実行形式は js だけの場合と、html 含めたブラウザ向けの 2種類あります。
ローカルで実行する場合は node コマンド (Node.js) を使うことが可能で、
この場合は js だけで OK です。
WebGL の描画や preload 指定した追加ファイルを読み込む場合は html で出力し、
Web サーバー経由でブラウザから読み込みます。
● 実行手順の例
ローカルで実行する場合 (node)
ローカルサーバーを経由する場合 (Browser)
● ファイルシステム
Emscripten の実行環境は仮想 FileSystem を持っており、
内部的に Unix FileSystem をエミュレートします。
実行時に読み込むファイル群は、一旦この仮想 FileSystem 内に読み込んでおく必要があります。
必要なファイルを実行ファイルに埋め込む方法は下記 2種類あります。
--embed-file の例
↑この場合 root/shaders フォルダ以下のファイルをすべて js の中に埋め込みます。
仮想 FileSystem 内のパスも /root/shaders/* とみなします。
もし実際のデータ置き場と、仮想 FileSystem 内のパスが異なる場合は
'@' マークを使います。
↑ '@' 以降が内部の仮想パスの指定になります。
実際のデータは root/shaders 以下に存在しており、
実行時は /data/shaders にあるとみなされます。
--embed-file は間違いが少なく node でも利用できて便利なのですが、
ファイル数が増えるとビルドが極端に遅くなる問題があります。
バンドルが 200MB くらいあるプロジェクトではコマンドが返ってきませんでした。
--preload-file の場合はデータを外部ファイルにまとめるため、
データ容量が増えても比較的時間がかからずにビルドできます。
どちらにせよ、これらの方法はブラウザを開いたあと長いロードが入るので
その間何もできなくなります。
現実的には、動的な逐次読み込みを実現する必要があるでしょう。
またメモリ上の仮想 FileSystem はデータの保存ができません。
必要ならば Indexed Databased を FileSystem に mount して
利用することができるようです。
● Inline JavaScript
仮想マシンが JavaScript となる Emscripten では、Inline Assembler ではなく
Inline JavaScript を利用することができます。
EM_ASM() の中に JavaScript code を記述します。
仮想ファイルシステムへの動的な読み込みなど色々コードを埋め込むことができるようです。
● Main Loop
Emscripten では callback を使って Game の Main Loop の代わりにします。
例えば AppModule::Render() を毎フレーム呼び出したい場合は下記のような使い方になります。
● HeapSize
実行環境のメモリが足りずに、アプリケーションを起動できないことがあります。
下記のようにアプリケーションが使用する HEAP SIZE を指定することができます。
あまり大きな値を指定し過ぎると、今度はブラウザ側のメモリが足りず
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 のアプリケーションをブラウザで動かす 一覧
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;
}
~
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;
}
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)

・Android Firefox (29.0.1)


Chrome では 7 fps 程度なので asm.js (Firefox) の効果は絶大でした。
Android でも十分な速度で動いています。
追記 2014/05/24: その後 Chrome でも 60fps 以上出るようになりました。詳しくはこちら
もともと 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 あり
2. 960x640 63万Tri/191万Vertices
3. 960x640 6万Tri/19万Vertices
描画負荷を変えても速度が大きく変わらないものは 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 のアプリケーションをブラウザで動かす 一覧
Native Code 向けのプログラムがそのままブラウザ内で動いています。
・Windows Firefox (29.0.1)

・Android Firefox (29.0.1)


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 は高速動作
追記 (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万円。
価格帯もスペックも良く似ているので比べてみました。
・Intel Celeron Processor J1900
・AMD Athlon
浮動小数点演算能力
前前回の予想通り浮動小数点演算能力は Jaguar (Kabini/Athlon) の方が高くなっています。
J1900 (BayTrail) は動作クロックの高さで補っている形です。
再測定して気が付きましたが、以前のエントリで J1900 の倍精度演算の
性能評価が間違っていました。下記訂正しましたので申し訳ありませんでした。
「Atom Bay Trail の浮動小数点演算能力」
前回のコンパイル速度比較を Kabini でも試してみました。
驚くほど拮抗しています。
テストした環境は下記の通り。
Kabini は DDR3-1600 が使えますが、テスト環境では手持ちの DDR3-1333 を使用しています。
本来の能力よりもスコアが低くなっていると考えられますので予めご了承ください。
AES 変換テスト
AES-NI が使えるため Jaguar (Athlon/Kabini) の方が高速です。
同じアルゴリズム同士でもわずかに Jaguar の方が速いようです。
メモリ速度、CPU の動作クロックともに J1900 (BayTrail) の方が上なので、
Jaguar (Athlon/Kabini) は Core 性能そのものが高いのだと思われます。
簡単なシーンのレンダリング速度の比較
比べるまでもなく内蔵 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 の浮動小数点演算能力
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 の浮動小数点演算能力
2014/05/08
コンパイル時間の比較 BayTrail
BayTrail-D Celeron J1900 の PC でコンパイル時間を比べてみました。
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 よりも数倍時間がかかっています。
↑こちらは ARM 上で走らない代わりに OS に依存せず比較できる利点があります。
ただ OS 環境による差が予想以上に大きいので
プロセッサの性能を見るならば同一 OS 上で比較した方がよさそうです。
Cygwin を経由せず直接 gcc を呼び出しているのですが、
それでも Windows 上のコンパイルは低速でした。
下記は OSX target での比較。
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 によってコードの量が異なるのでより時間がかかっています。
↑こちらのデータを見ると 2.9倍と差が開いており、
比率としては Core 2 Duo との差が少なくなっています。
最後はデータとしては意味が無いけど念のため iOS 向け build です。
5種類分の Fat Binary を生成するため時間がかかっています。
OSX と比べるとほぼ 5倍なので計算通りです。
Desktop PC として使っているとさすがに遅さを感じますが、
CPU core 数が多いためそれなりにパフォーマンスが出ている印象です。
8inch クラスの軽量な Tablet PC として携帯しつつ、コンパイル時間が
ノート PC の数倍の範囲に収まるなら十分使えそうだと感じました。
ただし実際の Tablet では動作クロックも搭載 RAM 容量も大きく減るので
その点は検証が必要かと思います。
関連エントリ
・Atom Bay Trail の浮動小数点演算能力
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 の浮動小数点演算能力
2014/05/06
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あたり)
演算内容の内訳は次の通り
Scalar 時の結果など、より詳しくまとめた表を下記に載せています。
・cycle あたりの演算命令の詳細
以下は実際の J1900 の VFP Benchmark の結果です。
演算命令単位など、より詳細な結果をご覧になりたい方は こちら よりどうぞ。
各種 CPU のログを載せています。
理論値は 2GHz 4core で 48 GFLOPS なので、計測結果はより高い数値が出ています。
Turbo Boost が効いているためで、57.902 / 24 = 2.41 から
Multi Thread 時におよそ 2.4GHz で動作していることがわかります。
他の CPU との比較。
↑ 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)
新しい世代の 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 が良い結果となっています。
VFP Benchmark の結果から求めた cycle あたりの演算 (1coreあたり)
Single FP Double FP --------------------------------------------------------- Atom Bonnell (旧Atom) 6 1.5 Atom Silvermont (新) 631.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.5GHz2.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)