Friday, December 25, 2015

OpenSL ESでActivityが非アクティブになった時に音を停止する簡単な方法

前回OpenSL ESでサウンドを再生しましたが、Activityが非アクティブになっても音が鳴り続ける問題がありました。それに対しての簡単な対処法として、バッファを細かく分けてEnqueueしてみました。Enqueueは32768バイト単位でしています。(サイズは適当です)

最終アップデートからの経過時間が0.5秒を超えたらコールバック無視します。この方法の長所は、コールバック一つでループ再生と非アクティブになった時の音停止を同時に実装でき、コードが簡潔になります。Activityから非アクティブの通知を受け取る必要もありません。短所としては、アップデートループで長い処理をすると音が切れてしまうことです。

また、停止した音を再開する処理を盛り込んでいません。ここは、ゲーム毎の実装依存が多いと思いあえてしませんでした。例えば多くの場合は再開すべき音はBGMだけであったりするでしょう。

AndroidのOpenSL ESはコールバックは別スレッドから来るので特に気をつける必要があります。Android特有の実装についてはNDKのdocs/Additional_library_docs/opensles/index.html に説明があるので、一度目を通しておくと良さそうです。

Saturday, December 12, 2015

実践OpenSL ES、AndroidでWAVファイル再生

OpenSL ESによるサウンドエンジンを使い物になるように書いてみます。

まず、気づくのはDirectXを彷彿させるインターフェース型のAPIであることです。多くのメソッドはSLresultで結果を返し、Destroyでリソースを解放して終了します。素直に書くと冗長なコードになりそうですが、まず最初にDirectX開発者にはおなじみの手法を導入します。

SafeDestroyはDestroy呼び出しと同時にnullptrを代入してダングリングポインタにならないようにします。

SLHandleErrorというのは、各種メソッドがエラーコードを返したらソースコード上の行数と関数呼び出しの概要をログに出力するためのマクロです。Direct3D9のサンプルでは"V"という一文字のマクロが多用されていましたが、それです。

SLCallは、インターフェース呼び出しをすっきり書きたいがために書いたマクロです。これを使ってエンジンの初期化と終了を書いてみます。

エンジンの初期化と終了が書けました。

DirectXから来ると戸惑うのが、SLObjectItfとその他インターフェースに分かれていることです。OpenSLでは全てのインスタンスの実体はSLObjectItfであって、何らかのメソッド呼び出しが必要になったらSLObjectItfから追加のインターフェースを取得します。また、SLObjectItfをDestroyするとSLObjectItfから取得したインターフェースも一緒に無効になります。

engineEngineがSafeDestroyではなくnullptrを代入しているのは、engineObjectをSafeDestroyした時に一緒に消滅しているからです。

次は、WAVファイルをロードして"audio player"を作ってみます。

CreateAudioPlayerで、WAVファイルの特性(ステレオ/モノラル、サンプリングレート、ビット数)に合わせた"audio player"を生成します。WaveFormatExはWindows APIにあるWAVEFORMATEX構造体で、この情報からSLDataFormat_PCM構造体を構築します。リファレンス通りに書くとまたコードが長くなりそうですが、例えばSL_SAMPLINGRATE_44_1は44100000と定義されていたりします。samplesPerSecondを1000倍して代入すれば良いわけです。事前定義値は https://www.khronos.org/registry/sles/api/1.1/OpenSLES.h にあります。

次はWAVファイルの再生部です。

Enqueueで再生するバッファを指定します。ループ再生する場合はコールバックを登録して、コールバックからEnqueueを繰り返します。コールバック設定前にSL_PLAYSTATE_STOPPEDを明示するのはステートがSL_PLAYSTATE_STOPPEDではない場合にSL_RESULT_PRECONDITIONS_VIOLATEDエラーが発生するためです。これはPlayメンバ関数が二回目に呼ばれた場合に起こります。

これで、OpenSLの初期化からWAVファイルの再生までができました。ところで、この方法だとActivityが非アクティブになっても音が鳴り続けるという問題があります。この解決は次回書きます。

Saturday, December 5, 2015

Luaのrequireで独自ファイルシステム上のスクリプトをロード

requireでAndroidのAPKのassetsフォルダからスクリプトを読み込みたくて、方法を探ってみました。requireはCのincludeに似た使い方をしますが、requireの実体はモジュール名を引数に取るただの関数です。

dofileとrequireはよく似た関数ですが、いくつか違いがあります。まずrequireは通常拡張子".lua"無しでモジュールを指定します。これを実装してみます。


前回、dofileのファイルシステムを置き換えているので置き換え版のdofileを流用することでLuaだけで書けました。厳密にはpackage.pathを考慮していないという問題がありますが、これだけで用足りることも多いと思います。

ところで、requireで比較的重要なdofileとの違いが、重複requireガードです。これはpackage.loadedテーブルにロード結果が記録される事で行われます。実際にテーブルの中身をprintで出力してみると、以下のようになりました。(左がキー、右が値)
debug   table: 00C9D770
vec4    true
coroutine       table: 00C98AA8
_G      table: 00C92760
package table: 00C98828
math    table: 00C9A2D0
my_class        userdata: 00C9C3B8
io      table: 00C98C38
os      table: 00C99880
bind_win        true
string  table: 00C99FB0
utf8    table: 00C9D6D0
table   table: 00C98BE8
キーを見ると、luaL_openlibsでロードされるLuaの標準ライブラリに混じってrequireでロードしたモジュール(ここではvec4, bind_win, my_classがそれです)の名前が見えます。対して値はそのモジュールをロードした時のreturn値が格納されています。(return値が無い場合はtrue)

また、この値は同じモジュールの二回目以降のrequireで返される値としても使われます。以下の例は、dofileが毎回math.random()を実行するのに対して、requireは初回呼び出し時の値をpackage.loadedにキャッシュする事を確かめるものです。


以上を踏まえたファイルシステム置き換え版のrequireは以下のようになります。

Sunday, November 22, 2015

Luaのdofileで使うファイルシステムを置き換える

Luaはdofile等でスクリプトを読み込む時にfopenを使います。しかし、AndroidのAPKのassetsフォルダはfopenでアクセスできません。独自のファイルシステムを構築したい場合もあります。

そういう時は、以下のようにして、標準のdofileを上書きしてファイルシステムを置き換えます。



LoadFile関数はファイル名を受け取ってmallocで確保したメモリブロックにファイルの内容を格納して返すようにしました。例えばAndroidの場合はassetsフォルダからファイルを持ってくるようにします。このエントリを参考にしてください。

luaL_loadbuffer関数にスクリプトの内容とファイル名を渡します。ファイル名は万一スクリプト内でエラーが発生した場合にLuaがどのファイルの何行目なのかを知らせてくれる為に重要です。

lua_pcallは、読み込んだスクリプトを一度「実行」します。Cのような言語と違い、Luaは関数や変数の定義も「実行」することで行われますので、これをしないとスクリプトを読み込まなかったのと同じことになります。

luaL_loadbuffer又はlua_pcallでエラーが発生した場合、Luaスタックの最上位にエラーメッセージが入ります。ここでは、適当にメッセージを加工してluaL_errorに渡します。これによって、dofileの呼び出し元のスクリプト側のエラーとして処理されます。

最後のreturn lua_gettop(L) - 1;lua_pcallがスタック何かを積んだ場合、その個数を呼び出し元に返します。-1するのはdofileが受け取ったファイル名がスタックを一つ使っているのでそれを除外するためです。説明より実例のほうがわかりやすいと思うので、以下に書いてみました。例えば、以下のような事ができるようになります。



呼びだされたcallee.luaの最後の行にreturn文があり、その戻り値をcaller.luaで受け取っています。こういった使い方が必要ない場合は、return lua_gettop(L) - 1;はreturn 0;に置き換えても不都合はありません。ただし、caller.luaはdofileから戻り値を受け取れなくなります。

Thursday, October 29, 2015

Windows 10でDirectX12のWARPデバイスの生成が失敗する時の処方箋

DirectX12対応ビデオカードを保有していなくてもWindows10だけでDirectX12のプログラムをテストできるWARPデバイスというものがあります。



GitHubで配布されているMicrosoftのサンプルプログラムであるDirectX-Graphics-Samplesに含まれるサンプルは、-warpというコマンドライン引数を渡すとWARPデバイスで実行できます。この時にOutputDebugStringでこんなエラーが出て失敗します。

D3D12GetDebugInterface: This method requires the D3D12 SDK Layers for Windows 10, but they are not present on the system.

また、DirectX11で、D3D11_CREATE_DEVICE_DEBUGを指定した場合も以下のエラーが出ていました。

D3D11CreateDevice: Flags (0x2) were specified which require the D3D11 SDK Layers for Windows 10, but they are not present on the system.

これは、Graphics Tools for Windows 10を入れることで解決します。以下のようにたどってインストールします。








あるいは、管理者権限で以下のコマンドを入力するといいようです。

Dism /online /add-capability /capabilityname:Tools.Graphics.DirectX~~~~0.0.1.0

参考:



  1. http://stackoverflow.com/questions/32809169/use-d3d11-debug-layer-with-vs2013-on-windows-10
  2. https://msdn.microsoft.com/en-us/library/mt125501.aspx#InstallGraphicsTools
  3. http://blogs.msdn.com/b/vcblog/archive/2015/03/31/visual-studio-2015-and-graphics-tools-for-windows-10.aspx

Tuesday, September 22, 2015

Upgrade可能なReaders–Writer Lock を自前で実装する

Readers–writer lockとは、複数のスレッドの同時リードは許可するが、ライトする場合は他のスレッドのリードとライトをすべて排除するというものです。

C++14でshared_timed_mutexがstdに含まれましたが、リードからライトへ昇格(Upgrade)はサポートされませんでした。

実は昇格も比較的簡単に自前で実装できます。C++11以降はstd::threadやstd::atomicなど、スレッドに関する機能が大幅に強化されているので、これらを使って昇格可能なロックを実装してみます。

基本のロック



std::atomicを使います。0ならロックされていない状態、1ならロック状態です。Lockを呼んだ時点ですでにロックされていたらその場でループして待ちます。

compare_exchange_weakというのは、いわゆる"Compare and Swap"、略してCASと呼ばれます。比較と交換を1命令で行う、マルチスレッドの実装に必須の命令です。

whileでループしているので、ロック取得の待機中はCPU時間を消費します。この方式をスピンロックといいます。比較的短時間のロックは効率的ですが、ロック時間が長くなると無駄が生じる特性ががあります。今回の記事はすべてスピンロックで実装しました。

最も簡単なreaders-writer lock


リードロックとライトロックを分けた実装です。リードロックは複数スレッドから同時に可能で、ライトは1つのスレッドから可能です。


val変数の0x40000000ビットをライトロックの為に使い、それより下のビットはリードロックの個数を記録するカウンタとして動作します。

LockInternal関数がポイントで、valの中にtestBitと被るビットがある間は待機し、testBitと被らなくなったらdeltaをvalに加算します。この関数に渡す数値の組み合わせだけでリードロックとライトロックが実現できます。

これを表にするとこうなります。
ステート移動立てるビット待機するビット
アイドル→リードリードライト
アイドル→ライトライトリード、ライト


昇格可能なreaders-writer lock


リードロックからライトロックへの昇格を実装してみます。実は「昇格」をどう定義するかで実装が変わってしまうのですが、以下は実装の一例です。
実はUpgrade関数の内部で「昇格中」という別のステートが存在しています。
ステート移動立てるビット待機するビット
アイドル→リードリードライト、昇格中
アイドル→ライトライトリード、ライト
アイドル→昇格中昇格中無し


なぜ「昇格中」ステートが必要かを説明します。

ライトロック取得の為には他の全てのスレッドのリードロックの解除を待ちます。しかし、リードからライトに移行しようとする複数のスレッドがある時デッドロックが発生することになります。なぜならお互いがリードロックを手放さずに待ち続けているためです。

ここで、「昇格中」というステートを設けます。このステートに入ることで、自身のリードロックを解除し、他のスレッドのリードロックの解除も待ちます。すると残るのは「昇格中」ステートを持つスレッドのみになるので、順次ライトロックを取得できるようになります。

この方法は、デッドロックを避ける事ができますが、「Upgrade関数実行中」に他のスレッドがライトロックを取得できてしまうという欠点があります。Upgrade関数呼び出しの前後でロック対象のデータが変更されている可能性があるわけです。

リードロックを昇格可能なものと不可能なものに分けたreaders-writer lock


昇格の前後でデータが変更されたくない場合、「昇格可能な特殊なリードロックは一つだけ取得可能」とする方法があります。boostのupgrade_lockの実装のようなものです。

リードロックはReadLock/ReadUnlockの組、昇格可能なリードロックはReadLockUpgradable/ReadUnlockUpgradableの組で取得します。

ReadLockUpgradableでロックを取得した場合、他のスレッドからのReadLockを妨げずに共存しますが、他のスレッドからReadLockUpgradableを同時に取得することはできません。1つのスレッドだけがReadLockUpgradableを取れるので、Upgradeの段階で競合が発生しないことと、Upgrade中に他のスレッドがライトロックを取得したりしないことが保障されます。
ステート移動立てるビット待機するビット
アイドル→リードリードライト
アイドル→ライトライト全ビット
アイドル→昇格可能リード昇格可能リードライト、昇格可能リード


リードロックを途切れなくライトロックに昇格可能、ただし失敗付き


全てのリードロックを昇格可能にしたいけれど、Upgrade中に別のWriteLockに割り込まれたくない場合の実装です。代償として、昇格の関数名がTryUpgradeとなっており、昇格が失敗する場合があります。

なぜ失敗するかというと、途切れない昇格を実現するために昇格中にリードロックを手放さないためです。二つ以上のスレッドが同時に昇格しようとするとお互いのリードロック解放を待つためにデッドロックします。そこで、最初に昇格しようとしたスレッドのみ昇格に成功させることにします。

万が一昇格に失敗した場合、次に出来ることはリードロックを解除することだけです。もし、どうしてもライトロックが欲しければ一度リードロックも解除して他のスレッドにライトロックを譲った後、改めてライトロックの取得を試みるしかありません。

この手法の短所は利用者が例外処理を書かなければならず、ただでさえバグを入れやすいマルチスレッド処理の実装がより複雑化しやすい事です。

TryUpgrade関数のwhileループで昇格中ステートへの移行を試みます。他のスレッドに先に昇格中ビットを立てられた場合は失敗します。

自身によって昇格中ビットを立てることに成功すればLockInternalでライトステートへ移行します。readerBitsの最下位ビットを待機対象から除外するのは、自身がリードロックを持っているためリードロックのカウンタが1の時は通過させなければいけないためです。

ステート移動立てるビット待機するビット備考
アイドル→リードリードライト、昇格中
アイドル→ライトライト全ビット
リード→昇格中昇格中無し他のスレッドが昇格中ビットを取得したら失敗
昇格中→ライトライトライト、リードリードロックの1つは自分が持っているので、最下位ビットを待機対象から除外


まとめ


リードロックからライトロックへの昇格が比較的簡単に実装できることがわかりましたが、問題は昇格の方式が色々あって、しかも一長一短があるために標準化しにくいということでした。

素直に昇格はあきらめてshared_timed_mutexで満足するのも手かもしれませんし、boostやIntel TBB等の有名なライブラリを使う場合はそのライブラリで提供された昇格機能に限って使うことにしてもいいかもしれません。

テストに使ったGitHubレポジトリ
https://github.com/yorung/ThreadTest

Saturday, August 22, 2015

DDS自力ロード(Cube Map, Mipmap, DXT1, DXT3, DXT5対応)

Windows10が提供されて誰でもDirectX12による開発ができるようになりました。しかし、DirectXTKやDirectXTexのようなテクスチャのロードが出来るライブラリがまだマイクロソフトから提供されていないようです。DDSは特に複雑なフォーマットではないはずなので、DX12の準備を兼ねて自力でロードを試みてみます。ただし、DirectX11です。(DX12も対応しました。

以下でほぼ全部です。キューブマップ、ミップマップ、圧縮/非圧縮すべて対応しています。エラーチェックはちゃんとしていないので壊れたファイルや未対応の形式でバッファーオーバーランなどの危険があるかもしれません。



ID3D11Texture2DとDDSファイルは共にキューブマップやミップマップの複数のイメージを内包します。よって、DDSからそれらを含むID3D11Texture2Dを生成するときにD3D11_SUBRESOURCE_DATAの配列でその複数イメージを指定します。

ミップマップ入りキューブマップ等になるとD3D11_SUBRESOURCE_DATAの順で悩みそうですが、これはIntroduction To Textures in Direct3D 11が詳しいです。更に都合のよいことに、D3D11_SUBRESOURCE_DATAとDDSの画像の配列順は同じになっているようです。

また、各画像の開始位置を正確にD3D11_SUBRESOURCE_DATAに設定しなければいけませんが、テクスチャ用の DDS ファイルのレイアウトと題されたページが参考になります。

やってみると意外と短いコードで出来たので紹介してみました。

Friday, August 21, 2015

HLSLでフォトスフィアをLittle Planetに変換する

スマホで撮った近所の公園のフォトスフィアからLittle PlanetとかTiny Planetと呼ばれている絵ができました。



ソース画像となるフォトスフィアとはGoogleによる呼称で、「360度パノラマVR」と呼ばれたり「全方位パノラマ」と呼ばれたり呼称が統一されていない様です。最近は「HDRi」で検索すると海外の素材集がいっぱいでてきますね。投影法はEquirectangular Projection(正距円筒図法)といいます。ここではフォトスフィアで統一します。

また、結果物であるLittle Planetのような投影法をStereographic Projection(ステレオ投影)といいます。

今回の目的は、HLSLによってEquirectangular Projectionされた画像をソースとしてStereographic Projectionした画像を得ることです。 まず、簡単のためにキューブマップをソースにしてやってみます。






scaleはアスペクト比と、Little Planetの大きさを決めます。ここは見た目が良くなるように適当に数値を決めればよいところです。
キューブマップのフェッチに使うdirはWikipediaのステレオ投影で紹介されている立体射影の逆写像の式そのままです。実際にフェッチの段階でyとzを入れ替えるのは、キューブマップのY方向が上下になっているからです。どうスィズルするかによってLittle Planetの中心をどこにするか決められます。

次に、フォトスフィアをソースにしてやってみます。冒頭の絵はこれで生成します。



前半部はキューブマップと全く同じで、dirを使ってフォトスフィアのどこからフェッチするかの計算が加わりました。まず、三次元ベクトルのdirを経度(longitude、-180~180)と緯度(latitude、-90~90)に変換します。そうすると経度と緯度がフォトスフィアのX方向とY方向に対応します。あとはテクスチャUVである0~1の範囲に変換すれば完成です。

Friday, August 14, 2015

Visual Studio 2015でCとJavaを併用したAndroidサンプル、Target API Levelの変更方法

https://code.msdn.microsoft.com/windowsdesktop/hello-jni-Android-790ab73d

JavaからActivityを作り、Cから文字列を生成してTextViewに表示するプログラムがMicrosoftから公開されています。Android NDKに含まれているhello-jniサンプルと同じもので、Visual Studio 2015のプロジェクトファイルが含まれています。

KitKat用にビルドされる設定になっていたのでうちの古い機種(Vega Racer)で動かす為にTarget API Levelを下げてみました。https://developer.android.com/about/dashboards/index.html によるとGingerbreadが4.6%ほどしぶとく残っていたので、Gingerbreadまで下げてみます。



プロジェクトのプロパティと、hello-jni.Packaging というファイルのプロパティの2箇所に設定があって、それぞれDebug/Releaseと、ARM/x86の組み合わせに対してTarget API Levelを設定します。(8箇所!)PackagingのほうはAndroid SDK Managerdで該当のAPIをダウンロードしておかないと選択肢が出てきませんでした。

古いAndroid実機でF5で実行しようとすると、"Unable to start debugging. The Visual Studio C++ Android debugger requires that the target emulator/device be running Android API level 17 (version 4.2) or newer."のメッセージが出ました。この場合でもCtrl-F5でデバッグ無しで実行できました。

Sunday, August 9, 2015

Visual Studio 2015からAndroid(+OpenGL ES)開発

Visual Studio 2015でAndroidが開発できるとのことで試してみました。OpenGL ESもちゃんと動きました。





インストールオプションは適当です。



新規プロジェクト画面。インストール時に何かが抜けていたのか、Android開発ツールの追加インストール画面へのリンクが表示されています。


追加インストール画面。


Android用のテンプレートが追加されました。OpenGLESによるAndroidとiOSのクロスプラットフォームのテンプレートがあります。

WindowsからiOSが開発できるのかと期待しましたが、結論から言うと残念ながらMacが別途必要のようです。
https://msdn.microsoft.com/en-US/library/mt147405.aspx
http://stackoverflow.com/questions/31843957/vs2015-build-agent-request-has-failed-uri-not-set



テンプレートからプロジェクトを生成してビルドすると、"Build agent request has failed, URI not set..."というiOS関連のエラーが出ました。*.iOS.StaticLibrary(iOS)と*.iOS.Applicationプロジェクトを削除するとAndroidの開発環境だけが残りました。

ところで、Visual Studioが認識するデバイスとしないデバイスがあるようです。Vega RacerとAcer Iconiaは認識しました。LG GFlex2は認識せず、"No devices available"と表示されます。GFlex2はadb devicesではちゃんと認識しており、Android Studioからのデバッグも問題がなかったのでadb以外の他の原因がありそうです。





また、認識した2機種も"No devices available."と出る時がありました。少し待てば認識されることもありましたが、プロジェクトを閉じて開き直すと認識することもありました。



ACER ICONIAはx86なのですが、armeabiと表示がでているのはバグでしょうか。左のプルダウンメニューからARMかx86かを選んでデバッグするようになっており、正しいアーキテクチャを指定すれば実行できました。

[slide]프로그래머를 위한 360VR

로드뷰, 360VR의 기반 기술인 Equirectangular Projection(이퀴렉탱귤러/등장방형도법)을 이해해, DirectX11과 HLSL로 화면 출력, 큐브맵과 상호 변환 방법을 공부합니다.

Saturday, August 1, 2015

HLSLでフォトスフィアビューアを作る

ストリートビューなどで使われ、最近はAndroidのカメラなどでも誰でも手軽に作れるようになったフォトスフィアをDirectX11とHLSLでレンダリングしてみます。全方向の色を記録するためEquirectangular projectionという投影方法で長方形になっています。世界地図などでは正距円筒図法と呼ばれるようです。



これにPhoto Sphere XMP Metadataと呼ばれるメタデータが埋まることで、対応アプリから認識されます。非対応アプリからは普通のJPGファイルです。

全方向画像と言えば、ゲーム開発で主に使われるのはcube mapです。実際、cube mapで遠景を描いているゲームも多いと思いますが、フォトスフィアビューアでやる事とほとんど同じです。各シェーダの全文の例を掲示します。何をしているかというと、各ピクセルの視線ベクトルを求め、その方向の色をcubemap又はPhoto Sphereから読み取っています。見ての通り頂点シェーダ(mainVS)からピクセルシェーダ(mainPS)の第一行目まで全く同じです。



C++からは頂点は4つ出力しています。頂点バッファをバインドしない代わりに頂点シェーダで画面の四隅の位置を設定して全画面を塗りつぶします。また、pos2をラスタライザに渡してピクセルシェーダからピクセルの画面上の位置を取得できるようにします。ピクセルシェーダの一行目で画面上の位置にinvVPを掛けることで画角とカメラの方向が反映された視線方向を求められます。invVPはView行列とProjection行列を掛けたものの逆行列です。

ここから先はcube mapとphotosphereで処理が変わりますが、キューブマップの場合は視線ベクトルから色を求めるのはたったの1行です。キューブマップは元々三次元ベクトルから色を取得するものなので当然ですね。それに対し、Photo Sphereは三次元ベクトルをEquirectangularで投影した二次元テクスチャの座標に変換する作業が間に入ります。

このプログラムの三次元の座標系は、右が+x、上が+y、奥が+zと定義しています。フォトスフィア上の二次元の座標系coordX, coordYは、フォトスフィアの中心を(0,0)とし、左上を(-1,-1) 右下を(+1,+1)と定義します。便宜上の定義であって実際のテクスチャの座標ではないことに注意してください。



写真だと位置をイメージしにくいので、Equirectangular projectionされた世界地図を使って、実例で座標を指定してみます。
東経0度、北緯0度 =(0, 0)、ガーナ南方の赤道上の地点、ここは視線ベクトル(0, 0, 1)になります。
東経0度、北緯45度 =(0, 0.5)、フランスのボルドー、ここは視線ベクトルは(0, 0.7071, 0.7071)です。
西経90度、北緯45度 =(-0.5, 0.5) ウィスコンシン州、ここは視線ベクトルは(-0.7071, 0.7071, 0)です。

こうして数値を見比べてみると数式が見えてこないでしょうか。
coordXは視線ベクトルのうちy成分を無視して、xとzからatan2で角度を求めればそれがそのまま経度になります。
coordYは視線ベクトルy成分からのみ算出します。どちらも-1から1の範囲ですが、経度は球面上の距離なのでasinで変換します。

最後に、coordX, coordYは実際のテクスチャ座標系ではないので0から1の範囲に変換してテクスチャを読み込みます。

Monday, July 27, 2015

OpenGL ESのextensionを機種別に簡単に確認できるページ

http://www.gpuinfo.org/
知る限り、最も使いやすそうなページ。機種別のエクステンションはもちろん、エクステンションに対応した機種を一覧にして見られます。OpenGL ES CapsViewerというアプリで取得した情報をこのサイトにアップロードできます。Androidのみ対応のようです。

最近ではESではないOpenGL、Vulkanにも対応しています。Vulkan用のAndroidアプリはHardware CapsViewer for Vulkanで、同様に端末のVulkanのスペックをアップロードできるようになっています。
(2017/11/5 URLを更新、OpenGLとVulkanへの対応した旨を追記)

https://gfxbench.com/result.jsp
Android, iOSはもちろん、PCやOSXのOpenGLの結果も見られます。GFXBenchというベンチマークのサイトで、extensionはちょっと見つけにくい場所にあります。各機種の結果ページのInfoタブの3D APIに下向きの三角形のボタンがあり、そこを押すとextensionが見られます。

Sunday, July 26, 2015

OpenGL ES 2.0でhalf float textureは使えるか?

結論から書くと、QualcommのAdrenoならいけるかもしれません。でもhalf float textureはOpenGL ES 3.0以上でしか使わないと決めてしまったほうがよさそうです。

ES 2.0におけるhalf float textureはextension扱いになり、OES_texture_half_floatがあれば使えます。OpenGL ES 2.0とOpenGL ES 3.0でglTexImage2Dのパラメータが違うのが面倒ですが、以下のように分岐すればいいだけなので、生成そのものは難しくありません。



問題は、OES_texture_half_floatはhalf float textureの生成を保証するだけで、half float textureに対するレンダリングまで保証しない事です。half float textureへのレンダリングは EXT_color_buffer_half_float という別のextensionあれば可能になるようです。

http://stackoverflow.com/questions/8621095/how-to-use-gl-half-float-oes-typed-textures-in-ios

これは、PowerVR SGX 544MP2搭載のAcer Iconia B1-730HD タブレットではその通りになりました。OES_texture_half_floatはあってEXT_color_buffer_half_floatが無い為か、glDrawElementsの段階でGL_INVALID_FRAMEBUFFER_OPERATIONというエラーを出しました。しかし、EXT_color_buffer_half_floatが存在しない手元のAdreno 220(IM-A770K)ではhalf float textureにレンダリングが出来たり、ちぐはぐなことになっています。

また、ES 2.0のARM Mali GPUの場合はOES_texture_half_floatそのものが無いようです。

extensionを見て切り分けができないのは困ったことです。しかしそれ以上にhalf float textureの用途はレンダーターゲットが大部分だと思うので、OES_texture_half_floatという拡張名を見て「OpenGL ES 2.0でhalf float textureにレンダリング出来る!」と思ったプログラマが貴重な時間を空転させてしまうのが最も大きな問題ではないかと思う次第です。

ただし、OpenGL ES 2.0時代のGPUは帯域幅も非力なので、仮にhalf float textureにレンダリングできたとしても効果を活かせる程凝ったことができなかったかもしれません。

Tuesday, July 7, 2015

OpenGL ES 2.0のglBindAttribLocationと、ES 3.0のInput Layout Qualifiers

OpenGL ES 3.0以降では、GLSLから頂点属性の入力位置をlayoutから始まる構文で指定することが出来ます。これをKhronosの公式文書ではInput Layout Qualifiersと呼んでいます。



OpenGL ES 2.0の場合はInput Layout Qualifiersが無いのでGLSLにlocationを記述できませんが、glBindAttribLocationでC側から同じ事ができます。glLinkProgramの直前にglBindAttribLocationを使って頂点属性の名前とlocationを任意に指定できます。



ただし、Input Layout Qualifiersを使う場合Cはlocationの番号だけ知っていればよいのに対し、glBindAttribLocationを使うと名前と番号の両方をCから指定するので冗長な感じがします。

GLSLでlocationを指定せず、glBindAttribLocationもしないとglLinkProgramの時点で自動的にlocationが振り分けられます。この場合、glGetAttribLocationでlocationを問い合わせる必要があります。



こうするとES2.0でもES3.0でも動く上、名前のみで頂点attributeを結び付けられる為に分かり易いという利点があります。いいことずくめのようですが、locationの割り当てが実装依存で予想できない為、複数シェーダでlocationを共有できないという欠点があります。同じ頂点バッファを複数のシェーダで描画する場合、シェーダを切り替える毎にglGetAttribLocationで問い合わせる煩雑さがあります。

この煩雑さはES 3.0でVAO(Vertex Array Object)を使いはじめると特に顕著です。シェーダ毎にlocationが異なるとVAOをシェーダー毎に作り直さなければいけません。一つのVAOを複数のシェーダで使いまわす為にはlocationの番号を固定する必要があります。

ある程度の規模のフレームワークを作ることを考えると、glBindAttribLocationかInput Layout Qualifiersを使い、"position"は0番、"normal"は1番などと、あらかじめlocationを決めておくとうまく書けそうです。

(2016/03/29 追記)
使いやすいフレームワークのためのもっといい方法を思いついたので書きました。
http://glhub.blogspot.kr/2016/03/opengldirectx.html

Sunday, June 7, 2015

Android NDKでC++11のstd::chronoを使った時間計測

今まで以下のような秒単位の時間計測関数を、Android用とWindows用にそれぞれ作って使っていました。


今更ながら、C++11のstd::chronoでどちらでも動く関数を書けることに気づき、以下のように書きなおしました。



static auto startが妙ですが、こうするとGetTimeの初回呼び出し時の時間をstartに記録できます。あとは毎回のGetTimeの呼び出しでstartからの経過時間を秒単位で返します。

#include <chrono> で "fatal error: chrono: No such file or directory"というエラーが出てしまう場合、build.gradleでgnustlを使うよう設定する必要があります。build.gradleの一部を抜粋します。


"stlport_static"ではだめで、"gnustl_static"を使う必要があるようです。android-ndk-r10d\sources\cxx-stl\stlport\stlportにはchronoが入っていないのに対し、android-ndk-r10d\sources\cxx-stl\gnu-libstdc++\4.9\includeには入っていました。

Monday, June 1, 2015

Evan WallaceさんのWebGL Waterのソースを読んでみた

https://github.com/evanw/webgl-water
このレポジトリをローカルに取ってきてじっくり読んでみました。

波紋の作り方


浮動小数点数テクスチャをグリッドとして使っています。RGBAそれぞれを別の意味で使います。Rは水面の高さ、Gは水面の上下方向の加速度、BAは法線を格納するのに使います。

各グリッドのシミュレーションは以下の2つのルールで行います。

  • 上下左右の隣接4グリッドの高さの平均が自分の高さと異なる場合、周囲に高さを合わせるように加速度を調節します。
  • 各グリッドは自身の加速度によってのみ上下します。

グリッドは当然四角形ですが、上のルールだけで放射状に広がる波紋が出来るのが不思議です。

水面の法線の格納方法


シミュレーションが終わったらレンダリング前に各グリッドの法線を求め、上と同じテクスチャのBに法線のX、Aに法線のZを格納します。Yが格納されていませんが、法線は長さが常に1なので、ピタゴラスの定理と同じ原理でXとZからYを推測できます。

この方法の欠点は符号を復元できないことで、ここにXやZではなくYを省略する理由があります。WebGL WaterではY軸が上方向を向いており、常に正の方向になります。対してXとZは符号も保存する必要があります。

コースティック


水面が一切波立っていない場合と、水面が波立っている場合に分けて太陽光を水面で屈折させ、底または壁との衝突点を求めます。これを水面のグリッド全体で行い、波立っていない場合に比べて隣接グリッドの衝突点と自分の衝突点の距離が変化するかを見ます。広がった場合は光の密度が減り、狭まった場合は光の密度が増えると判断します。

密度の計算ですが、dFdx関数とdFdy関数を使うのがユニークです。つまり、各グリッドの波立っている場合と波立っていない場合の衝突点の計算を頂点シェーダで行い、それぞれvaryingでフラグメントシェーダに渡します。隣接グリッドの情報を直接読みに行くのではなくvaryingの変化量を見るだけなので追加のテクスチャフェッチが発生せず、高速に動作しそうです。

WebGLの拡張機能


浮動小数点数のテクスチャを使うため、OES_texture_float 拡張を使っています。
また、dFdx関数とdFdy関数を使うため、OES_standard_derivatives 拡張を使っています。

まとめ


簡潔で無駄の無いコードで、こんなきれいなエフェクトが作れるのはすごいですね。

Saturday, May 23, 2015

Android StudioのGUIを使わずにビルド→インストール→起動

以下のようなバッチファイルをgradlew.batと同じ位置に作っておくことで、Android Studioを起動せずともコマンドライン又はエクスプローラからビルド、インストール、起動ができるようになります。NDKを使用している場合のビルドまで問題なく出来ました。

※ パッケージ名は、com.pinotnoir.adamasを、アクティビティ名はcommon.pinotnoir.glactivity.PinotGLActivityを置き換えてください。

ところで、"ADB server didn't ACK" というエラーに悩まされていたのですが、原因はNsight Tegra Visual Studio Editionでした。これが内部でADBをつかっているため衝突するようです。Visual Stuidio起動中に上のバッチファイルが使えない場合、Visual Studioを終了すると使えるようになりました。



Nsight Tegra Visual Studio Editionを一時停止することはできないようです。Visual StudioとAndroid Studioを同時に使いつつ、Nsight Tegraも使いたい場合、面倒なことになりそうです。Nsight Tegraをアンインストールすると衝突は起こらなくなりました。

Thursday, May 14, 2015

GitHubのDirectXTKをgit submoduleとして組み込む

MicrosoftのGitHubアカウントでDirectXTKが公開されています。gitのsubmoduleとして組み込めるので格段に管理しやすくなりそうです。早速組み込んでみました。

組み込み方


git bashからDirectXTKを組み込みたいプロジェクトのディレクトリに移動し、以下のように入力すればDirectXTKフォルダ以下にDirectXTKのプロジェクトファイルが生成され、DirectXTKを参照するコミットを生成できます。

$ git submodule init
$ git submodule add https://github.com/Microsoft/DirectXTK.git DirectXTK
$ git commit -m "DirectXTK added as a submodule"

submoduleの何が良いのか


自分のレポジトリに外部のgitレポジトリの参照を加えることで、(半自動的に)一緒にチェックアウトできるようになります。自分のレポジトリを肥大化せずに外部モジュールを取り込めるわけです。また、参照にはバージョンも含まれているのでバージョンを取り違えてビルドに失敗することもありません。何より外部モジュールの組み込み方をgitが標準化したという側面が大きいです。

当然ながら、取り込みたいモジュールがgitレポジトリとして公開されているのが前提条件になるのですが、MicrosoftもGitHubでプロジェクトを公開してくれるようになりました。ありがたいですね。

submoduleは以下が詳しいです。

→ Git のさまざまなツール - サブモジュール

レポジトリについて


→ https://github.com/yorung/dx11x

D3DXやDirectX SDKを使わないモダンなDirectX11で.xファイルを読み込んで表示するプログラムです。スキニングも含めてアニメーションもひととおり対応しています。




Saturday, May 2, 2015

OpenGL ES 2.0のvec4配列でUBOもどきを作る際の注意点

OpenGL ES 3.0ではUBOやVAOのような現状のハードウェアで効率的に動作するAPIが使えるようになります。しかし、開発者はモダンなAPIへの対応作業を迫られる一方、ES 2.0もサポートし続けなければいけません。https://developer.android.com/about/dashboards/index.html にもあるように未だに65%のデバイスはOpenGL ES 2.0であり、この状況は当分続きそうです。
「じゃあES2.0だけでいいや」では時代遅れになるので、ES2.0でもモダンなフリをしながら両バージョンでコードを共有する方法を模索してみます。

複数のuniform変数を配列化してひとつに


http://on-demand.gputechconf.com/gtc/2013/presentations/S3032-Advanced-Scenegraph-Rendering-Pipeline.pdf の24ページ目でuniform block(UBO)を使えない場合に、複数のuniformを配列にまとめて使う方法が提案されています。これは効率的であるのみならず、C側では構造体で転送するデータを管理できるので、UBOを使ったES 3.0のコードとのアーキテクチャの差を吸収できそうです。
https://devtalk.nvidia.com/default/topic/777618/scenix/announcing-nvpro-pipeline-a-research-rendering-pipeline/ この掲示板に動画もありますね。

mat4かvec4かfloatか


複数のuniformをまとめる際、どのような型の配列として渡すか決めなければなりません。行列だけのバッファならmat4の配列、色やベクトルだけならvec4配列にします。

mat4とvec4の混合のバッファを作りたい場合もあるかもしれません。そういうときはvec4の配列とするとよさそうです。mat4配列でもよいのですがバッファサイズがfloat16個単位になってしまうので柔軟性に欠けるからです。次のようにしてGLSL側でvec4を4つまとめてmat4を復元します。


ならば、vec4配列よりfloat配列のほうが柔軟そうですが、float4つを1単位とする癖をつけたほうが良いかもしれません。以下のポストはDirectX11のもので、NVIDIAによるものなので他のGPU全てに当てはまるかはわかりませんが、float4つ単位にしなかった場合の速度低下の可能性を示唆しています。
https://developer.nvidia.com/content/understanding-structured-buffer-performance

UBOとuniform vec4配列の違い


上のように、ES3.0のUBOのようなものをES 2.0で実現してみましたが、同一ではありません。UBOはシェーダから独立したバッファで、複数シェーダで共有可能です。uniformはシェーダの一部であってC側から書き換え可能な変数です。あるuniform vec4配列を複数のシェーダ間で共有したい場合、シェーダを切り替える度にglUniform4fvでバッファを転送してあげなければなりません。

OpenGL ES特有のprecisionの問題


ES特有のつまづきそうなポイントです。uniformはシェーダ間で共有できませんが、同じprogramに属するvertex shaderとfragment shaderに限って同じ名前のuniform変数を宣言すると共有されます。しかし、precisionが違うと共有できません(glLinkProgramでエラーになります)。vertex shaderではhighp、fragment shaderではmediump、のような使い方をすることが多いと思うので注意します。(fragment shaderでhighpがエラーになる機種もある)

Monday, April 27, 2015

언리얼엔진 4, 소스코드로부터 APK 생성 후기

무료화된 언리얼 엔진4로 APK를 만들어 보았습니다. GitHub로 제공되는데 PRIVATE리포지토리라서 언리얼 엔진 공식 사이트에서 회원등록해야 리포지토리에 접근이 됩니다.

소스코드 빌드와 에디터 실행


빌드 방법은 GitHub의 Readme.md에 다 나와 있어서, 지시대로 VisualStudio 2013로 빌드하고 실행하면 에디터가 뜹니다.


샘플 실행중의 모습.


안드로이드 바이너리 만들기


공식  https://docs.unrealengine.com/latest/KOR/Platforms/Android/GettingStarted/index.html

안드로이드 APK로 패키지합니다. ETC1텍스쳐를 지정하면 단말기를 가리지 않고 실행 가능할 겁니다...



missing UE4Game binary이란 에러가 떴습니다. 사전에 Visual Studio로 안드로이드 바이너리를 빌드하지 않으면 이런 에러가 뜹니다.
(참고: 옛날 버전 얘기입니다. 세상이 좋아져서 최근 버전은 에디터로부터 바이너리 생성까지 다 해줍니다! 따라서 아래 Visual Studio작업도 필요 없습니다.)



UE4.sln에서 Solution Configurations에 "Development", Solution Platforms에 "Android"를 지정해서 빌드하면 안드로이드 바이너리가 생성됩니다. 다시 에디터에서 패키지해 보니 APK가 생성되었습니다. 단말기에 APK를 전송해 주는 배치파일까지 만들어 주는 센스!



APK가 25MB, Obb파일이 1GB나 되네요. 깔아 보면 단말기에서는 100MB정도가 됩니다.

결론


언리얼엔진은 자료가 아주 잘 정리되어 있어서 APK까지 쉽게 만들 수가 있었습니다. 단점은 엔진 자체 크기 때문에 빌드 시간이 아주 깁니다. 소스코드 빌드 시간이 걸리는 것은 어쩔 수 없는데, 간단한 APK 생성이 30분 정도 소용이 됩니다. 언리얼 개발을 위해서는 하이엔드PC부터 맞추어야 될 것 같습니다.

그리고 어떤 단말기에서 실행이 안 되었습니다. Acer Iconia B1-730HD에서는 돌아갔는데, 팬택Vega Racer에서 안 돌아갔습니다. 하지만 TAPPY CHICKEN는 Vega Racer에서 실행되었으니 조정을 잘하면 될 수도 있겠네요...

Tuesday, April 7, 2015

OpenGL ESでのUniform Bufferのバインドの仕方(DX11比較付き)

Unifrom BufferはDirectX11のConstant Bufferとほぼ同じものです。OpenGL ES 3.1では使い方もよく似ていて、以下のようにGLSLの"binding"で指定するbinding pointがほぼHLSLの"register"と同じ概念です。



C側からバッファを"register"に指定するID3D11DeviceContext::VSSetConstantBuffers に相当するのは、glBindBufferBaseです。



OpenGL ES 3.1はここまでで動作します。

GLSLで"binding"が使えないOpenGL ES 3.0では以下のそれ相当の処理をC側から行う必要があります。これはOpenGL ES 3.1でも有効で、"binding"で指定するのと同じ結果になります。更には、既に"binding"指定があってもC側から上書きすることもできます。

Saturday, March 28, 2015

Androidで動くモダンなインスタンシングを考える

このNVIDIAのスライドは基本的にPC対象です。しかし、昨今のゲーム業界はモバイル無しでは語れません。OpenGL ESで対応可能か機能を吟味してみます。

一発のDrawCallでたくさんのオブジェクトを表示するには以下の様な機能が必要です。

  1. インスタンシングのためのAPI
  2. シェーダーからバッファにランダムアクセスする方法
  3. シェーダーから複数のテクスチャにランダムアクセスする方法

サンプルプログラム程度の簡素なインスタンシングならglDrawElementsInstancedのようなAPIと、glVertexAttribDivisorでper instanceデータとして個別の座標等を渡せば済むので、2,3は無くてもいいかもしれません。しかし、マテリアルやボーンを変えてインスタンシングしたいなど、実用性を考えだすと2や3が必要となってきます。

まずは1のAPIですが、glMultiDrawElementsIndirectを使うと形の違うメッシュを一回のDrawCallで同時に描画できそうでした。しかし、残念ながらOpenGL ESでは使えなさそうです。glDrawElementsInstancedという、DirectX9時代からあった伝統的なインスタンシングで我慢します。

2と3は、マテリアルやボーンなどが異なるオブジェクトを一回のDrawCallで描くためには描画と描画の間にテクスチャの切り替えやバッファの更新を入れることができないため、描画対象の複数のオブジェクトが必要なデータを全部VRAMに上げておいて、シェーダーからランダムアクセスする必要があるということです。特にボーンが入っていて個別にアニメーションするとなると大きなバッファが必要です。

2. は嬉しいことに、OpenGL ES 3.1 ではSSBOという、ランダムアクセス可能なサイズの大きいバッファに対応しています。(資料https://www.khronos.org/assets/uploads/developers/library/2014-gdc/Khronos-OpenGL-ES-GDC-Mar14.pdf)しかし、Lollipopより前、KitKat以前のOpenGL ES 3.0の場合、uniformバッファを使う必要がありそうです。当然uniformの最大サイズに制限があるので、それより大きなバッファにランダムアクセスしたい場合はテクスチャにデータを格納してvertex texture fetchするなど更なる工夫が要求されます。

3.はbindless texture(ARB_bindless_texture)に対応していると理想的です。シェーダーからどのテクスチャにアクセスするかに関してほぼ制限がありません。しかし、OpenGL ESでは使えません。しかも、PCであっても比較的最近のものしか対応していないようです。

スライドではテクスチャをタイル化して大きなテクスチャを構成するARB_sparse_textureにも言及されていますが、同様にESでは使えません。

OpenGL ES 3.0 であればTexture Arraysが使えます。ただし、テクスチャのサイズやフォーマットを一致させる必要があるなど、アーティストに制限を課してしまうかもしれません。

古典的な手法ではTexture Atlasesや、あるいはインスタンシング対象はテクスチャを変えない、インスタンシングはしない、なども視野に入れて悩む必要がありそうです。


まとめ


PCなら以下のセットで異種混合インスタンシング
  1. glMultiDrawElementsIndirect(ARB_shader_draw_parameters)
  2. SSBO(ARB_shader_storage_buffer_object)
  3. bindless texture(ARB_bindless_texture)

Lollipop(OpenGL ES 3.1)なら以下のセットでインスタンシング
  1. glDrawElementsInstanced
  2. SSBO(ARB_shader_storage_buffer_object)
  3. Texture Arrays

OpenGL ES 3.0なら以下のセットでインスタンシング
  1. glDrawElementsInstanced
  2. uniform か vertex texture fetch
  3. Texture Arrays

OpenGL ES 2.0はインスタンシングのAPIが無いのであきらめる。

Sunday, March 22, 2015

OpenGLで異種混合インスタンシングを考える

レンダリングの高速化のためにはGPUとCPUの平行動作が重要で、一発のDrawCallで大量のモデルを描画するためにOpenGLの拡張が続けられています。2014年にはNVIDIA、AMD、INTEL各社がインスタンシングやbindless texture等の技術講義を行っていたようですね。

オーバーヘッドを減らすといえば、先日GDC2015で発表されたVulkanや、DX12の登場が待ち遠しいところですが、現状で使える最強のDrawCallはglMultiDrawElementsIndirectかと思われます。

伝統的なインスタンシングは同じモデルを大量に描画できるのに対し、glMultiDrawElementsIndirectは異なるモデルを一回のDrawCallに含められる点が頭一つ抜きん出ています。

GDC2014のスライド、
http://www.slideshare.net/CassEveritt/approaching-zero-driver-overhead
が網羅的に解説しています。

また、NVIDIAのスライド
http://www.slideshare.net/CassEveritt/beyond-porting?related=1
はモダンな描画方法の何がいいのかわかりやすく解説しています。NVIDIAのサンプルプログラムMultiDrawIndirectは異種混合インスタンシングを実現したサンプルです。

原理は簡単で、同一の頂点バッファに複数のモデルを含めておくだけです。頂点バッファのどの部分を描画に使うかは、DrawElementsIndirectCommand構造体の変数が指し示します。glMultiDrawElementsIndirectにはDrawElementsIndirectCommandを配列で渡せるので異種混合インスタンシングができます。ただし、同じ頂点フォーマット、同じシェーダー、同じレンダーステートどうしでないと一緒に描画できません。

また、残念なのはAndroidでは現状で最も機能豊富なAndroid Extension Packに対応していてもglMultiDrawElementsIndirectは使えなさそうなことです。

AndroidではglDrawElementsIndirectがES 3.1から、glDrawElementsInstancedがES 3.0から使えます。これらは基本的に一回のDrawCallで同じモデルを複数描画することができます。OpenGL ESでどうしても異種混合インスタンシングをしたいなら、ボーンを操作したりモーフィングしたりで形を変えながら別のモデルに見せることは出来るかもしれません。しかし、基本的にインスタンスごとにインデックスバッファを変えたり頂点数を変えたりできないです。

現状では異種混合インスタンシングはPC、あるいはフルスペックのOpenGLをサポートしたSHIELD tablet等でしか使えなさそうです。


Saturday, March 7, 2015

Unreal Engine 4をソースからビルドしてAndroid用APKを作ってみる

Unreal Engine 4が無料化したので使ってみました。プログラマーなのでGitHubから取得します。無料化したとはいえGitHubではPRIVATEレポジトリなので、まずUnreal Engineの公式サイトで会員登録した上で自身のGitHubアカウントを紐付けます。すると、GitHubのUnreal Engineのレポジトリにアクセスできるようになります。

ソースコードのビルドからエディタ起動まで


必要な情報はGitHub上のReadme.mdにほとんど書いてあります。指示通りVisualStudio 2013でビルドするとエディタが起動しました。


サンプルプロジェクトを生成して実行している様子。ここまで何のエラーもありませんでした。えらく簡単です。


Android のバイナリの作り方


公式: https://docs.unrealengine.com/latest/JPN/Platforms/Android/GettingStarted/index.html

さっそく、AndroidのAPKを作ってみます。どの端末でも動くようにとりあえずテクスチャはETC1にしました。



missing UE4Game binary というエラーが出ました。アンドロイド用のバイナリをVisual Studioでビルドしていなかったのが原因です。
(追記:昔のバージョンの話です。最近はエディタからの操作だけでアンドロイド用のバイナリがコンパイル&ビルドされます。よって、以下のVisual Studioからバイナリを作る手順も必要ありません)



UE4.slnで、Solution Configurationsを"Development"、Solution Platformsを"Android"にしてビルドします。改めてエディタからパッケージしてみると、APKの生成に成功しました。インストール用のバッチファイルを実行すればAndroid端末にインストールできます。



APKが25MB、obbファイルが1GB…何かの間違いではと思いたくなるサイズですが、実際にインストールすると100MB程になります。

まとめ


この少ない手順でソースコードからAPKまで作れるのは驚異です。ドキュメント類も充実していて迷うことがありません。難点もあります。ソースコードのビルドに時間がかかるのは当然かもしれませんが、APKの生成に30分程掛かりました。本格的に開発するとなるとSSD付のハイエンドPCが欲しいところです。

また、動く端末と動かない端末があります。Acer Iconia B1-730HDでは実行できたのですが、 Adreno 220搭載の Pantech Vega Racerでは動きませんでした。端末が古すぎるせいでしょうか。ただ、TAPPY CHICKENはVega Racerでも動いたので調整次第なのかと思います。

また、Unrealで作ったゲームは実行ファイルも大きく、端末でゲームが起動するまでに時間が掛かりました。ある程度規模の大きいゲーム向けであって、カジュアルゲームにはオーバースペックということかもしれません。

Saturday, February 28, 2015

Visual StudioとAndroid StudioでOpenGLを使ったマルチプラットフォーム開発

実際に構築してみました。ここにコミットしています。
ソースを共用しながら、AndroidとWindowsの両方で動かせるようにしてみました。C++を主に使うのでNDKも入れておきます。

Android StudioでassetsフォルダやC++のソースを階層の浅い場所に移動


Androidのassetsフォルダにテクスチャやシェーダーを配置します。ただ、eclipse時代と違ってAndroid Studioはデフォルトで app/src/main/assetsとすごく深い場所にあります。これでは不便なので、build.gradle に以下のように書いて使いやすい場所を指定します。(同時にC++のソースも浅い場所に移動しています)


main.assets.srcDirsはassetsを置く場所、main.jni.srcDirsはC++のファイルを置く場所です。'../../assets'のようにAndroid Studioのプロジェクトファイルより上位のフォルダも指定できることがわかりました。

Visual Studioからも同じソースやファイルを使うので、この任意のフォルダ位置を指定する機能は重宝します。


assetsフォルダをC++から使う


AndroidのassetsはC++から直接アクセスできないので、Javaを経由します。以下のようなヘルパークラスを作っておき、C++から使います。

Javaでは指定のファイル名でassetsフォルダから読みだしてバイト列にしてC++側に返します。C++側でJavaからバイト列をもらい、改めてcallocでメモリを割り当て直してコピーしています。callocでサイズを+1しているのは、テキストファイルの終端に'\0'を入れたいためです。


テクスチャをJava経由でC++から生成


Android機はGPUによって対応している圧縮テクスチャフォーマットが違うので面倒です。この対応は後で考える事とし、とりあえず手始めにjpgやpngなどをまず使えるようにしておきます。
AndroidではJavaから直接テクスチャを生成すると簡単です。assetsフォルダからBitmapFactory.decodeStreamでBitmapを生成し、更にBitmapからGLUtils.texImage2Dでテクスチャを生成します。JavaからC++に渡すのはGLuint型(Javaではint型)の生成済みのテクスチャを表すtexture nameだけなのでシンプルに実装できます。


Windows版の対応


それぞれAndroid版と同名のローダを作ります。

LoadFileはassetsフォルダのファイルをfopenでファイルを読み込むだけです。
LoadTextureViaOSではGDI+を使うことで、アルファチャンネル付きの32bitのrawデータが得られます。ここからテクスチャを生成しています。

GDI+とOpenGLではRGBAの並びが違うのでビットシフトで置き換えています。
OpenGLは以前作ったWGLGrabberを使って関数ポインタを取得しています。


ネイティブクラスの設計


基本的に出来る限りC++で処理すると決めたので、onTouchEventを使ったタッチ等のイベントは全部JavaからC++に渡します。また、GLSurfaceViewを使うので、onDrawFrameからC++のレンダラーを呼び出すことになります。

注意すべきはJavaのスレッドが2つあることです。onTouchEventはUIスレッド、onDrawFrameはレンダースレッドです。

今回は簡単にするため、タッチの座標だけ保存しておいてonDrawFrameの中からタッチイベントもC++に渡します。こうすることでC++は常にレンダースレッドで実行されることが保証されます。

将来的にはマルチスレッドの同期取りはC++側に移動したほうが最適化しやすいかもしれません。


まとめ


OpenGLを使った開発はOpenGL関連のコードが大多数を占めるので、このようにプラットフォーム依存する場所を開発の始めに括っておくと以後のマルチプラットフォーム化が楽になります。

Wednesday, February 18, 2015

DIY: Binding Lua5.3 with C++11

Let's bind C++ stuff to Lua5.3! :)

THE MEAT AND POTATOES


It might be enough to simple scripting for several projects.


BRUTEFORCE WAY


Our main weapon is C++, so the above code doesn't satisfies me. So, let's bind C++ classes and create instances from Lua.

It works fantastic. Then MyClass is able to be generated with scripting, and the garbage collector calls our destructor and delete it.
However, it might be unpleasant chore to make additional method and classes. The bind code is too long and messy!

HACKING


Let's make stuff fun. The most brilliant thing for us is we are living in future. Now, C++11 allow us what we couldn't do before.

Below code is all to bind MyClass:


Most simple binds are able to be written with lambda. So, for compile it C++11 must be enabled. To prevent crashes by scripting bug, define a GET_MYCLASS macro to ensure the first element of the Lua stack is our MyClass. Note that MyClass instances are constructed with placement new. In this case, the destructor must be called directly instead of calling delete operator.

BindClass defined like below:


As you can see, there is no MyClass dependencies. To avoid dependencies, I made a closer generator and put the class names and userdata creators into upvalue. Of course, BindClass can also be used for binding two or more extra classes.

CONCLUSION


Binding to Lua is not so complex and easy to learn. It's only combinations of small number of features such as stack and metatables. I saw programmers bothered increasing binding codes. I bet we can simplify them. Hope this helps to find your own solution.

自力Luaバインド 決定版

前回の続きです。クロージャでクラスのインスタンス生成をすっきり書けそうなのでやってみました。 ここまでシンプルにかければ、バインドの為の外部ライブラリの力を借りずとも十分やっていけそうです。BindClassという新たな関数が追加され、こちらに雑多なコードが移動しています。BindClassがuserdataを生成するだけの関数(ラムダで定義)を引数としている点がポイントです。ラムダでは前回同様userdata上にplacement newでインスタンスを初期化します。 BindClassもこれだけで、特定のクラス名などが取り除かれ汎用化されています。CreateClassMetatableでメタテーブルを生成するのは定番のコードです。

C++にだけ慣れていると奇妙に見えるのはCreateCppClassInstanceです。これはLuaから呼べる関数型をしていますが単独では呼べません。upvalueという領域にクラス名とクラスのインスタンス生成を行う関数を結び付けてクロージャを生成しておくことで初めて関数として呼べるようになっています。そのクロージャを生成してクラス名と同名のグローバル関数を登録する部分がCreateClassInstanceCreatorです。

このようにしてクラスの種類数だけクロージャを生成することで、どんな種類のクラスインスタンスもCreateCppClassInstanceに生成させることが出来るようになります。

ところで、CreateCppClassInstanceのactualInstanceCreatorはlua_CFunction型として取り出して、直接CからactualInstanceCreatorを呼び出しています。これはBindClassの引数として受け取っているcreatorです。ということは、Luaが直接creatorを呼び出すことは無いのでlua_CFunction型を使う必要は無いということです。lightuserdataとして任意の型の関数にしても問題なさそうです。

LuaのC++バインドをラムダ式で書く

以前LuaからC++のクラスを使えるように自前バインドを書きました。しかし、非常に冗長でした。規模の大きいクラスになると恐ろしい長さになりそうです。そこで、C++11の恩恵に与りながら短く書き直してみました。

まず、luaL_setfuncsで複数関数を一気に登録します。その際luaL_Regの配列を渡すのですが、関数部をラムダ式で書きます。MyClassのポインタはGET_MYCLASSというマクロで取得し、更に万一型が合わないなどで取得に失敗してもクラッシュしないようになっています。

また、placement newでMyClassのメモリ確保をluaに任せるようにします。これによってダブルポインタを使う無駄を省いた上にコードもすっきりしました。deleteのかわりにp->~MyClassを明示的に呼ぶ必要があります。placement newを使うためには #include <new>が必要です。

単独のサンプルとしてはこのくらいが最短かと思いますが、複数のクラスをバインドするなら上のコードの一部を共通化できるのでもう少し短くなりそうです。例えばメタテーブルを生成して__indexに自身を指定するあたりはサブルーチン化します。

MyClassNewはマクロ化すれば複数クラスで使いまわせそうです。本当はテンプレート関数化したいところですが、メタテーブルに渡す文字列をテンプレート引数にするにはややトリックが必要です。現状のC++では文字列定数をテンプレート引数に出来ないためです。


Tuesday, February 10, 2015

Luaでシェーダ言語のベクトルのswizzlingのようなことをしてみる

3DをやっているとLuaからC++のベクトルや行列にアクセスしたいことがあります。今回の目標はLuaからC++のベクトルを作って、更にシェーダ言語のswizzlingみたいなことをしてみます。例えばこんな感じに書けるようになります。 これをやるためにはメタテーブルの"__index"と"__newindex"をそれぞれC関数として、実行時に解釈します。以下全ソースです。 swizzlingして読みだす時(右辺に来るとき)はVec4Index、swizzlingして書き込む時(左辺にくるとき)はVec4NewIndexが呼ばれます。

Vec4NewIndexで、代入する値がベクトルであってもスカラーであってもいいようにLUA_TNUMBERとLUA_TUSERDATAに分岐しています。

Luaからベクトルの長さを得るGetLengthというメソッドがあります。BindVec4関数内でバインドしようとしているのはだめな例です。なぜならBindVec4で作っているのはメタテーブルであって、__で始まるメタメソッドは定義出来ても任意のメソッドは定義できません。前回のMyClassはメタテーブルの__indexにメタテーブル自身を参照させていたのでメタテーブル内に任意のメソッドを定義することができましたが、今回は__indexをswizzlingのためのC関数であるVec4Indexにつかってしまっています。

ではどうするのかというと、Vec4Index関数の中で"GetLength"を参照しようとしたかを判定してVec4GetLength関数を返しています。これでLuaからGetLengthが使えます。

テストはこれでいいかもしれませんが、実際に使えるものにするにはもうひと工夫する必要がありそうです。


Monday, February 9, 2015

Lua stack dump (WRONG CASE)

This is very simple Lua stack dumper. It extracts all element values by using lua_tostring whatever the types is not a string. It looks work well, but the problem is that lua_tostring overwrites values in the stack by string version.

One of correct code is below. The programmer always needed to take a step to ensure their correct types when accessing the Lua stack.

Sunday, February 8, 2015

DIY: Bind OpenGL(WGL) APIs using std::regex

Windows SDK doesn't provides direct way to access modern OpenGL APIs. So, as you know developers suffer to choose the method to acquire huge amount of address of WGL APIs. (or just decide using GLEW)

The popular DIY option is parsing the Khronos Group's extension header files with script languages and generate C++ glue code. But, I didn't. A good news is std::regex is now available on Visual Studio 2013, So I felt it's the simplest option to do.

Tools are always getting better and better! Isn't it?:)

std::sregex_iterator looks wired from the perspective of STL programmers, but it is the way for multiple match of OpenGL API declaration in the header files.(std::regex_match is only for single match, it wasn't for me) and std::smatch contains divided parts of a declaration, such as name of API, return type and parameters.

My project files are available on GitHub.

Saturday, February 7, 2015

Lua5.3を自力バインド

LuaとC++を自力でつないでみます。

以前はtolua++を使っていましたが、グルーコードを毎回生成するのが意外と不便でした。ヘッダの日付が変わってしまったために全体ビルドがかかってしまったりします。nmakeやバッチファイルを導入してみたり、自動生成したファイルをバージョン管理に含めるべきか悩んだりしました。結局外部定義ファイルよりはcppに直接バインド内容を書けてしまうほうが悩まないかもしれません。

例えば、WindowsのMessageBoxをLuaから使えるようにするコードです。あっけないほど簡単です。わざわざ外部のライブラリを導入する必要もないです。プロジェクトの性質によってはこれで十分用足りるでしょう。

ここからが本題です。C++を使うからにはC++っぽいことをしたいわけです。目指す仕様は、
・C++のクラスをバインドしてLuaからインスタンスを作る
・グルーコードでnewからdeleteまで勝手にやってくれる
・ガーベージコレクションの時点でdeleteさせたい

これを実現したコードが以下です。

C++のBindMyClass関数でLua内にMyClassというクラスをメタテーブルで定義します。MyClassはSetValueとGetValueという2つのメソッドを持ち、__gcでガーベージコレクションの直前に呼ぶ関数があることをLuaに教えます。__indexはやや特殊ですが、MyClassのインスタンスがメソッドを探しに行くテーブルがメタテーブル自身であることを示します。これによってメタテーブル内にメソッドを定義できます。

最後のlua_registerで"MyClass"という名前のLua関数を呼ぶとC++側のMyClassNew関数がインスタンスを生成して返します。クラス名と同じ名前の関数として登録していますが関数名は何でも構いません。MyClassNewではLuaのユーザーデータを生成してC++側で生成したMyClassのポインタを格納し、先ほど作っておいたMyClassのメタテーブルを設定します。

MyClassSetValueとMyClassGetValueはLua側からC++側の変数に値を格納したり取り出したりする所です。せっかくなのでLua5.3の新機能である整数形式を試してみます。lua_Integerはデフォルトで64bitになりました。よって、int型に代入する場合キャストしないとwarningが出ます。

一見複雑ですが、アセンブリを読むような難解さはあれど覚えることはそう多くはないです。一度自力でバインドする方法を試しておくと、たとえ他のオープンソースのバインダーを使う場合でもより理解しやすくなるはずです。

GitHubはここです。

Saturday, January 31, 2015

Google ANGLEでWindows上でOpenGL ESを使う簡単な方法

AndroidでOpenGL ESを使ったアプリを開発する際にAPKを生成してデバイスに転送しながらデバッグするのは非効率です。そこで、Windows上でもOpenGL ESを実行できるようにしてゲームエンジン部を共通化しておくと便利です。

直接WindowsのOpenGLを動かせるようにしてもよいのですが、下位でDirectXを呼ぶANGLEで組むとIntel GPAやNVIDIA Nsightでデバッグできるメリットも得られます。

ところで、最近 git clone https://chromium.googlesource.com/angle/angle からcloneするだけではVisual Studioのslnファイルが得られなくなっているようです。以下のページで説明されているようにdepot_toolsという独自の環境を構築する必要があります。

https://code.google.com/p/angleproject/wiki/DevSetup

しかし、とりあえず最新でなくてもいいので以前のように手間をかけずに手軽に実行したい場合はslnを含む最後のブランチである"2214"をチェックアウトするといいです。 git bashより以下のように入力します。

1. git clone https://chromium.googlesource.com/angle/angle
2. git branch 2214 origin/chromium/2214
3. git checkout 2214

これで2214 ブランチをチェックアウトできました。
angle\projects\build\all.sln が生成されているのでビルドします。
   
なぜかessl_to_glsl, essl_to_hlslという二つのプロジェクトからエラーが出ますが、気にしないことにします。

 



自分のプロジェクトの必要に応じてヘッダとライブラリを設定します。こうして作ったプロジェクトをGitHubにアップしておきました。

Thursday, January 29, 2015

std::regexを使い、自力でOpenGL(WGL)バインド

WindowsでOpenGLを使う為にはwglGetProcAddress関数でAPIの関数ポインタを収集しなければなりません。文字列処理に長けたスクリプト言語でパーサを作って https://www.opengl.org/registry/ のヘッダからCのソースを生成するのが一般的かと思いますが、Visual Studio 2013でregexがstd::regexに移動していたのでC++だけでやってみることにします。
追加ライブラリ無しで正規表現が書けました。

STLに慣れているとstd::sregex_iteratorが奇妙に見えますが、これで複数のOpenGL関数定義を巡回させます。std::regex_matchでも正規表現を実行できますが、こちらは最初の一回しかマッチさせられません。マッチ結果はstd::smatchを使って戻り値、関数名、引数リストを取り出します。

GitHubはここです。