Calmery.me

みっかぼうずにならないようがんばる

WebAssemblyを試してみた

メモ.WebAssembly については WebAssembly のコンセプト - WebAssembly | MDN を参考にするとよさそう.

さらに追記

$ nvm install 8.0.0
$ nvm use 8.0.0
$ node -v
v8.0.0
$ node
> WebAssembly
{}

新しくリリースされた v8.0.0 の Node.js ではデフォルトで使えるようになってるみたい.
追記のところで main 関数がとか言っていたけど Emscripten を使えばその辺りもいい感じしてもらえるっぽい.

見直してみると,色々と勘違いしているところもあるけれど記録としてそのままに.

追記

main 関数があっても動かせた.と言うか足りない関数は補ってねという感じなのかな.
楓 software: wasm に clang を使った場合に C 内で関数呼び出しする場合に出るエラー

(index):25 Uncaught (in promise) LinkError: WebAssembly.Instance(): Import #0 module="env" function="printf" error: function import requires a callable
    at fetch.then.then.then.module (http://localhost/:25:24)
    at <anonymous>
$ s2wasm main.s -o main.wast --allocate-stack 1024
const loadWebAssembly = ( filename, imports ) => {
    return fetch( filename )
        .then( response => response.arrayBuffer() )
        .then( buffer => WebAssembly.compile( buffer ) )
        .then( module => {
            imports = imports || {}
            imports.env = imports.env || {}

            imports.env.printf = arg => console.log( arg )

            if( !imports.env.memory )
                imports.env.memory = new WebAssembly.Memory( {
                    initial: 256
                } )

            if( !imports.env.table )
                imports.env.table = new WebAssembly.Table( {
                    initial: 18,
                    element: 'anyfunc'
                } )

            return new WebAssembly.Instance( module, imports )
        } )
}

loadWebAssembly( 'main.wasm' ).then( instance => {
    const exports = instance.exports
    exports.main()
} )

Google Chrome Canary や Node.js で WebAssembly を読み込むための関数を作った(よくわかっていないのでものによっては動かないかも)
WebAssembly/loadWebAssembly.js at master · calmery/WebAssembly · GitHub

環境構築

Mac OS X 10.11.6
Google Chrome Canary 59.0.3030.0 canary (64-bit)
Firefox Nightly 54.0a1 (2017-03-05) (64 bit)
Node.js v8.0.0-pre

おまじない.

$ pyenv global 2.7.12

The LLVM Compiler Infrastructure Project

$ cd
$ mkdir workspace
$ cd workspace
$ git clone http://llvm.org/git/llvm.git
$ cd llvm/tools
$ git clone http://llvm.org/git/clang.git
$ cd ../projects
$ git clone http://llvm.org/git/compiler-rt
$ cd ~/workspace
$ mkdir llvm_build
$ cd llvm_build
$ cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr/local -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly ../llvm
$ make -j 8
$ sudo make install

GitHub - WebAssembly/binaryen: Compiler infrastructure and toolchain library for WebAssembly, in C++

$ cd ~/workspace
$ git clone https://github.com/WebAssembly/binaryen.git
$ cd binaryen
$ cmake . && make
$ sudo make install

GitHub - WebAssembly/wabt: The WebAssembly Binary Toolkit

$ cd /usr/local/src
$ sudo git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ sudo make gcc-debug-no-tests
$ cd
$ echo "export PATH=$PATH:/usr/local/src/wabt/out/gcc/Debug/no-tests/" >> .bash_profile

Node.js は v8.0.0-pre でないと Google Chrome Canary や Firefox Nightly で動く WebAssembly が動かない.そのため v8/nodenodejs/node を使う.Nightly builds の v8.0.0-nightly20170307efaab8fccf も試したが動かなかった.
GitHub - nodejs/node: Node.js JavaScript runtime

$ git clone https://github.com/nodejs/node.git
$ cd node
$ git checkout -b canary origin/canary
$ ./configure
$ make -j4
 if [ ! -r node -o ! -L node ]; then ln -fs out/Release/node node; fi
$ ./out/Release/node -v
 v8.0.0-pre
$ ./out/Release/node --expose-wasm
> WebAssembly
{}

GitHub - v8/node: Node.js JavaScript runtime

$ git clone https://github.com/v8/node.git
$ cd node
$ git checkout -b vee-eight-lkgr origin/vee-eight-lkgr
$ ./configure
$ make -j4
 if [ ! -r node -o ! -L node ]; then ln -fs out/Release/node node; fi
$ ./out/Release/node -v
 v8.0.0-pre
$ ./out/Release/node --expose-wasm
> WebAssembly
{}

WebAssembly に変換する

C や C++ で書かれたコードを WebAssembly に変換する.

extern "C" {
    int fib( int n ){
        switch( n ){
            case 0: 
            	return 0;
            case 1: 
            	return 1;
            default: 
            	return fib( n - 2 ) + fib( n - 1 );
        }
    }
}

C や C++ のコードをまず LLVM bitecode に変換し,次にアセンブリに変換する.その後 S 式に変換しバイナリに変換する.

$ clang++ -v
clang version 5.0.0 (http://llvm.org/git/clang.git fc2d9054c86c9b8acbc98b06f1b9d8aa0e5d40f9) (http://llvm.org/git/llvm.git 58580c59aefa59fb793a72dd5abeb6a60340a317)
$ clang++ main.cpp -emit-llvm --target=wasm32 -Oz -c -o main.bc
$ llc -version
LLVM (http://llvm.org/):
  LLVM version 5.0.0svn
...
$ llc main.bc -march=wasm32 -filetype=asm -o main.s
$ s2wasm main.s > main.wast
$ sexpr-wasm main.wast -o main.wasm

追記
sexpr-wasm ではなく wast2wasm を使うようにする.

$ wast2wasm main.wast -o main.wasm

動かしてみる

GitHub - 1984weed/webassembly-sample を参考にした.

const loadWebAssembly = ( filename, imports ) => {
    return fetch(filename)
        .then( response => response.arrayBuffer() )
        .then( buffer => WebAssembly.compile( buffer ) )
        .then( module => {
            imports = imports || {}
            imports.env = imports.env || {}
            imports.env.memoryBase = imports.env.memoryBase || 0
            imports.env.tableBase = imports.env.tableBase || 0
            
            if( !imports.env.memory )
                imports.env.memory = new WebAssembly.Memory( {
                    initial: 256
                } )

            if( !imports.env.table )
                imports.env.table = new WebAssembly.Table( {
                    initial: 0,
                    element: 'anyfunc'
                } )

            return new WebAssembly.Instance( module, imports )
        } )
}

loadWebAssembly( 'main.wasm' ).then( instance => {
    const exports = instance.exports
    console.log( exports ) // => Object {memory: W…y.Memory, fib: function}
} )

Node.js はオプションで --expose-wasm をつける必要がある.

// Node.js v8.0.0-pre
// node --expose-wasm
const fs = require( 'fs' )
const buffer = fs.readFileSync( './main.wasm' )

const arrayBuffer = new Uint8Array( buffer ).buffer

WebAssembly.compile( arrayBuffer ).then( module => {
    let imports = {}
    imports.env = {}
    imports.env.memoryBase = 0
    imports.env.tableBase = 0

    if( !imports.env.memory )
        imports.env.memory = new WebAssembly.Memory( {
            initial: 256
        } )

    if( !imports.env.table )
        imports.env.table = new WebAssembly.Table( {
            initial: 0,
            element: 'anyfunc'
        } )

    const instance = new WebAssembly.Instance( module, imports )
    console.log( instance.exports )
} )
// Node.js v7.7.1
Promise {
  <rejected> Error: WebAssembly.compile(): Wasm decoding failedResult = expected version 0c 00 00 00, found 01 00 00 00 @+4

ちなみに C++ では名前に修飾がついてしまうので extern "C" で囲う必要がある.

console.log( instance.exports ) // => Object {memory: W…y.Memory, _Z3fibi: function}
> WebAssembly.Instance( module ).exports
{ _Z3fibi: [Function: 0], memory: Memory {} }

サンプルなどを見ていると WebAssembly ではなく Wasm を使っているものがある.だが Google Chrome Canary や Firefox Nightly では Wasm がない.

// Replace
Wasm.instantiateModule() // => ReferenceError: WASM is not defined
WebAssembly.Instance()

Node.js v7.2.1

Getting Started With WebAssembly in Node.js | www.thecodebarbarian.com
この方法は v.7.7.1 では動かなかった.sexpr-wasm demo も s2wasm で作られた wast がそのままでは使えない.書き換えれば使えはしたが v7.7.1 で動かなかった.

Emscripten で変換する

Developer’s Guide - WebAssembly を参考にして導入する.
Main — Emscripten 1.37.22 documentation
latest ではなく incoming を使う.

$ git clone https://github.com/juj/emsdk.git
$ cd emsdk
$ ./emsdk install sdk-incoming-64bit binaryen-master-64bit
$ ./emsdk activate sdk-incoming-64bit binaryen-master-64bit
$ source ./emsdk_env.sh
$ emcc
INFO:root:(Emscripten: Running sanity checks)
WARNING:root:no input files
$ mkdir hello
$ cd hello
$ echo '#include <stdio.h>' > hello.c
$ echo 'int main(int argc, char ** argv) {' >> hello.c
$ echo 'printf("Hello, world!\n");' >> hello.c
$ echo '}' >> hello.c
$ emcc hello.c -s WASM=1 -o hello.html
$ ls
hello.c		hello.html	hello.js	hello.wasm
$ node -v
 v8.0.0-pre
$ node --expose-wasm hello.js
trying binaryen method: native-wasm
asynchronously preparing wasm
binaryen method succeeded.
run() called, but dependencies remain, so not running
Hello, world!

ここで生成された hello.wasm は動かしてみるのように読み込むことはできなかった.

> WebAssembly.compile( arrayBuffer ).then( ...
Promise { <pending> }
(node:92075) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): LinkError: WebAssembly.Instance(): Import #0 module="env" function="DYNAMICTOP_PTR" error: global import must be a number
(node:92075) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

どうやら main 関数があると動かないみたい.出力されたファイルをそのまま実行すると動きはする.Google Chrome Canary や Firefox Nightly も出力された hello.html では動くのだが動かしてみるのように読み込むと動かない.以下のようにオプションを指定すると動いた.だがこちらも main 関数があると動かない.この辺りはもうちょっと調べたい.(追記 未確認だけど main 関数というよりは存在しない関数を呼び出しているからっぽい.ここでは main 関数内で呼び出している printf 関数のせいかも)

$ emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -s 'BINARYEN_METHOD="native-wasm"' -o hello.wasm
$ ls
main.cpp main.wasm
$ node --expose-wasm
> const fs = require( 'fs' )
> const buffer = fs.readFileSync( 'hello.wasm' )
> const arrayBuffer = new Uint8Array( buffer ).buffer
...

動かしてみる のコードを使用して動かした結果はこうなった.

emcc hello.c -s WASM=1 -o hello.html

main 関数があってもなくても読み込めない.

emcc hello.c -Os -s WASM=1 -s SIDE_MODULE=1 -s 'BINARYEN_METHOD="native-wasm"' -o hello.wasm

main 関数がなければ Google Chrome Canary,Firefox Nightly,Node.js v8.0.0-pre で動かせた.
最適化の -Os を抜くと読み込めなかったりとよくわからない.

Rust から変換する

Compiling Rust to WebAssembly Guide – Hacker Noon
結局は Emscripten と同じみたい.

fn main(){
    println!( "Hello World" );
}
$ rustc --target=wasm32-unknown-emscripten hello.rs -o hello.html
$ node -v
 v8.0.0-pre
$ node --expose-wasm hello.js
trying binaryen method: native-wasm
asynchronously preparing wasm
binaryen method succeeded.
run() called, but dependencies remain, so not running
Hello World

まとめ

Emscripten を使えば良さそうだけどよくわからなかった.調べる.

稲荷山登山チャレンジ(成功)

友達と京都旅行,伏見稲荷大社に行ってきた.以前来たときは時間がなくて本殿で引き返したのだけど今回は登ることができた.満足.夕方と翌日のお昼,合わせて二回登ったのだけど雰囲気が違っていて面白い.夜は本当に何か出そう.夕方から夜にかけては人も少ないし(暗くて危ないけど)いいかも.

きれいだった😆 #theta360 - Spherical Image - RICOH THETA
京都駅から稲荷駅までは JR の奈良行きに乗って二駅.近い.快速は停まらないみたい.初めは日も暮れかけていたので寄り道せずに登った.翌日は記録をとってみた.多少抜けはあるけどこんな感じ.これで歩いた距離は 7 キロらしい.

思い出というか,一度目は飲み物を買い忘れていて途中の自販機で買った.当然といえば当然なのだけど高い.飲み物は登る前に買った方がよさそう(当たり前)

翌日は参拝できるところは全て行こうと五円玉を三十枚ほど持っていったのだけど足りなかった.倍はいる.この日は休日で五円玉を作るのに京都駅近くのゆうちょでひたすら五円玉を引き出した.今思うともはや不審者だった.

手荷物は稲荷駅から本殿に向う途中,右手の参集殿にあるロッカーに預けた.駅のロッカーは全く空いてなかったけど,こっちはかなり空いていた.意外と知られてないのかも.

案内には山頂まで一時間と書いてあるけど,休まず歩いて 30 分くらい.思ったより近い.二回目はお参りしつつ,寄り道しつつだったので時間がかかった.登り始めたのが 11 時半,山頂に着いたのが 13 時半,本殿に戻ってきたのが 15 時で三時間半くらい.途中でご飯を食べたりお参りしたりで歩いたのは二時間くらいだと思う.

ところどころ寄り道するのが楽しい.力松社近くからいける清瀧はすごい.行ってよかった.ジブリに出てきそう.ただ帰りが登りできつい.
f:id:calmery:20170225034614j:plain
f:id:calmery:20170225012858j:plain
四ツ辻からは道が二つに分かれていた.どちらに行っても山頂に行くことはできる.だけどここは登ってきた方から見て右側の道に進んだ方が良さそう.まっすぐ行くと緩やかで楽かと思いきや最後にトドメを刺しにくる.鬼畜.ここを登るのは相当きつそう.

Post from RICOH THETA. - Spherical Image - RICOH THETA
楽しかった.また秋くらいに行きたい.春は花粉症で夏は暑い,冬は寒いし.秋くらいがちょうどいい.まあ人が多そうだけど.

伏見稲荷大社とは全く関係がないのだけど嵐山の トップページ|京都嵐山 鯛茶漬け専門店-鯛匠HANANA- で食べることができる鯛茶漬けがおいしい.本当においしい.お店が開く 30 分前に行ったからか待たずに食べることができた.あんまり遅いと待つことになるので早めに.というか予約しておけばいい.
f:id:calmery:20170225021237j:plain
友達と.わーい!すごーい!おいしー!

Post from RICOH THETA. - Spherical Image - RICOH THETA

Blenderでモデルを作りthree.jsで表示する

Blender で作成したモデルとアニメーションを three.js を使いブラウザ上で表示できたのでメモ.今月の 17,18 日に開催された 林業応援ハッカソン でブラウザ上でモデルを動かすのに必要だった.

準備

Blender にアドオンを追加する.これは three.js が提供するもので,このアドオンを使用すると three.js で使用できる JSON 形式でモデルを書き出せるようになる.README か Three.js の JSONLoader のメモ - Qiita を参考にする.というか見た方が早いと思う.

$ cp -r three.js-master/utils/exporters/blender/addons/io_three /Users/calmery/Library/Application\ Support/Blender/2.77/scripts/addons/io_three

アドオンを追加後,Blender の User Performance から Add-ons を開き,Import-Export: Three.js Format にチェックを入れれば three.js 用の JSON 形式に書き出せるようになる.
f:id:calmery:20161224234440p:plain
Blender の他にも 3ds MAX や Maya,Revit 用のアドオンもあるみたい.どれも README に手順が書かれているのでそれを見ながらやればできそう.

書き出す

わかりやすいようにキューブに色をつけた.
f:id:calmery:20161225000501p:plain
Export から Three.js(json) を選択する.
f:id:calmery:20161225041940p:plain
Face materials にチェックを入れて書き出す必要があるみたい.チェックを入れずに書き出してマテリアルが表示されず大変だった.
f:id:calmery:20161224234922p:plain

読み込む

three.js の JSONLoader を使い,書き出した JSON からモデルを読み込む.

<body></body>

<script src="resources/js/three.min.js"></script>
<script src="resources/js/TrackballControls.js"></script>
<script>
    let scene = new THREE.Scene()

    let renderer = new THREE.WebGLRenderer( { antialias: true } )
    renderer.setSize( 600, 400 )
    renderer.setClearColor( 0xffffff, 1 )
    document.body.appendChild( renderer.domElement )

    let camera = new THREE.PerspectiveCamera( 60, ( 600 / 400 ), 1, 1000 )
    camera.position.set( 0, 0, 300 )

    let directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 )
    directionalLight.position.set( 0, 150, 1000 )
    scene.add( directionalLight )

    let trackball = new THREE.TrackballControls( camera )

    let loader = new THREE.JSONLoader()
    loader.load( 'blenderToThreejs.json', ( geometry, materials ) => {
        let faceMaterial = new THREE.MeshFaceMaterial( materials )
        mesh = new THREE.Mesh( geometry, faceMaterial )
        mesh.position.set( 0, 0, 0 )
        mesh.scale.set( 50, 50, 50 )
        scene.add( mesh )

        let animation = ( function(){
            requestAnimationFrame( arguments.callee )
            renderer.render( scene, camera )
            trackball.update()
        } )()
    } )
</script>

ちゃんと表示することができた.
f:id:calmery:20161225001648p:plain
もし,書き出したモデルが正しく表示されない場合は Blender Three.js エクスポート その3( テクスチャなどがエクスポート出来ない場合) を参考にする.人のモデルを作成したまでは良かったが,頭だけ表示されてないなんてことにならないようにしたい(なった)

アニメーション

キューブが下に落ちていくアニメーションを作成した.
f:id:calmery:20161225012147p:plain
同様に Export の Three.js(json) から保存する.このとき Animation の Morph Animation と Settings の Textures にチェックを入れる必要がある.
f:id:calmery:20161225012207p:plain
f:id:calmery:20161225012204p:plain
アニメーションを再生できるように修正する.ほとんど Blender Three.js エクスポート その4( モーフアニメーション) そのままなのは気にしてはいけない.

loader.load( 'animation.json', ( geometry, materials ) => {
    // 全てのマテリアルのモーフターゲットの値を true にする
    for( let i=0, l=materials.length; i<l; i++ ) materials[i].morphTargets = true;

    let faceMaterial = new THREE.MeshFaceMaterial( materials )
    mesh = new THREE.Mesh( geometry, faceMaterial )
    mesh.position.set( 0, 0, 0 )
    mesh.scale.set( 50, 50, 50 )
    scene.add( mesh )

    let current_frame = 0,
        total_frame   = 50

    let animation = ( function(){
        requestAnimationFrame( arguments.callee )

        last_frame = current_frame
        current_frame++

        // 現在のフレーム数が合計のフレーム数以上になったら現在のフレームを始めに戻す
        if( total_frame <= current_frame ) current_frame = 0

        mesh.morphTargetInfluences[last_frame] = 0
        mesh.morphTargetInfluences[current_frame] = 1

        renderer.render( scene, camera )
        trackball.update()
    } )()
} )

動かしてみた.
f:id:calmery:20161225034936g:plain

まとめ

Blender で作成したモデルやアニメーションを three.js 上で動かすことができた.