C++ Advent Calendar 2012 / day 4th : Native-client vs. HTML5 ; C++ in the web-client-world!
C++ Advent Calendar 2012 / 4th day
- (!) この記事はC++ Advent Calendar 2012の参加記事です ヽ(=´▽`=)ノ
- 記事の公開と本編内容が4th dayのリミットより数時間遅れてしまいました事をお詫び申し上げます。
Native-client vs. HTML5 ; C++ in web-client-world!
諸元
- Published: 2012.12.4
- By: Usagi Ito <usagi@WonderRabbitProject.net>
- Environments: (to see the last section of this entry)
Abstract
世間ではHTML5が一般にも話題になり始め、徐々に持て囃される様になり、次第に一般にも浸透して来た様な気がする2012年のクリスマスシーズン、
(…中略…)、
ウェブはコンパイルされるべきである( ・`ω・´)
と、言う訳でコンパイルを恐れるじゃぱすくりぷたーどもをC++ぱわーでひれふしsdpgこrthkよりネイティブにアプローチを掛けつつもJavaScriptの柵に囚われたHTML5に対しNative-clientの優位性を示すと共にC++erにC++のウェブクライアントのお仕事も下さいフラッシャーなにそれ変態なのHTML5にプラスしてNative-clientを協調的に動作する様に組み込みよりパワフルな底力と表現力の可能性を示したい。
Introduction
Native-client
Native-clientはウェブブラウザー上でネイティブコードアプリケーションを実行する為の枠組みで、 Chromium(≈Chrome) 14 からブラウザーへの実装が進められ執筆時点最新の Chromium 24 でも勿論使用可能です。
一般に従来ウェブブラウザー上でアプリケーションを動作させる方法は、FLASH、Javaアプレット、JavaScript、その他のマイナースクリプト環境などが考えられますが、何れもクライアントのマシン性能を生かし切るには高級過ぎたり、マルチスレッディングやリアルタイム性能に欠ける、実行環境が一般的でない、などしています。尤も最後の実行環境の一般性についてはNative-clientは潜在的にはChromeのシェアを以って普及していると言うのはやや乱暴な状況ではありますが、Chromeウェブストアから既にSecure Shell、Lime Berta(NaClテスト版)、Bullet Physics NaCl Test、Ogre Sample Browser NaCl、などNative-clientを用いたサンプルや実用アプリの開発を見ることができます。と、いうかNative Clientのクオリティー高すぎ!家庭用ゲーム機と同クオリティーを実現したブラウザゲームまとめ | Chrome Lifeとか実際とてもゲームです。
HTML5
みんな大好きHTML5。WHATGが"Living Draft"として永遠に完成させない(もちろん良い意味で表現している)方針を取りながらW3Cとやんやしつつ成長中の新型HTML関連規格。"関連"と付けたのは、既に之が純粋なHTMLというデータ記述言語の枠を超えて、CSSは勿論の事、スクリプト言語たるJavaScriptや、通信のリアルタイム性や接続性を高めるWebSocket、ウェブとマルチメディアデバイスとのHTMLによる接続が実現しつつあるWebRTCなど、ほか細かい仕様もたくさん(正直全部把握してない)。
しかし、HTML5のプログラマビリティはJavaScriptだけに掛かっている。JavaScriptは2014年に次の規格を発表する事になっていた気はするが、この言語はここ10年、規格の更新云々でもめ続けて今に至っている。不安は大きい。
HTML5で追加された今回も扱うcanvas、それにvideoやaudioの制御は勿論の事、WebGL(≈OpenGL)、WebCL(≈OpenCL)に至ってもそのAPIはJavaScriptのみを大前提としている。実に馬鹿げている。ブラウザーはスクリプト言語の処理系を切り離し、ユーザーに多様なスクリプトサポートを提供する枠組みを整備すべきではなかろうか(例えば<script type="application-x/haskell">とかもっと手軽にできて欲しいし、Native-clientやそこまでいかなくともJavaScriptではなくPythonやRubyなどももっと手軽に使えても良いのではなかろうか、速度的にはJavaScriptの方がマシらしいけど)。
JavaScriptは変態的な最適化が施されているであろう処理系V8を以ってしても、所詮はJavaScript、少なくとも現在のJavaScriptの言語仕様では高速化やよりネイティブな仕組みの実装に対し柵が大きく、そもそもイベント駆動に最適化され、まともなスレッディングやリアルタイム要求に答えらず、データ構造やメモリーの取り扱いも不自由なJavaScriptでなんでGLだのCLだの…無理矢理過ぎる。
そこで、C++、Native-clientの出番です( ・`ω・´)
Contents
前置きが長くなりましたが、今回の記事で取り扱う内容について整理します。
- ① HTML5/canvas を用いたパーティクルシステムとピクセルベースドレンダリングによるベンチマークページ
- デモ
- ソース
- 解説
- ② Native-client の準備と雛形
- 準備
- 雛形
- ③ Native-client による①へのC++ぱわー注入
- デモ
- ソース
- 解説
また、今回は時間が少々足りなかった都合(ゴメンナサイゴメンナサイ)、そもそものJavaScriptアプリの最適化(但しこれはメンテナンス性の著しいトレードオフを伴わなければ真の最適化はできないくそ言語なのだけど;w;)や、HTML5/WebGL、Native-client/OpenGL ES 2.0等については年内に余力が取れれば追加記事にしようと思います。
① HTML5/canvas を用いたパーティクルシステムとピクセルベースドレンダリングによるベンチマークページ
- デモ
こんな感じの何かとっても重いかもしれないけれどクリスマスっぽいデモが(たぶん)動きます。
画面左上に SCORE と FPS を表示するベンチマークアプリになっています。FPSは凡そ現在のフレームレート、SCOREは一定間隔でFPSを基に加算されデモの終了時に増加が止まる仕組みです。
フルHD(1920x1080)専用です(ぉ
F11など押して全画面でお楽しみ下さいませ。
ちなみに、私の開発環境(記事末尾参照)での Chrome 23 のスコアは 464 でした。
(※動かない場合はこのデモの実行に対してはマシンスペックが足りていないか、canvasやaudioの使えないレガシーなブラウザーを使っているのが原因かもしれません。デバロッパーツールでコンソールを見るとエラーが出ているかもしれません。基本的にはサポートはしませんが悪しからず・x・)
- ソース
- 解説
基本的にはコンピューターグラフィックスやシミュレーション分野での基礎技術の1つ、パーティクルシステムをJavaScriptで構築し、表示画面内をピクセル(≠ディスプレイのピクセル)に分割しパーティクルに基づいてピクセルの色付けを行なっています。なお、初期状態の設定ではパーティクルが直接描画される事はありません。
パーティクルは単純なオイラー法によるニュートンの運動方程式により、重力の影響を受けつつ、速度、位置を変化させます。また、パーティクルは色と半径を持っています。
ピクセル(しつこい様ですがディスプレイのピクセルではありません)は画面を所定の分解能で分割した領域で画面をタイル状に埋め尽くしています。そしてピクセルは毎フレーム、運動するパーティクルとの距離から色を更新しています。
ソースコード: https://github.com/usagi/cpp_advent_calendar_2012_4th/blob/gh-pages/HTML5/main.html
HTMLファイルに定義されたHTMLタグはDOCTYPE宣言の他は meta 、 style 、 script のみです。ブラウザーに読み込まれると、
window.addEventListener('load', main);
が定義され、ページロード後に main 関数が呼ばれます。プログラム全体の仕組みとしては、 config が各種設定をまとめ、実行時の一時領域は tmp に整理される様になっています。一部の config はデベロッパーツールのコンソールから値を変える事で即座に適用できます。例えば、
conf.force_gravity = false;
とか評価すると重力が無くなります。また、
conf.present_particles = true;
とか評価するとパーティクルの位置と半径と色が分かる様に描画される様になります。
より詳細についてはソースコードをご覧下さい。
② Native-client の準備と雛形
- 準備
Native-clientの開発環境導入については、openSUSE-12.2にnative-client開発環境を入れる - C++ ときどき ごはん、わりとてぃーぶれいく☆に書いた様にとても簡単かつLinuxディストリビューターやOS付属のパッケージ管理システムに依存しません。さくっと入れちゃいましょう。環境変数 NACL_SDK_ROOT を通して置く事をお忘れなく。
- 雛形
Native-clientの開発環境を整えると、個別のバージョンのPepper SDKと同時にその中にexamplesも導入されます。お約束のヘロウワアルドも入っていますので、まずはその辺りをちょいちょい試したり、元にすると良いかもしれません。
しかし、SDK付属のexmapleに入っているプロジェクトではMakefileが冗長な割に低機能だったり、glibcツールチェインを使う際の .nmf 定義は手作業で書く必要があるなど不便です。
と、言う訳で雛形を用意してみました。
機能面では雛形としてまったく足りていない部分や調整途中の部分も多いのですが、ディレクトリー構造やMakefileは役立つかもしれません。特に、このMakefileでは生成する.nexe(Native-clientの配布用の実行ファイルです)を基に依存するライブラリーの .so の定義など面倒な .nmf の作成を完全に自動化しています。
また、JavaScriptとのメッセージング周りなどもmain.jsに実装済みの状態となっていますのでそこら辺もNative-clientを始めてお試ししようかな、そんな時の雛形としては便利かもしれません(main.jsのwrp.nacl.prototype.on_message/post_message)。このmain.jsも238行目のaddEventListener('load',main)によりmain関数が開始する実装になっています。コンソールへのログ出力がデフォルト有効ですので、この雛形は.nexeから標準出力へのログも勿論、JavaScriptレベルでも以下のようなログがブラウザーのJavaScriptコンソールに吐き出されます。テスト、デバッグのお供にどうぞ。
このJavaScript側のログ出力は当然負荷が掛かりますので、main.jsの冒頭辺りでの
wrp.etc.log_off = true
とか、それに続くwrp.logオブジェクトの実装を参考に適宜に切り替えて使用して下さい。
このほか、雛形にはx86_64版とi686版のビルドから、ローカルテストサイト作成、ローカルテストサイトをdarkhttpdで立ち上げたり、ブラウザーを立ち上げたりする機能も一応程度ですが付いています。
% make % make _site
これでNative-client実行ファイルのビルドからローカルテスト用のディレクトリー構造を作成できます。
darkhttpdが使える場合は、
% make && make test-server
とかするとテストサーバーを起動します。ログ流し、分離の都合、フォアグラウンドで通常動作させます。クライアントでのテストサーバーへの接続は、
% make test-client
とか別端末を開いてから実行する設計です。これは、ChromiumブラウザーがNative-clientの標準出力及び標準エラー出力にLinuxであれば対応してくれる(Windowsではcmd.exeからChromiumを起動しても何も出ませんはずです)為、そのログ取りに便利という事で別端末を前提とした仕様にしてあります。
実際、ログ取りのサンプルとして、 include/wrp/log.hxx なるちょこちょこと以前書いてあったログ取りクラスをこの雛形のプロジェクトでは利用する様になっていますので、すぐに標準出力等の動作を確認できる状態になっています。
ソースコード以外の生成物、.nexe、.nmf、_siteなどを削除したい場合は、
% make clean
とします。
さて、Native-clientの動作する仕組みについては、
HTMLがロードされる→HTMLのembedにより対応ブラウザー(Chromium)であればNative-clientが起動し
→.nmfを読み込みNative-client実行ファイル.nexeがロードされ→.nmfより必要なら.soもロードし→pp::CreateModuleが実行されpp::Module(から派生した自分のアプリのクラス)が作られ→pp::Module::CreateInstanceが実行されpp::Instance(から派生した自分のアプリのクラス)が作成され→インスタンスが動作する
という流れです。動作中のpp::Instanceは必要ならばHTMLページのJavaScriptとの相互のメッセージングを備えていますので、これだけでも割とアレコレできます。他にAudioやOpenGL ES 2.0も扱うことができますが、それはまた別の機会に。
動作の流れは実際のローカルテストサイトまでビルドしてから、その内容物で追いかけた方が良いでしょう。(.nmfは.nexeから生成する為、雛形のソースコード中には含まれていないなどの都合もありますし。)
なお、雛形ではアプリ名を app_name で作成してありますので、そこら辺は使う際に適宜に書き換えて下さい。また、 bland_name もソースコード中で名前空間の定義に使っていますので、こちらも同様に書き換えて使用して下さい。書き換えはfind|sedとfind|tr|mvに慣れて居ればそれでも良いのですが、こんな時はmsrpを使うと楽ですよ。
% msrp app_name cppac2012_4th . ( changed) ./main.js ( changed) ./Makefile ( changed) ./main.html ( changed) ./src.cxx/common.hxx (renaming f) ./src.cxx/app_name-client-nacl.cxx => ./src.cxx/cppac2012_4th-client-nacl.cxx trying: plain rename % msrp bland_name WonderRabbitProject . ( changed) ./src.cxx/common.hxx
こんな具合でファイルの中身の文字列、ファイル名を一気に置換できます。正規表現にも対応しているのでmsrpを使えると何かと便利です。
③ Native-client による①へのC++ぱわー注入
- デモ
(※Chromium 14以上でNative-clientのデモが動作しない場合、お使いのChromeの chrome://flags/ より、(野良)Native-clientの実行を許可する必要があるかもしれません。)
- ソース
- 解説
私の環境ではスコアが874になりました^-^
先のHTML5(pure)版に比べると凡そ1.88倍もなめらかに動くので、見ていてもかろうじて映像系のデモとして見れる…かなぁ…うーむ^^;
こちらは、パーティクルシステムはNative-client側に実装し、
- JavaScript → Native-Client : 次の pixels を計算しておくんなまし!
- Native-Client : パーティクルシステムを dt [sec] だけ進め pixels を決定
- Native-Client → JavaScript : できたよん っ[☆☆☆…(ピクセルの色情報群)]
- JavaScript : pixels を canvas に描画するデス!
この 1-2-3-4 をJavaScript側のメインループで回しています。
ちなみに、JavaScriptでメインループを回す際は、ビジーループは論外として、setInterval/setTimeoutを使います。どちらが良いかは負荷次第で、実は先に実装したHTML5(pure)版でも conf.main_loop_timeout を true/false で setInterval/setTimeout を切り替えて実行する様に仕込んであります。
通常は setInterval を用いた方がループ処理の為の負荷が軽くなるようです。しかし、 setInterval で指定する呼び出し間隔に対して過負荷な処理については、 setTimeout(arguments.callee, 1) などして回した方がどんなに重かろうとも一応はコールスタック的にも健全な状態を維持出来る為か setInterval で回すよりもループ処理による負荷は軽く済むようです。(この判断はChromium 24でループを回して行った定性的な評価によります。)
今回は Native-client のコード内で pixels の確定まで行なってしまい、上記工程の 3 にて結果を JavaScript へメッセージングしましたが、実際に実用的な面白いアプリとなると、JavaScriptから受け取る値に応じてNative-clientからの応答(出力)を変化させるなどしたい場合もあるかもしれません。しかし、今のところ JavaScript → Native-client のメッセージングには未実装機能があり、
[6:6:1204/185322:ERROR:message_channel.cc(129)] Not implemented reached in PP_Var webkit::ppapi::<unnamed>::CopyPPVar(const PP_Var&)
なんてエラーを吐いたりする事があります^^; JavaScript側からNative-clientに対してpostMessageする際には、スカラーっぽい値または文字列しか仕様としても実装されておらず、ハッシュオブジェクト { ... } や内部的は割と等価な配列オブジェクト [ ... ] をNative-client側へポストすると発生してしまいます。このため、こうしたデータは JSON.stringify でJSON化、つまり文字列にシリアライズしてNative-clientへポストするなどの手間が必要となります。
逆に、今回の様に、Native-client → JavaScript のメッセージングでは <ppapi/var_array_buffer>で定義されている pp::VarArrayBuffer を送り付けられます。今回は pixels = [pixel,pixel,pixel,...] の様なイメージの配列オブジェクトをNative-clientからJavaScriptへ送り付けて居ます。
とりあえず、Native-client → JavaScript のメッセージングは JSON でピクセル群のカラーデータを送り付ける実装になっています。
Conclusion
このヘタレというかダメダメな実装状態でもNative-clientのJavaScriptに対する優位性が十分に伝わった(ことにしてください・x・)かと思います。また、今回はNative-clientとJavaScriptのメッセージングを(無理矢理)使い、協調的に動作させる事にも可能性を見いだせた方もいらっしゃったかもしれません。
また、少々面倒なglibcツールチェイン時のライブラリーの同梱や.nmfの準備なども今回配布するお手軽Makefile付きのNative-clientプロジェクト雛形を使って頂ければほぼ苦労する事も無いと思います。まだほんのさわり程度しかNative-clientのぱわーを使えて居ませんし、そもそも今回のデモの動作方式では潜在能力も活かせる状態ではありませんでしたが、そんな中でも何かしらお伝えできたり、クリスマスへのアドベンカレンダーとして、或いはベンチマークアプリとして少しでも楽しんで頂けたなら幸いです。
引き続き、ブログではNative-clientも扱い続けたいと思います。
・w・;
実はNative-clientぱわーどの方のソースはとてもじゃないけど見せられないよ>< な状態です。徹夜納期でなんとか動く状態でデプロイしたに過ぎないとんでもない状態です。余裕を見てどうにかしようかなーと思います。
また、今回は中途半端にHTML5/JavaScriptとメッセージング連携していますが、そもそもNative-clientにも強力な描画APIがありますから、そちらのガチなNaClコードも時間を取れ次第載せたいなーと思います。
=w=;
Native-client について一言だけグサリと言いたいとすれば、いーかげんに GCC-4.4 ベースとか卒業してーーーーー>< って事くらいかな!
そうそう…Eigen使おうかと思ったんだけど、どうもコンパイルした.nexeが動作テストしてみたら例外落ちするっぽい状態で、結局使うのを止めて簡単なvector2だとかcolorだとかさくっと定義して使ってたり。そのへんも後できちんと調査したいなー。
ちなみに、今回の企画、実はこの企画の前にWebRTCとカメラ映像処理も企んだのだけど、どうもLinuxのChromiumはWebRTCのカメラ機能が未実装らしくてね(´・ω・`) libusb動かないかなーとか画策もしたものの、素直に諦めるなどしていたのでした。WebRTCがLinux版でも実装されたら遊ぼうかと思ってるよ。
Future
時間が取れ次第、年末にかけて逐次もうちょっとこのネタを引きずろうと思っています。
References
- HTML5
- Native-client
- Meta-ball
- Color space conversion
- Tool
- Compiler
Special Thanks
Environments
執筆時に用いた環境の諸元を掲載します。
Hardwares
Softwares
- OS
- Web Browser
- Chromium 24.0.1290.0 (160607)
- Pepper SDK (≈native-client SDK)
- Boost C++ Libraries
- libboost 1.49
- Eigen
eigen 3.1.2