最近のブログ

RDF(RSS 1.0) URL RSS URL ATOM URL

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

WebAssembly :: WebAssembly がデフォルトで有効化されるのはいつ?

問題なく進めば2017年の第一四半期にバイナリフォーマットの初期バージョンが固まって、ブラウザで順次有効化されるらしい。
Firefox は 2017年3月のバージョン52 で有効化が予定されているとか。

2017年4月以降に本格的に取り組むと変更の対応が少なそう。


以下の記事に書かれている。
コミュニティにフィードバックを求めるためのWebAssemblyのブラウザプレビュー
JavaScriptを補完するウェブ用の新バイナリフォーマット「WebAssembly」--ブラウザプレビュー段階に

投稿者 Takenori : 19:31

WebAssembly :: wasm に clang を使った場合に C 内で関数呼び出しする場合に出るエラー

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 オプションが必要になる。

投稿者 Takenori : 18:28

WebAssembly :: C/C++ を wasm に clang を使用してコンパイル、JavaScript から呼び出す

WebAssembly は、2017年第一四半期のどこかで一区切りつけて破壊的変更が行われる予定ということなので、それ以後またツールチェーンの更新が必要になる。
以下、clang を使う方法で書いているが、Emscripten を使う方が簡単な様子。

環境構築
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) {
  return WebAssembly.compile(bytes).then(m => new WebAssembly.Instance(m, imports));
}
var importObject = { env: { print : arg => console.log(arg) } };
var instance;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'sample.wasm', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
  var binary = xhr.response;
  var binarray = new Uint8Array( binary );
  instantiate( binarray, importObject ).then( inst => instance = inst );
  document.getElementById( 'countup' ).addEventListener( 'click', function (){
    putString();
    this.value = instance.exports.count();
  }, false);
};
xhr.send(null);

このサンプルスクリプトでは 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 );
int c=0;
int count(){
  print( c );
  return c++;
}


整数引数で JavaScript の関数を呼び出すのは問題ないが、文字列(バイト列)を受け渡しするのはひと手間必要になる。

投稿者 Takenori : 18:01

2017年01月23日

吉里吉里Z 開発 :: Android版のサウンドの制限

Android の OpenSL ES 仕様上発生する Windows 版の現吉里吉里Z 制限とは異なる制限。
ソフトウェアで事前に変換することで(表面上の)制限をなくすことは可能ではあるが、初期は行わない予定。

以下、具体的な制限。

・ 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 の時はバッファサイズを推奨サイズの倍数にするというところまでの対策にとどめる。

投稿者 Takenori : 16:54