今年 WebAssembly でつくった3つのアプリ

WebAssembly Advent Calendar 2019 の 11日目の記事です。🦀

WebAssembly の登場で C/C++/Rust など JavaScript 以外の言語のエコシステムをウェブブラウザーに持ち込むことができるようになり嬉しいな〜ということで、どのくらい動くのかという検証もかねて、3つほどアプリをつくって動作させてみました。

Emscripten 編

C言語でかかれたゲーム機メガドライブのエミュレーター Genesis-Plus-GX に WebAssembly 用のインターフェースを追加し、Emscripten でコンパイルして動作させてみました。

かなり重めのサウンドコアエミュレーションを有効にしてコンパイルしているのですが、iOS Safari を含め非常に高速に動作しています。(ベアナックル2が最後までプレイできました 🙂

主に Firefox と iOS Safari で確認しています。 Firefox は7段キーボード世代の ThinkPad(いつのだ…)で動作させていますが 60 fps 維持できています。

ソースコードを以下で公開しています。cmake/make と webpack でビルドできるようになっています。

Genesis-Plus-GX WebAssembly porting

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

ROM を吸い出す環境がない方は、homebrew の ROM が動作するかもしれません。.wasm はコンパイル済みのバイナリをコミットしていますので node だけあれば遊べると思います。

ゲームパッドのアサインは手持ちの XBOX ONE 用になっていますので適当に修正ください。。ちなみに、iOS 13 から PS4/XBOX ONE コントローラーサポートが入りましたが、Safari の GamePad API からも接続できました。

Emscripten 環境で少し詰まったのは次のポイントでした。

Emscripten を webpack からモジュールとして import する方法:

リンカオプションで MODULARIZE=1 を指定。

add_compile_flags(LD
    "-s MODULARIZE=1"
)

JavaScirpt で import して module を取得

import wasm from './genplus.js';

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

Wasm 側で malloc したメモリーポインタの取得する方法:

モジュールの module.HEAPU8.buffer など HEAP* ビューで取得。

wasm().then(function(module) {
    gens = module;
    // memory allocate
    gens._init();
    // load rom
    fetch(ROM_PATH).then(response => response.arrayBuffer())
    .then(bytes => {
        // create buffer from wasm
        romdata = new Uint8Array(gens.HEAPU8.buffer, gens._get_rom_buffer_ref(bytes.byteLength), bytes.byteLength);
        romdata.set(new Uint8Array(bytes));
        message("TOUCH HERE!");
        initialized = true;
    });
});

大きめの static のアロケートに失敗した場合:

リンカオプションで初期メモリーサイズを指定。

add_compile_flags(LD
    "-s ALLOW_MEMORY_GROWTH=1"
    "-s TOTAL_MEMORY=32MB"
)

Rust / wasm-pack 編 (1)

Rust/wasm-pack で最初につくったアプリです。

Wasm 側でアロケートしたメモリーを仮想 VRAM として、Rust で何も考えずにむちゃ描きしたらどれくらいの速度になるだろうということで試したものになります。

デモサイトから実際に動作するところが見れます。

https://github.com/h1romas4/wasm-canvas-bitblt

sin/cos で画像回転させながらラスタースクロール的な動きをさせていますが、思うままにプログラムをかいているため RGBA の 4Byte 転送を全画素で何度も回していたりします。

ちょっと興味があったのが、速くなるかなと Rust の unsafe のブロック転送を使い、

unsafe {
    ptr::copy_nonoverlapping(
        [color.0, color.1, color.2, 0xff].as_ptr(),
        self.vram.as_mut_ptr().offset(pos),
        4,
    );
}

のようにしてみたのですが、これは Wasm 的には単純なループで展開されてコンパイルされていました。これは今後 Bulk memory operations が入りコンパイラが対応することで改善するかもしれません。

WebAssembly/bulk-memory-operations

Some people have mentioned that memcpy and memmove functions are hot when profiling some WebAssembly benchmarks.

なお、このプログラムは前述の古い ThinkPad T420s では 45fps そこそこでしたが、iPhone X では余裕で 60fps でていました。速い。。

Rust / wasm-pack 編 (2)

最初の Emscripten メガドライブエミュレーターから、ゲーム機の音源部分(FM音源・PSG)を取り出しプログラムを C言語から Rust に移植したものです。

エミュレーターから音源 LSI に発行するコマンドを横取りして保存した、ゲームミュージックを楽しむ VGM という形式のファイルがありますが、それを再生するプレイヤーになっています。

YM2612/SN76489 VGM player by Rust

こちらもデモサイトから動作を見ることができます。自分がつくったサンプル VGM をひとつ入れています。しょぼいですがクリックで鳴ります。本当はもっとすごい楽曲が再生できます。。

.vgm を準備できる方はドラッグアンドドロップしてみてください。(なお全て WebAssembly で処理してますので、サーバーにファイルアップロードはされません。安心してお試しください)

ソースは以下から参照できます。

https://github.com/h1romas4/rust-synth-emulation

デバッグ手法:

プロジェクトを pure Rust 部分と、Wasm 部分に分けて構成しています。現在 WebAssemby のデバッグ環境はまだ整っていませんので、複雑な処理はネイティブで実行できる環境で行うと良さそうです。

Wasm のデバッグ環境については Chrome が DRAWF に対応しつつあるとのことで(まだステップ実行のみ)、今後整っていくのではないかと思います。

Improved WebAssembly debugging in Chrome DevTools

As a first step, DevTools now supports native source mapping using this information, so you can start debugging Wasm modules produced by any of these compilers without resorting to the disassembled format or having to use any custom scripts.

ライブラリの活用:

本プレイヤーアプリですが、.vgz と呼ばれる .gz 圧縮された .vgm ファイルの再生にも対応させています。

WebAssembly/Rust は stdlib でコンパイルできますので、pure Rust の Gzip, and Zlib ライブラリーである flate2 を dependencies に追加してコンパイルして、ファイル展開させてみたところ問題なく動作しました。

[dependencies]
flate2 = "1.0"

この辺は各言語のエコシステムを活用できる Wasm の強みだなと感じます。

Runtime Error: Index out of bounds.:

移植中 Rust のオブジェクトを JavaScript から new した際に、Index out of bounds. が発生してオブジェクトがつくられない事象が発生しました。ぱっと原因が分からなかったため、ソースを削る方向で試していくと、[0; 50000] ほどの配列の初期化の部分で発生していました。

Make stack size configurable

Currently the stack-size for local variables of the generated wasm code is preconfigured to be 1048576 bytes. It is easy to reach this limit,

どうやら stack-size の初期値が小さいということで、.cargo/config に次の記述をして回避しています。

[target.wasm32-unknown-unknown]
rustflags = [
  "-C", "link-args=-z stack-size=32000000",
]

WebAssembly 登場にてウェブブラウザーで好きな言語で、好きなプログラムを動かせるようになって嬉しいです。

今後も継続してウォッチしていきたいと思います。

Visual Studio Live Share の活用

Visual Studio Code Advent Calendar 2018 の 16日目です。 🙂

今年もいろいろな分野で活躍してくれた VS Code でしたが、拡張として提供されている Visual Studio Live Share にもずいぶん助けられました。

Visual Studio Live Share は自分の Visual Studio から他の Visual Studio に接続してコード編集することを基本とするコラボレーション用の拡張です。ソースコード以外にも相手ホストのいろいろな機能に接続することができます。

  • ランゲージサーバーへの接続(相手の環境を使ってコード補完ができる)
  • プロジェクトファイル全体の grep 検索
  • 任意のポートにブラウザで接続
  • ターミナルに接続
  • デバッグ実行

自分の環境にソースコードや実行環境を持っていない状態でも、相手の環境を使って簡単にプログラミングを開始することが可能で、相手の環境で発生している不具合を手元で再現させながら修正するといったことが数分の準備で簡単に行えます。

接続はインターネット上に配置された Micsoroft の Live Share サーバーを介して行いますので、残念ながらクローズドな LAN 環境では使うことができませんが、代わりにルーターのポート開放など難しい設定なしに、どんな場所にいても機能を使うことが出来ます。

またクロスプラットフォームにも対応していますので、Windows から macOS に接続して動作させるようなことも可能です。ということで、いくつかの例を紹介してみたいと思います。

ランゲージサーバーとコンソール接続

Rust を編集している Linux をホストに Windows ゲストから接続して、Linux 上にあるソースコードと Rust Language Server に接続してみたところです。

補完も定義表示も相手の環境のものを使っていい感じに動作します。

Windows から Linux への bash の接続も OK で、ショートカットキーや Powerline の制御文字などもそのまま転送してくれます。相手の環境のコマンドがそのまま打てますので、Windows 側からビルドなんかもすることができます。すごい。

デバッガーへのアタッチ

ホスト側で起動したデバッガーにアタッチして、ゲストからステップオーバーやウォッチなどの操作をすることができます。 Linux / Rust / LLDB ホスト環境に Windows ゲストからアタッチした図です。

デバッグコンソールなども転送されてきます。

たとえば、Windows で動く LLDB を準備するのはちょっと大変ですが、このように Linux に接続できると簡単に環境を揃えることができます。(残念ながらゲスト側からデバッグの実行構成を起動することができませんでした。”The host doesn’t allow starting the debugger.” と言われてしまうので何かできそうな気がするのですが、引き続き調査してみます)

任意のポートにブラウザから接続

ウェブ開発をしている時に大きな力を発揮するのが、サーバーへのポート開放です。

今度は Windows で動作している Java ウェブアプリ環境に、macOS から接続してみます。Windows ホストに macOS ゲストから接続した上で、Live Share の Share Server を選択して、開けるポートを “localhost:8080” のように指定します。(この例は SpringBoot アプリケーションなので 8080 で起動します)

あとは macOS ゲスト側のブラウザから http://localhost:8080/ にアクセスするだけです。(localhost なのが不思議な感じですが VS Code がプロキシーして、ホストに接続してくれます)

macOS から Windows のターミナル(Powershell)に接続することもできますので、gradle を動かしてアプリケーションの起動停止をするようなこともでき、ソースコードを編集しながら、ブラウザで動作確認といったことがリモートで簡単にできるようになっています。

今年はお隣におられる方の環境につないでプログラムを修正したり、一番遠くは北海道・福岡間でプログラムの修正を行ったりしました。

特に不具合の修正では、それを手元で再現させる環境をつくるまでに時間がかかるものですが、VS Live Share を使えば不具合が起きている環境に接続してすぐ修正を開始することができます。(これができなかったら修正箇所が分らなかったのではないかということもありました。

VS Code はそれ自体の導入も手軽ですので、そのようなシチュエーションに出会ったら試してみてはいかがでしょうか。

あ、ひとつまえのポストも VS Code ネタですのでよければご一緒にどうぞ。。

AsciidoctorとGradleでつくる文書執筆環境

技術文書を書く環境が欲しくなり、VS Code と Gradle を使って Asciidoc 文書を執筆する環境を整えてみました。 お手軽に構成できて、300ペジくらいの文書でも耐えられそうです。

てなわけでまた来年もよろしくお願いいたします。。 🙂

Alacritty 高速ターミナルエミュレーター

Rust 方面で気になっていた高速ターミナルエミュレーターである Alacritty を Ubuntu 18.10 にて試してみました。

まだ alpha levelとのことですが、2日ほど使い続けてみましたが不具合もなく、むしろ速くてとても快適でしたので常用させていただいております。:)

https://github.com/jwilm/alacritty

Alacritty is the fastest terminal emulator in existence. Using the GPU forrendering enables optimizations that simply aren’t possible in other emulators.Alacritty currently supports macOS and Linux, and Windows.

サイトに導入手順がありますが、Ubuntu の場合はライブラリ依存関係と Rust のツールチェインを入れビルドをかけると .deb が作成されパッケージとしてインストール・することができます。

Rust ツールチェインの導入

https://rustup.rs/

curl https://sh.rustup.rs -sSf | sh

依存関係の導入

https://github.com/jwilm/alacritty/blob/master/INSTALL.md#debianubuntu

apt-get install cmake libfreetype6-dev libfontconfig1-dev xclip

Alacritty のビルドとインストール

https://github.com/jwilm/alacritty#debianubuntu

git clone https://github.com/jwilm/alacritty.git
cd alacritty
cargo install cargo-deb
cargo deb --install

起動すると設定ファイルが、$HOME/.config/alacritty/alacritty.yaml として作成されますので、画面サイズやフォントの設定をします。自分はプロンプトなどに Powerline を使っていますのでフォントの指定を Ricty Diminished w/ Powerline patched に変更しました。

font:
  # Normal (roman) font face
  normal:
    family: Ricty Diminished Discord for Powerline
    # The `style` can be specified to pick a specific face.
    # style: Regular

ということで起動してみると、、

フォントずれも発生せず日本語環境でもいい感じに動作しました!

IM による漢字入力についてはまだインライン入力ができないようですが、カーソル位置の適切な場所に吹き出し形式でインサートすることができますので、大量に日本語を入力しない限り違和感はないと感じました。

マウスエミュレーションも動作し、スクリーンショットのように tmux を動作させるとペインの移動やスクロールなどがマウスで行うことができます。

tmux(アプリケーション)にマウスを奪わせたくない場合は、一般的なターミナルエミュレーターのアサインと同様 Shift キーを押しながらマウス操作すればOKです。また OS に対するコピーアンドペーストは、Shift + Ctrl + c や v などが使えます。

検証もかねまして 2日ほどがちゃがちゃいじってみましたが、不具合を起こすこともなく快適に動作しています。すごい 🙂

(画面を覆い尽くす)Ctrl + Enter のフルスクリーンモードがあれば自分的には完璧だったのですが、issue も上がっていましたので少し経てば実装されるものと思います。

Alacritty は Windows 版も開発が進んでおり先日起動が成功したようです。

ためにし WSL(bash on Windows)で試したところ、マウスエミュレーションが動作しませんでしたが、かなり動き始めているようです。(残念ながら IM による日本語入力窓はへんな位置にポップアップしてしまいました)

Windows 上の Alacritty で WSL を起動する場合は $HOME\alacritty.yaml のシェルの指定を次のようにします。

shell:
  program: "C:\\Windows\\sysnative\\bash.exe"

Rust でつくられているということで高速で強固。自分は Rust の勉強中にてソースコードもかなり参考になりそうです。継続してウォッチしていきたいと思います。 🙂

関連