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

プロジェクトが巨大な場合メモリ不足で起動できないことがあります。
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 
#include 


static EM_BOOL KeyCallback( int event_type, const EmscriptenKeyboardEvent* event, void* user_data )
{
    AppModule*  _This= reinterpret_cast(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(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 のアプリケーションをブラウザで動かす 一覧