最近のブログ

RDF(RSS 1.0) URL RSS URL ATOM URL

2017年10月18日

吉里吉里Z 開発 :: 32コア問題をソースコードレベルで少し追う

環境が準備出来る前にソースコードレベルで何か怪しいところはないか少し調べてみた。

怪しいのは、定数「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」が絡むところだけれど、実際に何が問題を起こしているのかは、実際の環境で動かせないことには特定しづらい。

投稿者 Takenori : 19:46

2017年10月13日

吉里吉里Z 開発 :: 論理コア数32以上のCPUで動画再生時エラーが出る問題

実際に何個以上からエラーとなるのかは不明だが、少なくとも32コア(16コア/32スレッド)のCPUでは発生する様子。
Ryzen Threadripper 1950X と Opteron 6376 x 2 の環境で確認されている。
IMediaControl::Run や IMediaControl::Pause で 0x80004005 が返され、動画再生に失敗している。
エラーコード 0x80004005 は特定できないエラーが発生。

DirectShow 自体が論理コア数32以上に対応していないか、自前のフィルタ内部で何か問題のある実装をしてしまっている可能性がある。
現在貰ったり見たりしたエラーログからは、これ以上の追求は難しそう。
Ryzen Threadripper 1950X マシンが必要か……

このエラーは吉里吉里2/Z固有の問題ではない(他のエンジンでも発生)ようなので、DirectShow がだいぶ疑わしい。
動画以外のところで発生しているように見えるエンジンもある。
上手くやらないとProcessor affinity周りで問題が起きそうではある。
吉里吉里2/Zでも論理コア数が33以上で32bit版だと動画以外でも落ちる可能性はあるが、それはそもそもAPIレベルでどうしようもない気がする。

vomMFEVR モードを使用して、動画を再生すれば回避できるのではないかと思われる。

----
追記
詳しいログをもらったのでリンクを貼っておく。
エラーログ
このログだとe-moteで問題が発生しているように見える?
エモーションレイアウト調整ファイルと言うのが何かわからないけど、間接的に動画読んでいるファイルなのだろうか?

追記2
ログを追加してもらった。
原因がわからなくなってきた。
再現環境が必要そう。

投稿者 Takenori : 21:54

2017年09月23日

吉里吉里Z 開発 :: DirectSoundの廃止とWASAPIへの移行を検討

吉里吉里Zマルチ版ではある程度互換性が失われるので、ついでに DirectSound を切り捨てて、WASAPI に切り替えることを検討している。
Windows Vista 以降は DirectSound で直接再生は出来なくなっていて、WASAPI によるソフトウェアエミュレーションで再生されている。
WASAPI は、Windows Vista から使える。
互換性が失われると言っても、コマンドラインオプション周りで DirectSound 関係で設定している項目がなくなるのと、ドキュメント化されていない 3D オーディオ周りが消える、もしくは Windows10 以降用となるだけで、影響は少ない。

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 以降限定ということになってしまうが、そこは諦める。
バイノーラル再生のライブラリを組み込むという手もあるが、それはまた将来の話としたい。

投稿者 Takenori : 10:43

2017年09月22日

吉里吉里Z 開発 :: ゲーム中のバックグラウンドでのマイニングを考える

広告の代わりにマイニングするという記事を見て、マイニングプラグインを作るのはどうだろうか?と思った。
ゲーム中空いているCPU/GPUリソースを使ってマイニングする。
ゲームプレイに支障がない範囲であれば許容してくれる人も結構いるのではないかと思うのだがどうだろうか?
個人的な感覚では嫌われる広告よりは嫌悪感少ないように思う。
プレイ中のCPU利用率がー、メモリ使用量がーという人たちもいるので、全ての人が気にしないということはないだろうけど。

プロテクトの代わりにマイニングが入っていれば、違法コピーされてもプレイされたのなら仮想通貨を稼いでくれる。
プロテクトも色々と嫌われている(個人的にはオンライン認証なら気にしない派)が、それよりもマイニングの方が嫌悪感少ないのでは?

フリーゲームでも体験版でもゲームをプレイしてもらえれば、その分仮想通貨を稼いでくれる。
広告はゲームの没入感を損なうが、ゲームプレイに支障のない範囲のマイニングであれば、プレイに支障はない。
問題があるとすれば、安定性を高めないとマイニングのせいでゲームが落ちた時に嫌悪感が増大することか。

ビジネスモデル

ビジネスモデルをどうするかは大きな問題である。
フリーゲームは気にする必要はない、ただ仮想通貨≒プレイ時間というのものがある程度計測出来て、仮想通貨が臨時で入るメリットが増える。
今まで有料だったものを無料(マイニング)版と従来通りの有料版で出して、有料版が売れるかどうか?
特典狙いの人には問題ないだろうけど、プレイだけの人は無料版があればそれで十分になってしまう。
広告版と広告なし版のアプリを作ると、ある程度の人は広告なし版を買ってくれる。
広告はそれほど鬱陶しいのである。
プレイに支障がない範囲でも電気代が少し余計にかかるかもしれないとしたら、有料版を買うか?
普通に電気代の方が従来の価格より安い気がする。
負荷が上がって、ファンが回ってうるさいから買うというのはあるかもしれない。
この辺りやってみないことにはわからない。
マイニングで利益が確保できるのかどうかも。

無料(マイニング)版は強制マイニングON。
有料版はデフォルトOFFでマイニング切り替え可、辺りが妥当か。
後は、応援マイニングモード。100%/75%/50%/25%辺りで負荷設定してゲーム止めてマイニングだけしてもらい、メーカーを支援するモードも備えると面白そう。

アイデアとしてはこの辺りか。
ビジネスモデルの検討が課題(実験が必要)。
税制は知らん。

仕組み

マイニングについて調べてみたが、単体でマイニングして利益出すのは非現実的で、マイニングプールを形成して、複数人でマイニングしてヒットしたらそれを計算量を基に分け合うのが一般的なようだ。
どこかのマイニングプールに参加するのもいいかもしれないが、自前で吉里吉里Zとして形成すると楽しそうだ。
リアル吉里吉里Zクラスタの誕生である。

対クラック性

「マイニングの結果を送る先が改竄された版がでまわる」とリプがあり、私も最初それは想像した。
よく考えると2つの意味でその可能性は低い。
1. 公式が無料(マイニング)版を出したとしたら、わざわざクラックされた怪しいところの物をダウンロードするのか?というところ。
常識的な考えを持っていれば、可能性としては低い。
2. 自前でマイニングプールを形成すれば、認証されていないIDへの送信を阻止できる。
もちろんマイニングプールの接続先自体を書き換えれば、無力化されてしまう。
さらにマイニング処理自体を別物にすればなんでも可能だが、それは既にマルウェアであり、現状も可能で、クラック版にマイニングを追加したものと言うのは既にあるようだ。


何はともあれ、まずは吉里吉里Zプロジェクト支援マイニング版を作りたいな。
OSS で開発費をどうするかは常に悩みの種だったけど、マイニングである程度捻出できるのなら、継続時に吉里吉里Zを拡張できる道が開ける。

投稿者 Takenori : 12:28

2017年06月13日

吉里吉里Z 開発 :: ハードウェア描画と互換性

従来の Layer を使った描画機能と互換性を持たせつつ、新規実装されるハードウェアによる描画機能を使うように作ることも出来る形にする。

新規に実装されるハードウェアによる描画は、従来のソフトウェアによる描画、つまり 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 描画にすることもできる。
ただし、その場合はハードウェア描画は使えない。

投稿者 Takenori : 18:28

2017年05月22日

日常の備忘録 :: Git submodule メモ

自分で使う場合によく出くわすものについて。
前提 : Git の submodule は、特定リポジトリの特定コミットとフォルダを関連付ける物。
特定コミットなのが曲者で、submodule 内をいじるとその度に何をすればいいんだったか検索している。

自分の場合は、git status で見た時ほぼ以下のどちらか。

(untracked content) : ローカル submodule 側が更新されているけど、参照側でコミットしていないなど。
TortoiseGit でコミットしようとしたときにサブモジュールがリストに現れる時はこれかな?
自分の場合は、.gitignore 追加漏れで発生していることが多い。
コミットする必要のないファイルで更新がかかっているけど、コミット漏れ扱いになってる。

対策は、.gitignore に追加する必要のないものはきちんと追加する。
まだコミットしていないものがあるのならコミットする。


(new commits) : submodule 側が進んでいるので、参照先コミットを差し替える。
サブフォルダの submodule に変更加えて、そのままそこでコミット/push を行っていると出る。
git add submodule_path コマンドで参照先を差し替えてやる。
例 : git add external/zlib
参照先を差し替えた後はコミット。

自分の場合はほぼこの2パターン。
サブフォルダで編集して、そのまま submodule の方へコミットするというような更新の仕方をしているからだと思う。

確かこうだったはずと、このエントリーを書いているので、次に同じことになった時、間違っていたらこの記事は更新する。

投稿者 Takenori : 16:55

2017年04月07日

吉里吉里Z 開発 :: AndroidでのCPU-GPU画像(メモリ)転送速度はロック時間を考えないと大差ない?

Android で CPU/GPU でメモリが分離しているのかどうかはわからないが、画面描画するのに必要なメモリ転送の時間の話。

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 を使うのが良さそう。

投稿者 Takenori : 22:58

2017年02月18日

吉里吉里Z 開発 :: OpenSL ES と XAudio2

ループチューナ で指定されたループやラベル等吉里吉里 2/Z のサウンド部分は複雑なので、PC でデバッグできると効率が良い。
ただ、OpenSL ES と DirectSound は API 体系がだいぶ違うので共通化は大変。
XAudio2 は OpenSL ES とほとんど同じで、データがなくなるとコールバックで呼ばれ、バッファをキューに入れていく形になっている。
インターフェイスで実装を分離すれば、基本部分は共通化できるのでそうした。

せっかく実装したのだから、PC 版では XAudio2 か DirectSound かを選択できる形にしようと思い、XAudio2 についていろいろと調べてみたが手間がかかりそうなので、共通部分の開発用にのみ使用して、実環境では使わないことになりそう。
使うのなら、WASAPI で置き換える形が現実的。

投稿者 Takenori : 04:54

吉里吉里Z 開発 :: XAudio2

DirectX のバージョンによって 2.0 ~ 2.7 がある。最新版である 2.7 を対象とすれば問題ないと思われる。
Windows8 は 2.8、Windows10 は 2.9 となっている。
環境にある DLL の xaudio2_7.dll、xaudio2_8.dll、xaudio2_9.dll の内、新しいバージョンの DLL をロードすればよさそうであるが、それだけではうまく動かなさそうである。

ヘッダーファイルを見るとバージョンによって、構造体にメンバが増えていたり、インターフェイスにメソッドが増えていたり、メソッドにパラメータが増えていたりするので、別物として扱う必要がある。
2.8 と 2.9 はヘッダー共通なので、2.7 とそれより後の 2 パターンで使用するメソッドについてラッパーを書けば両バージョンに対応できなくはなさそうである。
dll から XAudio2Create 関数を得て、それ以降で得られるインターフェイスの IXAudio2 などを別物として扱えば、理屈上は両方に対応できるはずである。
もしくは、Windows7 以下を切り捨てるのなら、2.8 と 2.9 のヘッダー(インターフェイス/構造体)でそのまま使える。

Windows7 を切らないという選択を選ぶのであれば、namespace とファイル分離、Factory によって何とかするのが記述量一番少ないと考えられる。
以下のような実装。

namespace xaudio2_7 {
#include "xaudio2_7.h"
#include "xaudio2driver.h"
};
IXAudio2Driver* CreateXAudio2Driver7(HMODULE hDll){...}


namespace xaudio2_9 {
#undef _WIN32_WINNT
#define _WIN32_WINNT _WIN32_WINNT_WIN8
#include "xaudio2_9.h"
#include "xaudio2driver.h"
};
IXAudio2Driver* CreateXAudio2Driver9(HMODULE hDll){...}


extern IXAudio2Driver* CreateXAudio2Driver7(HMODULE);
extern IXAudio2Driver* CreateXAudio2Driver9(HMODULE);

IXAudio2Driver* pDriver = nullptr;
HMODULE hDll = LoadLibrary(L"xaudio2_9.dll");
if( !hDll ) {
  hDll = LoadLibrary(L"xaudio2_8.dll");
}
if( hDll ) {
  pDriver = CreateXAudio2Driver9( hDll );
} else {
  hDll = LoadLibrary(L"xaudio2_7.dll");
  if( hDll ) pDriver = CreateXAudio2Driver7( hDll );
}
return pDriver;

// ここでは放置しているが hDll は使い終わったら FreeLibrary する。
// IXAudio2Driver は自前のインターフェイス

xaudio2_7 と xaudio2_9 でファイルを分離するのは define で定数等定義されているから。
xaudio2driver.h 内に通常の実装を入れて include することで2回書くのを回避する。
xaudio2_7.h は、DirectX SDK のヘッダーをリネームして置いて、xaudio2_9.h は Windows SDK の方のヘッダー。

と書いたものの、ここまでして実装した方がいいかは疑問。
XAudio2 に比べれば複雑になるものの、DirectSound よりは記述量少なそうな WASAPI を選択する方が良さそうに思える。

投稿者 Takenori : 04:30

2017年02月12日

WebAssembly :: wasm と JavaScript の間の文字列(バイト列)の受け渡し

extern void print( const char* ); などとしてコンパイルしたものの wast ファイルを見ると const char* は i32 となっている。以前見たバイトコードの仕様では文字列型はなかった。
Emscripten ではヒープの実態は ArrayBuffer となっていたが、WebAssembly でもメモリは ArrayBuffer で確保されているよう。
Understanding the JS API の Memory で書かれている。
メモリは、wasm で import / export 可能とあるが、C からコンパイルした wast を見ると (export "memory" (memory $0)) となっている。
このメモリに対して直接値を書き込む、もしくは読み込むことでバイト列を読み書きできると書かれている。
上述の print 関数の const char* は i32 として JavaScript に渡されるが、この値は何かというと、export された memory のインデックスを表していた。
JavaScript 側で文字列終端チェックを省くために C 側で長さを渡すように extern void print( const char*, int ); とした場合、JavaScript 側では以下のようにすることで文字列を受け取ることができる。

function print( offset, length ) {
  var buffer = new Uint8Array(instance.exports.memory.buffer, offset, length);
  console.log( String.fromCharCode.apply(null,buffer) );
}

const char* は、instance.exports.memory のインデックスとなっているので、そこから文字列長分を uint8 配列として切り出し、文字列化している ( ASCII 文字列以外ではうまくいかないかもしれない )。
これで C 側から文字列(バイト列)をコンソールに出力する関数が呼び出せる。


JavaScript から C 側へ文字列(バイト列)を渡すのはもう少し手間がかかる。
C 側で以下のように定義する。

static const int memSize = 1024;
char inputmemory[memSize];
char* getInputMemoryStart() { return inputmemory; }
int getInputMemoryLength() { return memSize; }

この領域に JavaScript から文字列を書き込んでもらい、そこを読み取ることで C 側で文字列を受け取れる。
JavaScript 側は以下のようになる(長さチェックはしていないし、ASCII 以外は考慮していないので注意)。

function putString( str ) {
  var offset = instance.exports.getInputMemoryStart();
  var length = instance.exports.getInputMemoryLength();
  var buffer = new Uint8Array(instance.exports.memory.buffer, offset, length);
  for( var i = 0; i < str.length; i++ ) {
    buffer[i] = str.charCodeAt(i);
  }
  buffer[str.length] = 0;
}

このスクリプトで文字列を書き込んでもらった後に――

const char* text = inputmemory;
print( text, stringLength(text) );

として C 言語側でアクセスできる。


以上でとりあえずは文字列の受け渡しが出来る。
ただ、実際には実用性に欠ける。
ある程度の規模で使うとなると malloc を自作するだろうから、そこで確保したメモリのポインターを JavaScript に渡せば、そのメモリの前に管理構造がついているような実装にすれば、その確保されたメモリの長さもわかるため、JavaScript でアクセスできる範囲も知ることができる。
そうすれば実用上も問題はなくなる。
malloc していないメモリを渡してしまうと壊れるが。

投稿者 Takenori : 22:59