WebAssembly/Emscripten を使ってエミュレーターをブラウザで動かす

WebAssembly を使うとブラウザー上でいろいろな言語のエコシステムが使えて楽しいなと、最近 Rust/WebAssembly で遊んでいたのですが、ふと C もやってみようかなと hello world 的にメガドライブエミュレーター(Genesis-Plus-GX)を移植することに挑戦してみました。

実は Emscripten はかなり前に一度挑戦していたのですが、ブラウザーで動作させる際のビルド周りがなかなか大変で、あまり大きなものは動かすことができませんでした。

再挑戦ということで調査したところ、昨今はビルド周りも整備されていてなかなかいい感じに環境ができあがっているようです。

この記事のソースコードは github で公開しています。解説よりも、ソースを見ていただいたほうが早いかもしれません。 🙂

https://github.com/h1romas4/wasm-genplus

Genesis-Plus-GX WebAssembly porting (work in progress)

Firefox で動作を確認しています。

Emscripten + webpack ビルド

よい製作にはよいビルドということで、JavaScript 系は webpack を使ってビルドし Emscripten/wasm を読み込むようにしています。

当初は emcc-loader という webpack の extention を使って、webpack から emcc(Emscripten のコンパイラ)を呼び出す形にしていたのですが、現在メンテナンスされていないようで Emscripten 1.39.0 では emcc が出力する JavaScript のグルーコードがエラーとなりうまく wasm をロードすることができませんでした。

Module.ENVIRONMENT has been deprecated. To force the environment, use the ENVIRONMENT compile-time option 

どうやら emcc-loader が生成するコードが指定している環境変数指定が非推奨となったということのようです。emcc-loader の次の場所(ENVIRONMENT: 行)をコメントアウトすると動作させることができました。

/**
 * Builds a loader script.
 */
async buildLoaderScript(baseScriptContent : string, options : LoaderOption) {
    const config = {
        ENVIRONMENT: options.environment,
    };

動かせるようになったものの、emcc-loader はプログラムに修正がかかるとフルビルドになる動作となり、少し大きめのプロジェクトだと時間がかかるため、今回は emcc-loader は諦めて C の部分は通常の cmake / make でビルドするようにしています。

この場合で emcc で出力される wasm/JavaScript のグルーコードを webpack で読み込むときは、リンカーオプションを次のように指定しモジュール化してあげます。

add_compile_flags(LD
    "-s DEMANGLE_SUPPORT=1"
    "-s ALLOW_MEMORY_GROWTH=1"
    "-s MODULARIZE=1"
)

この上で自分の JavaScript から、emcc が出力したグルーコード JS を import して次のようにすると、 wasm が async でロードされ wasm モジュールが操作できるようになります。

import wasm from './genplus.js';

let gens;

wasm().then(function(module) {
    gens = module;
    gens._init();
    // ...
});

モジュールとなったグルーコードを wasm() などと受けて then 以下で取得します。

WebAssembly とインターフェースする C の関数は次のように定義し、JavaScript からはモジュールから _ 付の関数として呼び出しすることができます。

void EMSCRIPTEN_KEEPALIVE init(void)
{
    // ...
}

また、C側の make についてですが cmake、make ともに Emscripten のラッパーコマンドが準備されていて、これを経由して cmake / make することでコンパイルオプションなどを自動的に調整してくれるようです。

emcmake cmake ..
emmake make

github のほうにビルド手順を記載してあります。

メモリーの共有

WebAssembly 側で malloc したメモリーはモジュールの HEAP*.buffer ビュー経由で JavaScript から見ることができ、JS 側の TypedArray 経由でアクセスできます。

vram = new Uint8ClampedArray(gens.HEAPU8.buffer, gens._get_frame_buffer_ref(), CANVAS_WIDTH * CANVAS_HEIGHT * 4);
uint32_t *frame_buffer;

void EMSCRIPTEN_KEEPALIVE init(void)
{
    // ...
    frame_buffer = malloc(sizeof(uint32_t) * VIDEO_WIDTH * VIDEO_HEIGHT);
    // ...
}

uint32_t* EMSCRIPTEN_KEEPALIVE get_frame_buffer_ref(void) {
    return frame_buffer;
}

エミュレーターで作成した VRAM (uiint32_t) を JS 側で取得して canvas にそのまま描画しています。

ちなみに、この例では C 側は uint32_t ですが、canvas は RGBA を Uint8ClampedArray ビューで扱うためエンディアンで逆になってしまい色がおかしくなりました。。ややはまり。エミュレーター側で ABGR 順の VRAM をつくることで対応しています。

音声

ブラウザ WebAudio API 側は Float32Array の 2チャンネル分離で、エミュレーター側はサンプリングを S16LE で生成するため wasm 側で変換しています。

float_t convert_sample_i2f(int16_t i) {
    float_t f;
    if(i < 0) {
        f = ((float) i) / (float) 32768;
    } else {
        f = ((float) i) / (float) 32767;
    }
    if( f > 1 ) f = 1;
    if( f < -1 ) f = -1;
    return f;
}

また、生成したサンプリングを WebAudio API の createBufferSource で即発声させると、次の発声が途切れてしまうので web-audio-buffer-queue ライブラリを使わせてもらって、少しバッファリングしてから発声するようにしています。

残念ながら iOS Safari ではブラウザの発声規制にかかっているせいか音が鳴りません。一応、クリック後にコンテキストをつくるようにしてみたのですが…

WebAssembly/Rust で似たようなことをやった時は iOS でも発声していたので、要調査。

ソースコードデバッグ

C 側のソースですが、emcc がソースマップ出力に対応しているためブラウザ(Firefox で確認)でデバッグブレイクが可能です。ただし、変数の値などはみることはできないようです。

ソースマップを出力するためには emcc のコンパイルオプションで -g4 を指定し、–source-map-base オプションを指定します。

# source map option (but not working)
#    -g4
#    --source-map-base src/main/c

その上で、ソースコードがブラウザーから見えるように http の領域に配置します。

devServer: {
    inline: true,
    contentBase: [
        path.join(__dirname, '/docs'), // eslint-disable-line
        // for sourcemap - src/main/c
        path.join(__dirname, '/'), // eslint-disable-line
    ],

基本的にはこれで止まるのですが、残念ながら現在 –source-map-base オプションが wasm の場合はうまく効かず、出力がすべてフルパスになってしまうようです。(emcc-loader だといい感じに source-map がでていたので何か方法があるのかもしれません)

とりあえず、出力された source-map のフルパス部分を置換して http から見えるパスにしてあげれば動作します。

WebAssembly/Emscripten 上でプログラムを動作させると、メモリーアクセスに対して境界値チェックが働くパターンがあるようです。実は移植当初 ROM のおしり 0x800000 から SRAM がマッピングされているのに気が付かず(ネイティブだと動いてしまっていた)、wasm がダウンしてしまっていたのですが、ソースコードマッピングすることで場所を特定することができました。

C 側の軽いデバッグの場合は、EM_ASM_ マクロでブラウザーのコンソールに文字列を出力できます。

#include <emscripten/emscripten.h>

uint8_t *rom_buffer;

void console_log() {
    EM_ASM_({
        console.log('genplus_buffer0: ' + $0.toString(16));
    }, buffer[0x100]);
}

なお、wasm 側でエラーがコンソールに出力された場合は、このマクロの出力が消える場合があるようです。(Firefox にて)

Emscripten 移植のこつ

箇条書きにて。

  • C 側はネイティブでも動作を確認する環境をつくりつつ、Emscripten でコンパイルして動作させりるとすると切り分けが早いです。前述の境界値系のエラーが wasm ででた場合はソースをアタッチするといいと思います。
  • WebAssembly の場合、イベントや入出力系は全て JavaScript 側の役目になりますので、エミュレーター系の移植の場合は一貫して JS -> wasm の呼び出しパターンでプログラムを構成すると、処理の粒度的に分かりやすくなりそうです。
  • C のプログラムを動かすというよりも、ビルドや JS とのインターフェース周りを調査するのに時間がかかりました。逆説的には、そこがクリアできれば大抵のものが動かせそうです。

というわけで、WebAssembly はいろいろできて楽しいですね。エミュレーターのほうですがまだコントローラーをつないでないので、Gamepad API で接続してみたいと思います。 🙂

ついに iOS でエミュレーターが動かせる…!

関連

Kindle Fire HD 購入

Kindle Fire の 3000円割引最終日に、Grails2 系の洋書がいくつかでていることに気がついてしまったのが運の尽き・・・。 スマートフォンや 10インチ Android タブレットの Kindle アプリで読んでみたものの、小さすぎ大きすぎ。。

以前から Kindle Paperwhite は持っていて、小説を読むのにこれ以上のものはないと思っていたのですが、技術書を出力するにはちょっとページ送りの速度や、画面の大きさに難があり。小説と違って、ペラペラ戻ったり飛んだりしますゆえ。

というわけで、Kindle Fire HD を買ってしまいました。 割引入って 12,800 円。:)

20130714_111847

手前 Paperwhite、奥 Fire HD。こうしてみると、Paperwhite の紙っぽさはさすがですね。今後も小説はこちらで!

さて、Kindle Fire HD で The Defintive Guide To Grails2 を読んでみている図。良い感じです。

Screenshot_2013-07-14-11-15-03

Kindle 版の Grails 書籍は、紙版よりずいぶん安くなっています。 たぶん、Groovy in Action Second Edition も今後でてくるとと思うので楽しみです。当たり前なんですが、洋書でも送料もかからないのが良いですなぁ。:)

Screenshot_2013-07-14-10-56-07

Kindle Fire は、普通の Android に近いですので .apk ファイルさえあれば Kindle Store にないアプリも(たぶん大抵)動作します。 XBMC を入れてみた図。

Screenshot_2013-07-14-10-53-10

GPU は PowerVR SGX 540。 残念ながら XBMC では動画再生支援が効いていないのかコマ落ちします。 Twitter クライアントや FIrefox などはずいぶん機敏に動作します。 🙂

重量は若干あるものの、カバンのサイドぽけっとに入れておくにも丁度良いです。純正ケースも良い意味で存在感がなく、これは本なんだなって感じがしてお気に入りました。

ちなみに、洋書を買っていますが英語が読めません。(←壮大なオチ

WordPress for Android のビルド

突然ですが WordPress の Android スマートフォン投稿アプリ「WordPress for Android」のビルドの仕方です。

事の発端は、スマートフォンを Galaxy SII から S4 に変えたこと…。なんと S4 は写真の画素を VGA に落とせず、サーバのメモリ不足にて WordPress の写真ブログに投稿できないという衝撃の事実。(←時代についていっていないひろましゃくん…

まぁ、ついでに好きなように WordPress for Android のソースコードを修正できたら面白いのではないかと始めたのでした。

wp-android10

どうやら次世代 WordPress には、JSON ベースの REST API も付きそうな雰囲気ですので、こういった WordPress が備える Web 管理画面外からの投稿アプリの研究をしておくのも良いかもしれませんね。 🙂

てなわけで、自分でも忘れそうなのでビルド方法を記しておきます。

Android SDK や ADT はインストール済みとします。 画面は Ubuntu の 英語版 Eclipse より。 他の環境の方は適宜読み替えください。

依存ライブラリのセットアップ

WordPress for Android をビルドするには 、ActionBarSherlockandroid-menudrawer ライブラリが必要です。 何気に pom.xml とかがないので dependency がよく分からないのですが、、とりあえずそれぞれの master ブランチを使っています。

両ライブラリともに github で公開されていますのでクローンします。

ActionBarSherlock
android-menudrawer

Eclipse に EGit が入っていると思いますので、Git Repository ビューより。

wp-android01

2つのライブラリがクローンできたら双方とも Eclipse のワークスペースに持ってきます。

File -> New -> Other -> Android Existing Code

wp-android02

Next ボタン押下後にパスの指定がでますので、さきほどクローンした 2ライブラリを追加します。

ActionBarSherlock はリポジトリに複数のプロジェクトが含まれていますが、「actionbarsherlock」ディレクトリを指定します。通常は次のパスになるでしょう。

~/git/ActionBarSherlock/actionbarsherlock
~/git/git/android-menudrawer

ワークスペースにプロジェクトが持ってこれたら、それぞれに API レベルとライブラリ利用の指定をします。

プロジェクト名右クリック -> Properties -> Android

android-menudrawer のマスターブランチは、Jerry Beans でないとコンパイルが通らないようだったので、双方とも 4.2.2 (API 17) に設定しました。 また、Library の「Is Library」のチェックを忘れずにつけます。

wp-android03

以上で依存関係の操作は終わりです。

WordPress for Android コアのビルド

WordPress for Android は SVN でソースが公開されていますので、いつも通り Eclipse の SVN ビューからチェックアウトし、ワークスペースにのせます。

http://android.svn.wordpress.org/

次に先ほど入れた依存ライブラリのパスを設定していきます。

wp-android プロジェクト右クリック -> Properties -> Android

チェックアウト後は、Library がパス切れ(バッテン)していると思いますので、一度 Remove して先ほどワークスペースに追加したふたつのライブラリを Add し直します。

wp-android04

wp-android05

次にデプロイ時に含むライブラリを選択します。

wp-android プロジェクト右クリック -> Properties -> Other and Export

SDK(Android 4.2.2) と Android Private Libraries と Android Dependencies にチェックがついていることを確認してください。(指定できていないと ClassNotFound Exception します)

wp-android07

ここでいよいよ、ライブラリも含めてビルド(クリーン)します。

Project -> Clean

3つのプロジェクトを選択して OK を押します。

wp-android08

ビルド通りましたか? 😀

ビルドパスにライブラリの jar が追加されていることを確認します。

wp-android プロジェクト右クリック -> Properties -> Java Build Path

wp-android06

では、アプリを実行してみます。

wp-android 右クリック -> Run as -> Android Application

wp-android09

おめでとうございます。 WordPress for Android はあなたの手の中に 😀

wp-android11

動かない場合は、おそらく依存関係の欠如だと思いますので Marker ビューで追っていけばなんとかなると思います。

ビルドができれば、あとは知恵と勇気でなんとかなるですね。これくらいの大きさの実装だと Android アプリ作成の勉強にもちょうど良いです。

これよりソースをいじってみたいと思います。 🙂