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++では文字列定数をテンプレート引数に出来ないためです。