怪しいのは、定数「MAXIMUM_PROCESSORS」である。
この定数は、「MAXIMUM_PROC_PER_GROUP」になっていて、MAXIMUM_PROC_PER_GROUP は、_WIN64 が定義されていると 64、それ以外は 32 となっている。
つまり、32bit 版吉里吉里Z はコア数 32 を上限として処理している。
64bit 版は 64 になる。
SetThreadIdealProcessor では、「優先プロセッサがない場合は、MAXIMUM_PROCESSORS が返ります。」と書かれている。
MAXIMUM_PROCESSORS が 32 で、論理コア数も 32 。
どこかミスして潜在的な不具合があってもおかしくない。
Windows7 からは、SetThreadIdealProcessor の代わりに SetThreadIdealProcessorEx が使えるようだ。
吉里吉里2/Z は描画のマルチスレッド化の際、コア数を GetSystemInfo で取得している ( マルチ版ではstd::thread::hardware_concurrency() に変更 )。
SYSTEM_INFO.dwNumberOfProcessors は論理コア数なので、32 になるはずである。
ただ、内部の定数で 8 を上限としているので、描画スレッド数は最大 8 になるように書かれているように見える ( 私が書いたところではないので、8 はマジックナンバーで意図は不明 ) 。
不具合がありそうに思えるのは、定数「MAXIMUM_PROCESSORS」が絡むところだけれど、実際に何が問題を起こしているのかは、実際の環境で動かせないことには特定しづらい。
]]>DirectShow 自体が論理コア数32以上に対応していないか、自前のフィルタ内部で何か問題のある実装をしてしまっている可能性がある。
現在貰ったり見たりしたエラーログからは、これ以上の追求は難しそう。
Ryzen Threadripper 1950X マシンが必要か……
このエラーは吉里吉里2/Z固有の問題ではない(他のエンジンでも発生)ようなので、DirectShow がだいぶ疑わしい。
動画以外のところで発生しているように見えるエンジンもある。
上手くやらないとProcessor affinity周りで問題が起きそうではある。
吉里吉里2/Zでも論理コア数が33以上で32bit版だと動画以外でも落ちる可能性はあるが、それはそもそもAPIレベルでどうしようもない気がする。
vomMFEVR モードを使用して、動画を再生すれば回避できるのではないかと思われる。
----
追記
詳しいログをもらったのでリンクを貼っておく。
エラーログ
このログだとe-moteで問題が発生しているように見える?
エモーションレイアウト調整ファイルと言うのが何かわからないけど、間接的に動画読んでいるファイルなのだろうか?
追記2
ログを追加してもらった。
原因がわからなくなってきた。
再現環境が必要そう。
Android では、OpenSL ES での再生となっている。
OpenSL ES は、キュー方式での再生となっていて、 DirectSound のリングバッファ方式とは根本的な再生方法が異なる。
Windows でのキュー方式での再生では、XAudio2 が OpenSL ES と近い API 形態のため、まず XAudio2 での再生を実装した。
キュー方式での再生の基本構造を Windows 上で開発した方が開発効率が良いため、使うかどうかは考慮せず XAudio2 を選択した。
ただ、 XAudio2 はバージョン分岐がある。
XAudio2 は低レベルのクロスプラットフォーム オーディオ API と言いつつ、Widnows8/10 で API 体系が Windows7 以前の DirectX 付属の XAudio2 と異なっている。
しかも、この互換性をとるのはだいぶ厄介で、工夫を要する。
このような状況なので、XAudio2 の API 互換性には信頼が置けない。
WASAPI を直接叩くのが一番安心である。
XAudio2 実装は、開発効率とテストのために実装したものなので、捨ててしまう。
DirectSound を切り捨て、基本再生構造をキュー方式に一本化すれば、マルチプラットフォームでの不具合の検出も行いやすくなる。
Windows10 Creators Update 以降で使えるようになった立体音響の Windows Sonic も WASAPI を用いて再生されているようだ。
Windows Sonic は HoloLens 用に追加された立体音響ということなので、どの程度立体感をもって聞こえるのか試してみたい。
WASAPI に切り替えるついでに、実装に手間がかからなさそうなら Windows Sonic も入れてしまいたい。
ヘッドフォンでの立体音響は、ノベルゲームで効果を発揮する場面は少なからずあるように思う。
バイノーラル録音の音声コンテンツは同人でだいぶ流行っているように見える。
Windows10 Creators Update 以降限定ということになってしまうが、そこは諦める。
バイノーラル再生のライブラリを組み込むという手もあるが、それはまた将来の話としたい。
プロテクトの代わりにマイニングが入っていれば、違法コピーされてもプレイされたのなら仮想通貨を稼いでくれる。
プロテクトも色々と嫌われている(個人的にはオンライン認証なら気にしない派)が、それよりもマイニングの方が嫌悪感少ないのでは?
フリーゲームでも体験版でもゲームをプレイしてもらえれば、その分仮想通貨を稼いでくれる。
広告はゲームの没入感を損なうが、ゲームプレイに支障のない範囲のマイニングであれば、プレイに支障はない。
問題があるとすれば、安定性を高めないとマイニングのせいでゲームが落ちた時に嫌悪感が増大することか。
ビジネスモデル
ビジネスモデルをどうするかは大きな問題である。
フリーゲームは気にする必要はない、ただ仮想通貨≒プレイ時間というのものがある程度計測出来て、仮想通貨が臨時で入るメリットが増える。
今まで有料だったものを無料(マイニング)版と従来通りの有料版で出して、有料版が売れるかどうか?
特典狙いの人には問題ないだろうけど、プレイだけの人は無料版があればそれで十分になってしまう。
広告版と広告なし版のアプリを作ると、ある程度の人は広告なし版を買ってくれる。
広告はそれほど鬱陶しいのである。
プレイに支障がない範囲でも電気代が少し余計にかかるかもしれないとしたら、有料版を買うか?
普通に電気代の方が従来の価格より安い気がする。
負荷が上がって、ファンが回ってうるさいから買うというのはあるかもしれない。
この辺りやってみないことにはわからない。
マイニングで利益が確保できるのかどうかも。
無料(マイニング)版は強制マイニングON。
有料版はデフォルトOFFでマイニング切り替え可、辺りが妥当か。
後は、応援マイニングモード。100%/75%/50%/25%辺りで負荷設定してゲーム止めてマイニングだけしてもらい、メーカーを支援するモードも備えると面白そう。
アイデアとしてはこの辺りか。
ビジネスモデルの検討が課題(実験が必要)。
税制は知らん。
仕組み
マイニングについて調べてみたが、単体でマイニングして利益出すのは非現実的で、マイニングプールを形成して、複数人でマイニングしてヒットしたらそれを計算量を基に分け合うのが一般的なようだ。
どこかのマイニングプールに参加するのもいいかもしれないが、自前で吉里吉里Zとして形成すると楽しそうだ。
リアル吉里吉里Zクラスタの誕生である。
対クラック性
「マイニングの結果を送る先が改竄された版がでまわる」とリプがあり、私も最初それは想像した。
よく考えると2つの意味でその可能性は低い。
1. 公式が無料(マイニング)版を出したとしたら、わざわざクラックされた怪しいところの物をダウンロードするのか?というところ。
常識的な考えを持っていれば、可能性としては低い。
2. 自前でマイニングプールを形成すれば、認証されていないIDへの送信を阻止できる。
もちろんマイニングプールの接続先自体を書き換えれば、無力化されてしまう。
さらにマイニング処理自体を別物にすればなんでも可能だが、それは既にマルウェアであり、現状も可能で、クラック版にマイニングを追加したものと言うのは既にあるようだ。
何はともあれ、まずは吉里吉里Zプロジェクト支援マイニング版を作りたいな。
OSS で開発費をどうするかは常に悩みの種だったけど、マイニングである程度捻出できるのなら、継続時に吉里吉里Zを拡張できる道が開ける。
新規に実装されるハードウェアによる描画は、従来のソフトウェアによる描画、つまり Layer を使った描画機構とはまったく互換性がない。
現状、最終描画は DrawDevice 機構によって行われているが、Android 版では DrawDevice はなくしてしまう予定である。
DrawDevice は、主に描画 API の抽象化を行うものであるが、描画 API の異なる Windows と Android では別実装になってしまう上、DrawDevice 自体が Windows 依存している。
DrawDevice は、Layer の最終合成結果を実際に画面に描画する機能を持っているので、DrawDevice がないと Layer を使った描画が出来ないかと言うと、そうではなく、以前この時のために実装していた LayerTreeOwner 機構がある。
Window 抽象化機構 と Layer Tree Owner に詳しく書いている。
「ハードウェア描画対応の準備も兼ねている」と書いているが、これがやっと日の目を見る。
BitmapLayerTreeOwner クラスは、実証実装と言うか、期待通り機能することを示すための実装だった。
ハードウェア描画機能の一部でテクスチャに Layer 合成結果を描画(転送)する機能が追加される。
ただ、これだけでは従来のように Window クラスを指定して Layer を生成しても何も描画されない。
そこで、Window クラスを指定して Layer が生成された場合、Layer Tree Owner インターフェイスを持つテクスチャクラスが内部で生成され、その LTO インターフェイスを Window クラスは返す。
これによって Window を指定した Layer の生成が従来通りできる。
この時、内部生成された LTO インターフェイスを持つテクスチャクラスは、自動的に画面描画するテクスチャとして関連付けられ、特に意識することなく従来のような Layer を使った描画が実現する。
この仕組みによって多くの人はそのまま移行できると考えている。
ただし、前述のように DrawDevice が Android では消えるので、DrawDevice 依存した実装を行っていた人はそのままでは移行できない。
まあ、 DrawDevice 自体 Windows 依存で、独自実装しているということは、Windows に依存した実装を行っていた可能性も高いので、どのみち Android 対応には何かしら作業が必要であったと思われるが。
なお、LTO インターフェイスを持つテクスチャクラスが自動追加される機構が初回リリースで入るかどうかは未定。
マルチプラットフォーム版はハードウェアによる描画が本丸である。
追記
Windows 版では、オプションで従来の DrawDevice 描画にすることもできる。
ただし、その場合はハードウェア描画は使えない。
自分の場合は、git status で見た時ほぼ以下のどちらか。
(untracked content) : ローカル submodule 側が更新されているけど、参照側でコミットしていないなど。
TortoiseGit でコミットしようとしたときにサブモジュールがリストに現れる時はこれかな?
自分の場合は、.gitignore 追加漏れで発生していることが多い。
コミットする必要のないファイルで更新がかかっているけど、コミット漏れ扱いになってる。
対策は、.gitignore に追加する必要のないものはきちんと追加する。
まだコミットしていないものがあるのならコミットする。
(new commits) : submodule 側が進んでいるので、参照先コミットを差し替える。
サブフォルダの submodule に変更加えて、そのままそこでコミット/push を行っていると出る。
git add submodule_path コマンドで参照先を差し替えてやる。
例 : git add external/zlib
参照先を差し替えた後はコミット。
自分の場合はほぼこの2パターン。
サブフォルダで編集して、そのまま submodule の方へコミットするというような更新の仕方をしているからだと思う。
確かこうだったはずと、このエントリーを書いているので、次に同じことになった時、間違っていたらこの記事は更新する。
]]>1. SurfaceView の Surface を ANativeWindow_fromSurface で ANativeWindow 取得、ANativeWindow_lock - ANativeWindow_unlockAndPost で転送する。
2. glTexImage2D で転送する。
3. OpenGL ES 3.0 の PBO に転送する ( PBO-Tex は DMA と言うことでここでは気にしない)。
これらの方法を Nexus5 で計ってみたが、2と3は初回遅いものの2回目以降は早く、1は v-sink 待ちのためか lock 含むと 16ms くらいになるものの、lock 時間除くと他と大差ない。
当たり前と言えば当たり前かもしれないが、どの方法でも有意な速度差がないようだ。
他に、OpenGL の Texture から、SurfaceTexture -> Surface として、1と同様に転送する方法もあるはずだがそれほど差が出ないだろうと感じたので計っていない。
毎フレーム転送が発生する場合は、Optimizing Texture Transfers(pdf) にあるように、PBO をダブルバッファリングして転送待ちを減らすのが効果的なようだ。1フレーム遅れてしまうが。
また、PBO の場合、FrameBuffer へ直接転送することで、1番と同じようにダイレクトに描画することも可能なようだ。
結局 lock(busy) されている状態を回避して転送するのが速いようなので、PBO を使うのが良さそう。
]]>せっかく実装したのだから、PC 版では XAudio2 か DirectSound かを選択できる形にしようと思い、XAudio2 についていろいろと調べてみたが手間がかかりそうなので、共通部分の開発用にのみ使用して、実環境では使わないことになりそう。
使うのなら、WASAPI で置き換える形が現実的。
ヘッダーファイルを見るとバージョンによって、構造体にメンバが増えていたり、インターフェイスにメソッドが増えていたり、メソッドにパラメータが増えていたりするので、別物として扱う必要がある。
2.8 と 2.9 はヘッダー共通なので、2.7 とそれより後の 2 パターンで使用するメソッドについてラッパーを書けば両バージョンに対応できなくはなさそうである。
dll から XAudio2Create 関数を得て、それ以降で得られるインターフェイスの IXAudio2 などを別物として扱えば、理屈上は両方に対応できるはずである。
もしくは、Windows7 以下を切り捨てるのなら、2.8 と 2.9 のヘッダー(インターフェイス/構造体)でそのまま使える。
Windows7 を切らないという選択を選ぶのであれば、namespace とファイル分離、Factory によって何とかするのが記述量一番少ないと考えられる。
以下のような実装。
namespace xaudio2_7 { |
namespace xaudio2_9 { |
extern IXAudio2Driver* CreateXAudio2Driver7(HMODULE); IXAudio2Driver* pDriver = nullptr; // ここでは放置しているが hDll は使い終わったら FreeLibrary する。 |
xaudio2_7 と xaudio2_9 でファイルを分離するのは define で定数等定義されているから。
xaudio2driver.h 内に通常の実装を入れて include することで2回書くのを回避する。
xaudio2_7.h は、DirectX SDK のヘッダーをリネームして置いて、xaudio2_9.h は Windows SDK の方のヘッダー。
と書いたものの、ここまでして実装した方がいいかは疑問。
XAudio2 に比べれば複雑になるものの、DirectSound よりは記述量少なそうな WASAPI を選択する方が良さそうに思える。
function print( offset, length ) { |
const char* は、instance.exports.memory のインデックスとなっているので、そこから文字列長分を uint8 配列として切り出し、文字列化している ( ASCII 文字列以外ではうまくいかないかもしれない )。
これで C 側から文字列(バイト列)をコンソールに出力する関数が呼び出せる。
JavaScript から C 側へ文字列(バイト列)を渡すのはもう少し手間がかかる。
C 側で以下のように定義する。
static const int memSize = 1024; |
この領域に JavaScript から文字列を書き込んでもらい、そこを読み取ることで C 側で文字列を受け取れる。
JavaScript 側は以下のようになる(長さチェックはしていないし、ASCII 以外は考慮していないので注意)。
function putString( str ) { |
このスクリプトで文字列を書き込んでもらった後に――
const char* text = inputmemory; |
として C 言語側でアクセスできる。
以上でとりあえずは文字列の受け渡しが出来る。
ただ、実際には実用性に欠ける。
ある程度の規模で使うとなると malloc を自作するだろうから、そこで確保したメモリのポインターを JavaScript に渡せば、そのメモリの前に管理構造がついているような実装にすれば、その確保されたメモリの長さもわかるため、JavaScript でアクセスできる範囲も知ることができる。
そうすれば実用上も問題はなくなる。
malloc していないメモリを渡してしまうと壊れるが。
2017年4月以降に本格的に取り組むと変更の対応が少なそう。
以下の記事に書かれている。
コミュニティにフィードバックを求めるためのWebAssemblyのブラウザプレビュー
JavaScriptを補完するウェブ用の新バイナリフォーマット「WebAssembly」--ブラウザプレビュー段階に
extern void print( const char*, int );
int stringLength( const char* s ) {
int len = 0;
while( s[len] ) { len++; }
return len;
}
int c=0;
int count(){
const char* text = "Hello world!";
print( text, stringLength(text) );
return c++;
}
このように JavaScript から count() を読んで、その中で stringLength() を呼ぶようなプログラムを書くと "Uncaught RuntimeError: memory access out of bounds" などと出てうまく動かない。
ソースコードに何か問題があるのか?と言うとそうではなく、コンパイル時にスタックの指定が必要とのこと。
具体的には――
s2wasm count.s -o sample.wast --allocate-stack 1024
のように --allocate-stack オプションが必要になる。
]]>環境構築
WindowsでWebAssemblyの環境を整える で書かれているのと大体同じだけど、「sexpr-wasm-prototype」は「WABT」に代わっている。
コンパイルエラーは最新版 (2017/2/11 に clone) では発生しなかった。
後、ALL_BUILD.vcxproj を開くと書かれているが、開くのはソリューションファイル LLVM.sln/binaryen.sln/WABT.sln が本筋。
コンパイル手順として、clang→llc→s2asm→sexpr-wasm と書かれているが、これも clang→llc→s2asm→wast2wasm となる。
コマンドとしては以下。
clang -S -emit-llvm --target=wasm32 sample.c
llc sample.ll -march=wasm32
s2wasm sample.s -o sample.wast
wast2wasm sample.wast -o sample.wasm
実行環境
Firefox 51 だと wasm 有効にしても「failed to match binary version」とエラーが出て動かない。少し古い binaryen だと動くかもしれないが今回は無視。Chrome で試す。
Chrome 56 は動かせた。ただローカルに置いた html ファイルから wasm ファイルを XMLHttpRequest で読み込ませようとしたらセキュリティーエラーが出るのでそれを回避するオプションをつけて起動する。
Chrome のショートカットを作って、「--user-data-dir="%UserProfile%\AppData\Local\Google\Chrome\UserDataLocal" --disable-web-security」などの引数を追加して起動する。
--user-data-dir= でユーザーデータフォルダを指定しているが、デフォルトの User Data のままではセキュリティ無効が効かなかったので、別途フォルダ作ってそちらを使うようにした。
後、wasm をアドレスバーに chrome://flags/#enable-webassembly と入力して有効に変更する必要がある。
ユーザーデータフォルダを切り替えたら wasm の指定はデフォルトになっているので注意。
C で書いた関数の呼び出し
Understanding the JS API に大体書かれている。
fetch で wasm ファイルを読むように書かれているが、ローカル file:// だと動かないので、XMLHttpRequest を使う必要がある。
サーバーに置くのならこの辺り関係ないが、まずはローカルで色々と実験するだろうから注意が必要。
wasm ファイルが読み込めたら WebAssembly.compile でコンパイルして、new WebAssembly.Instance でインスタンス化。
インスタンス化されたら instance.exports.func(); などとして呼び出せる。
function instantiate(bytes, imports) { |
このサンプルスクリプトでは id が countup のボタンが html に必要。
then と書かれているのは非同期処理の Promise と言う仕組みのため。
WebAssembly.compile は非同期処理で Promise を返すため、then で非同期処理が終わった後の処理を記述する。
arg => console.log(arg) などは JavaScript のラムダ式。
JavaScript の関数を C 側から呼び出す
wasm 側で import する関数は、WebAssembly.Instance の第二引数に namespace + 関数名 のオブジェクトを渡せばよいようだ。
{ "namespace" : { "function" : function } }; のようなオブジェクト。
S 式ではリンクのように import で関数に割り当てればいいようだが、C 言語側からの場合は単純に extern で宣言して呼び出せば env.function_name のような形で import する宣言が追加されていたので、{ "env" : { "function_name" : function } } と言う形でオブジェクトを作れば、その関数をすんなり呼び出せる。
関数の形式は wast (wasm のテキスト形式の S 式) ファイルの import 部分を見ればわかるから、その形で JavaScript で関数を書けばよい。
extern void print( int ); |
整数引数で JavaScript の関数を呼び出すのは問題ないが、文字列(バイト列)を受け渡しするのはひと手間必要になる。
以下、具体的な制限。
・ 8 ビット符号なしまたは 16 ビット符号付き
(浮動小数点データでの再生は、Android 5.0 以降でのサポート。Android 版は初期は実装しない予定)
・モノラルまたはステレオ
( Windows 版はさらに多チャンネルサポートしているが Android 版は非サポート)
・サンプルレート
8000,11025,12000,16000,22050,24000,32000,44100,48000Hz
( 48000Hz を推奨。推奨値は端末依存だが 48kHz が多い模様。レイテンシ低減に効くとドキュメントに記載)
・同時再生数 32
(ドキュメントでは object と言う表現なので、もしかしたらもう少し少ないかもしれない。また OpenMAX AL と共有とのことなので動画もこの数に含む。自前でミキシングすればこの上限は関係ないが、そこまで厳しい制限でもないため現状上限はこのままの予定)
Audio output latency に低遅延に関する情報がある。
デバイスの最適サンプリングレートとバッファサイズを使用して、無音出力して準備するというものであるが、そこまでは行わない予定。
リップシンクや UI でタップ音などで遅延を感じる可能性はある。
あまりにひどいということであれば対策を考えるが、現在は 48kHz を推奨し、48kHz の時はバッファサイズを推奨サイズの倍数にするというところまでの対策にとどめる。
System.appDataPath = Context.getFilesDir() ex) /data/data/{パッケージ名}/files
System.dataPath = Context.getExternalFilesDirs() ex) /storage/emulated/0/Android/data/{パッケージ名}/files
System.exeName = Context.getPackageCodePath() ex) /data/app/apkname.apk
System.exePath = Context.getPackageCodePath() ex) /data/app/
System.personalPath = Context.getExternalFilesDirs()ex) /storage/emulated/0/Android/data/{パッケージ名}/files
System.savedGamesPath = 対応するものなし
System.exePath は、apk が置かれるパスが相当すると思うが、Windows と違って Android では取得する意味がないと思われる。
System.exeName も、apk のファイル名を含んだフルパスであるが、これもあまり意味がない。
一応、apk は zip なので、吉里吉里Z 本体で展開して中のファイルをあれこれと出来なくはないが、意味は薄い。
Android 固有のパスは色々あるので、また後日リストアップする。
]]>