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

前回 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 も予想よりも速い印象です。

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

以下詳細データ

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 
~

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

    return  0;
}

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

● Main Loop

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

#include 

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

// 毎フレーム呼ばれる
static void ems_loop( void* arg )
{
   AppModule*  iApp= reinterpret_cast( 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 のアプリケーションをブラウザで動かす 一覧