RenderScript の用途は大きく分けて二通りあります。
Compute と Graphics です。
Compute (rs_cl) は rsForEach() 命令を使い、CUDA/OpenCL のような
並列処理を意識した呼び出し方となっています。
おそらくマルチコア CPU による並列化や GPU が将来的にサポートされる
のではないかと考えられます。
Graphics (rs_graphics) は少々特殊で、描画ループを Native 実行する
C言語で記述できることが特徴です。今までにないレイヤーですが、
NDK の部分的な置き換えを狙っているようにみえます。
またシームレスに rs_cl を呼び出せるのも大きなメリットです。
RenderScript は他にも vector や matrix 演算がサポートされており、
NEON 等の SIMD 命令に展開されるのではないかと考えられます。
ただし現状だと Android 3.x (Honeycomb) デバイスの大半が
NEON 命令が使えない NVIDIA Tegra 250 (Tegra2) なので、
その効果を確認することができませんでした。
●速度テスト
RenderScript は本当に速いのか、実際に速度を調べてみました。
Matrix4x4 * float4 を 100万頂点分演算します。
データの転送時間は含まれていません。演算とループのみです。
ただし (8) に関してはスレッドの起動時間、終了同期(join) の時間が
含まれています。
●RenderScript
(1) は確かに速いですが、普通に NDK と C++ だけで書いたコード
(5)/(6) とほとんど差がありませんでした。
NEON があればもっと大きく差がついたのかもしれません。
同時に dual core CPU であっても、RenderScript は特に並列実行
していないことがわかります。
(2) のように自分で乗算展開した場合はかなり遅くなっています。
NDK の Cコンパイラよりも通常演算の最適化がまだ弱いようです。
●Java
(3) が予想よりも高速でした。
演算自体の負荷が高い場合は比較的言語の差が出にくいのですが、
JIT が効果的に働いているのではないかと思われます。
結果を Java で受け取るならば、データ転送時間が不要な分もっと
差が縮まる可能性があります。
(4) は Android に含まれるライブラリを利用したものです。
最も低速でした。
●NDK
RenderScript (1) にかなり近い数値でした。
debug build ではこの半分の速度だったので release build の最適化が
かなり効いているようです。
展開コードを調べると、fmuls + fadds の組み合わせが最適化により
fmacs の積和命令に置き換わっていることがわかります。
試しにアセンブラで記述してみたのが (7)。
やはり NEON はなくても fmacs が効果的です。
さらに最速ケースを調べるためにスレッド化し、Cortex-A9 2 core を使って
並列実行したのが (8) です。
以上より
RenderScript(LLVM)
・Single Thread 実行
・rsMatrixMultiply() は内部で fmacs (積和) 命令を使用し最適化されている
・RenderScript は通常の演算は fmacs (積和) に展開しない
NDK(gcc)
・通常の演算コードも fmacs (積和) に最適化される
おそらく NEON があれば rsMatrixMultiply() が更に高速で、コンパイラが
展開したコードよりも高速に実行できたのではないかと考えられます。
●まとめ
SIMD がなくマルチコアも活用されないので、そのまま NDK C++ で書いた
コードと大差ない結果となってしまいました。
今後 Android OS 4.0 (Ice Cream Sandwich) が登場し、さらに多くの
デバイスで走るようになれば面白いデータが見られるのではないかと思います。
当然ながら、何でもできる NDK が最も速度を引き出せることには変わりありません。
その代わり全部自分でやらなければなりません。
armeabi, arameabi-v7a, neon, x86 等のアーキテクチャ対応、
SIMD 対応、スレッド化などすべてに取り組むのは大変です。
RenderScript はアーキテクチャを選ばず、バイナリ化してもランタイムの
改良で性能が向上する可能性があります。
これまで使ってきたように Java から気軽に呼び出せるため
Android 4.0 が普及すれば色々と使い道が増えると思います。
関連エントリ
・Android 3.x RenderScript (6) Graphics まとめ
・Android 3.x RenderScript (5) 任意の 3D モデルを描画する
・Android 3.x RenderScript (4) script で頂点を書き換える
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
Compute と Graphics です。
Compute (rs_cl) は rsForEach() 命令を使い、CUDA/OpenCL のような
並列処理を意識した呼び出し方となっています。
おそらくマルチコア CPU による並列化や GPU が将来的にサポートされる
のではないかと考えられます。
Graphics (rs_graphics) は少々特殊で、描画ループを Native 実行する
C言語で記述できることが特徴です。今までにないレイヤーですが、
NDK の部分的な置き換えを狙っているようにみえます。
またシームレスに rs_cl を呼び出せるのも大きなメリットです。
RenderScript は他にも vector や matrix 演算がサポートされており、
NEON 等の SIMD 命令に展開されるのではないかと考えられます。
ただし現状だと Android 3.x (Honeycomb) デバイスの大半が
NEON 命令が使えない NVIDIA Tegra 250 (Tegra2) なので、
その効果を確認することができませんでした。
●速度テスト
RenderScript は本当に速いのか、実際に速度を調べてみました。
Matrix4x4 * float4 を 100万頂点分演算します。
Android 3.1 API Level 11 LG Optimus Pad L-06C Tegra250 1GHz (Cortex-A9 dual) NEON 無し VFPv3-D16 (1) RenderScript rsMatrixMultiply 160msec (2) RenderScript 乗算展開 248msec (3) Java 乗算展開 571msec (4) Java android.opengl.Matrix 呼出 2537msec (5) NDK C++ 関数呼び出し 167msec (6) NDK C++ 乗算展開 168msec (7) NDK inline asm fmacs 117msec (8) NDK inline asm fmacs + pthread 72msec (実行時間。値が小さいほうが高速)
データの転送時間は含まれていません。演算とループのみです。
ただし (8) に関してはスレッドの起動時間、終了同期(join) の時間が
含まれています。
●RenderScript
(1) は確かに速いですが、普通に NDK と C++ だけで書いたコード
(5)/(6) とほとんど差がありませんでした。
NEON があればもっと大きく差がついたのかもしれません。
同時に dual core CPU であっても、RenderScript は特に並列実行
していないことがわかります。
(2) のように自分で乗算展開した場合はかなり遅くなっています。
NDK の Cコンパイラよりも通常演算の最適化がまだ弱いようです。
// RenderScript: (1) rs_matrix4x4 TransformMatrix; void root( const float4* vin, float4* vout ) { *vout= rsMatrixMultiply( &TransformMatrix, *vin ); }
// RenderScript: (2) rs_matrix4x4 TransformMatrix; void root( const float4* vin, float4* vout ) { vout->x= TransformMatrix.m[ 0] * vin->x +TransformMatrix.m[ 4] * vin->y +TransformMatrix.m[ 8] * vin->z +TransformMatrix.m[12] * vin->w; vout->y= TransformMatrix.m[ 1] * vin->x +TransformMatrix.m[ 5] * vin->y +TransformMatrix.m[ 9] * vin->z +TransformMatrix.m[13] * vin->w; vout->z= TransformMatrix.m[ 2] * vin->x +TransformMatrix.m[ 6] * vin->y +TransformMatrix.m[10] * vin->z +TransformMatrix.m[14] * vin->w; vout->w= TransformMatrix.m[ 3] * vin->x +TransformMatrix.m[ 7] * vin->y +TransformMatrix.m[11] * vin->z +TransformMatrix.m[15] * vin->w; }
●Java
(3) が予想よりも高速でした。
演算自体の負荷が高い場合は比較的言語の差が出にくいのですが、
JIT が効果的に働いているのではないかと思われます。
結果を Java で受け取るならば、データ転送時間が不要な分もっと
差が縮まる可能性があります。
// java : (3) float[] fv= transformMatrix.getArray(); float[] destbuf= new float[MAX_TRANSFORM*4]; long stime= System.currentTimeMillis(); for( int i= 0 ; i< MAX_TRANSFORM ; i++ ){ int base= i*4; destbuf[base+0]= fv[ 0] * srcbuf[base+0] + fv[ 4] * srcbuf[base+1] + fv[ 8] * srcbuf[base+2] + fv[12] * srcbuf[base+3]; destbuf[base+1]= fv[ 1] * srcbuf[base+0] + fv[ 5] * srcbuf[base+1] + fv[ 9] * srcbuf[base+2] + fv[13] * srcbuf[base+3]; destbuf[base+2]= fv[ 2] * srcbuf[base+0] + fv[ 6] * srcbuf[base+1] + fv[10] * srcbuf[base+2] + fv[14] * srcbuf[base+3]; destbuf[base+3]= fv[ 3] * srcbuf[base+0] + fv[ 7] * srcbuf[base+1] + fv[11] * srcbuf[base+2] + fv[15] * srcbuf[base+3]; }
(4) は Android に含まれるライブラリを利用したものです。
最も低速でした。
// java : (4) for( int i= 0 ; i< MAX_TRANSFORM ; i++ ){ int base= i*4; Matrix.multiplyMV( destbuf, base, fv, 0, srcbuf, base ); }
●NDK
RenderScript (1) にかなり近い数値でした。
debug build ではこの半分の速度だったので release build の最適化が
かなり効いているようです。
展開コードを調べると、fmuls + fadds の組み合わせが最適化により
fmacs の積和命令に置き換わっていることがわかります。
// NDK C++ : (6) static void loopndk2( const Vect4* vin, Vect4* vout, int length ) { for( int i= 0 ; i< length ; i++ ){ vout->x= TransformMatrix._11 * vin->x +TransformMatrix._21 * vin->y +TransformMatrix._31 * vin->z +TransformMatrix._41 * vin->w; vout->y= TransformMatrix._12 * vin->x +TransformMatrix._22 * vin->y +TransformMatrix._32 * vin->z +TransformMatrix._42 * vin->w; vout->z= TransformMatrix._13 * vin->x +TransformMatrix._23 * vin->y +TransformMatrix._33 * vin->z +TransformMatrix._43 * vin->w; vout->w= TransformMatrix._14 * vin->x +TransformMatrix._24 * vin->y +TransformMatrix._34 * vin->z +TransformMatrix._44 * vin->w; vin++; vout++; } }
試しにアセンブラで記述してみたのが (7)。
やはり NEON はなくても fmacs が効果的です。
さらに最速ケースを調べるためにスレッド化し、Cortex-A9 2 core を使って
並列実行したのが (8) です。
// NDK asm : (7)/(8) __asm__ __volatile( "\ 1: \n\ vldmia %1!, {d8-d9} \n\ vldmia %0, {d0-d7} \n\ fmuls s20,s0 ,s16 \n\ fmuls s21,s4 ,s16 \n\ fmuls s22,s8 ,s16 \n\ fmuls s23,s12,s16 \n\ fmacs s20,s1 ,s17 \n\ fmacs s21,s5 ,s17 \n\ fmacs s22,s9 ,s17 \n\ fmacs s23,s13,s17 \n\ fmacs s20,s2 ,s18 \n\ fmacs s21,s6 ,s18 \n\ fmacs s22,s10,s18 \n\ fmacs s23,s14,s18 \n\ fmacs s20,s3 ,s19 \n\ fmacs s21,s7 ,s19 \n\ fmacs s22,s11,s19 \n\ fmacs s23,s15,s19 \n\ vstmia %2!, {d10-d11} \n\ subs %3,%3,#1 \n\ bne 1b \n\ " : "=&r"( mat ), "=&r"( vin ), "=&r"( vout ), "=&r"( length ) : "0"( mat ), "1"( vin ), "2"( vout ), "3"( length ) : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10", "d11", "cc" );
以上より
RenderScript(LLVM)
・Single Thread 実行
・rsMatrixMultiply() は内部で fmacs (積和) 命令を使用し最適化されている
・RenderScript は通常の演算は fmacs (積和) に展開しない
NDK(gcc)
・通常の演算コードも fmacs (積和) に最適化される
おそらく NEON があれば rsMatrixMultiply() が更に高速で、コンパイラが
展開したコードよりも高速に実行できたのではないかと考えられます。
●まとめ
SIMD がなくマルチコアも活用されないので、そのまま NDK C++ で書いた
コードと大差ない結果となってしまいました。
今後 Android OS 4.0 (Ice Cream Sandwich) が登場し、さらに多くの
デバイスで走るようになれば面白いデータが見られるのではないかと思います。
当然ながら、何でもできる NDK が最も速度を引き出せることには変わりありません。
その代わり全部自分でやらなければなりません。
armeabi, arameabi-v7a, neon, x86 等のアーキテクチャ対応、
SIMD 対応、スレッド化などすべてに取り組むのは大変です。
RenderScript はアーキテクチャを選ばず、バイナリ化してもランタイムの
改良で性能が向上する可能性があります。
これまで使ってきたように Java から気軽に呼び出せるため
Android 4.0 が普及すれば色々と使い道が増えると思います。
関連エントリ
・Android 3.x RenderScript (6) Graphics まとめ
・Android 3.x RenderScript (5) 任意の 3D モデルを描画する
・Android 3.x RenderScript (4) script で頂点を書き換える
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
2011/11/06
Android 3.x RenderScript (6) Graphics まとめ
Android には様々な描画手段が用意されています。
GPU を使って高速に描画可能な組み合わせは下記の通り。
下に行くほど低レベルな API になります。
実際に使ってみた限り、描画速度もほぼこの順番になるようです。
● RenderScript
マテリアルのセットアップやジオメトリ演算など、描画ループ自体を
Native 実行可能な C言語で記述することが可能です。
リソースの生成や初期化は Java で行い、プロジェクトも開発環境も
Java と非常に親和性が高いのが特徴です。
Compute 用の RenderScript を簡単に呼び出せます。
大量の並列演算を伴う場合も対応できます。
描画 API は OpenGL ES 2.0 の上に構築された Class ライブラリとなります。
理解してしまえば OpenGL ES 2.0 を直接扱うよりも簡単で使いやすいですが、
直接 GL 命令を使えないのでできることに限界があります。
特にぎりぎりまでハード性能を引き出すような用途には向きません。
また描画に GLSL を使う場合は OpenGL ES 2.0 の知識も必要となります。
利点
・C言語 + Native による高速実行。
・CPU アーキテクチャ非依存でどの環境でも走る。
・Java と親和性が高く Java のプロジェクトに組み込みやすい。
・描画 API が Class 化されており GL 直接より簡単。リソースも扱いやすい。
・rs_cl と併用できる。
欠点
・Android 3.0/API Level 11 以上のみ
・GL 周りで高速化に限界がある。
・描画リソースを一旦 script に渡してから描画する必要あり。
● GLSurfaceView + OpenGL ES 2.0 (Java)
GPU のハードウエアや OpenGL をよく知っていて、普段から GPU の性能を
めいっぱい使うような使い方をしている場合こちらの方が速いです。
CPU 負荷よりも GPU 負荷のほうが高いと考えられるためです。
逆にもし CPU がボトルネックとなっている場合は RenderScript の方が
向いているといえるでしょう。
アーキテクチャ非依存という点では同一で、特に OpenGL に慣れているなら
容易に扱えます。
利点
・CPU アーキテクチャ非依存でどの環境でも走る。
・Java だけで完結するため開発しやすい。
・GL API をそのまま使うため機能制限が無い
欠点
・Android 2.3/API Level 9 以上 (2.2 ではバグあり)
・低レベルな GL API をそのまま使うためコード量が増える
・Java によるボトルネックが考えられる
● GLSurfaceView + OpenGL ES 2.0 (NDK)
OpenGL ES 2.0 そのままで、かつ普通に C/C++ で開発できるため他の
プラットフォームで経験があるなら一番親和性が良いはずです。
ただし Java 環境への dll によるアドオンとなるため jni による通信が必要。
開発も両環境を行き来する必要が生じます。特にデバッグで。
また Native Code への事前コンパイルなので、ビルド時に実行環境が固定されます。
速度も一番引き出せる可能性がありますが、ハードに近いところまでそれなりの
知識が必要です。
利点
・C/C++ / OpenGL ES そのままなのでソース互換性が高い(移植しやすい,理解しやすい)
・GL API をそのまま使うため機能制限が無い
・Android 2.0/API Level 5 以上対応なので多くのデバイスで実行できる
欠点
・CPU アーキテクチャに依存する。ビルド時に実行環境が固定される
・低レベルな GL API をそのまま使うためコード量が増える
・リソースアクセスやイベント時に Java と通信が必要
● NativeActivity + OpenGL ES 2.0 (NDK)
Java を使わずに開発できます。
描画は EGL + OpenGL ES 2.0 のみで、全て自力で描画する必要があります。
Android の便利な Class ライブラリを使うことができません。
ほぼゲーム用。
利点
・C/C++ / OpenGL ES そのままなのでソース互換性が高い(移植しやすい,理解しやすい)
・C/C++ だけで完結し Java コードを必要としないためボトルネックがない。
・GL API をそのまま使うため機能制限が無い
欠点
・Android 2.3/API Level 9 以上のみ
・CPU アーキテクチャに依存する。ビルド時に実行環境が固定される
・低レベルな GL API をそのまま使うためコード量が増える
・使える API が限られており、Android の豊富なライブラリが使えない
続きます。次は RenderScript Compute の速度 「Android 3.x RenderScript (7) RenderScript Compute の速度」
関連エントリ
・Android 3.x RenderScript (5) 任意の 3D モデルを描画する
・Android 3.x RenderScript (4) script で頂点を書き換える
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
GPU を使って高速に描画可能な組み合わせは下記の通り。
View + onDraw (HW) RSSurfaceView + RenderScript GLSurfaceView + OpenGL ES 2.0 (Java) GLSurfaceView + OpenGL ES 2.0 (NDK) NativeActivity + OpenGL ES 2.0 (NDK)
下に行くほど低レベルな API になります。
実際に使ってみた限り、描画速度もほぼこの順番になるようです。
● RenderScript
マテリアルのセットアップやジオメトリ演算など、描画ループ自体を
Native 実行可能な C言語で記述することが可能です。
リソースの生成や初期化は Java で行い、プロジェクトも開発環境も
Java と非常に親和性が高いのが特徴です。
Compute 用の RenderScript を簡単に呼び出せます。
大量の並列演算を伴う場合も対応できます。
描画 API は OpenGL ES 2.0 の上に構築された Class ライブラリとなります。
理解してしまえば OpenGL ES 2.0 を直接扱うよりも簡単で使いやすいですが、
直接 GL 命令を使えないのでできることに限界があります。
特にぎりぎりまでハード性能を引き出すような用途には向きません。
また描画に GLSL を使う場合は OpenGL ES 2.0 の知識も必要となります。
利点
・C言語 + Native による高速実行。
・CPU アーキテクチャ非依存でどの環境でも走る。
・Java と親和性が高く Java のプロジェクトに組み込みやすい。
・描画 API が Class 化されており GL 直接より簡単。リソースも扱いやすい。
・rs_cl と併用できる。
欠点
・Android 3.0/API Level 11 以上のみ
・GL 周りで高速化に限界がある。
・描画リソースを一旦 script に渡してから描画する必要あり。
● GLSurfaceView + OpenGL ES 2.0 (Java)
GPU のハードウエアや OpenGL をよく知っていて、普段から GPU の性能を
めいっぱい使うような使い方をしている場合こちらの方が速いです。
CPU 負荷よりも GPU 負荷のほうが高いと考えられるためです。
逆にもし CPU がボトルネックとなっている場合は RenderScript の方が
向いているといえるでしょう。
アーキテクチャ非依存という点では同一で、特に OpenGL に慣れているなら
容易に扱えます。
利点
・CPU アーキテクチャ非依存でどの環境でも走る。
・Java だけで完結するため開発しやすい。
・GL API をそのまま使うため機能制限が無い
欠点
・Android 2.3/API Level 9 以上 (2.2 ではバグあり)
・低レベルな GL API をそのまま使うためコード量が増える
・Java によるボトルネックが考えられる
● GLSurfaceView + OpenGL ES 2.0 (NDK)
OpenGL ES 2.0 そのままで、かつ普通に C/C++ で開発できるため他の
プラットフォームで経験があるなら一番親和性が良いはずです。
ただし Java 環境への dll によるアドオンとなるため jni による通信が必要。
開発も両環境を行き来する必要が生じます。特にデバッグで。
また Native Code への事前コンパイルなので、ビルド時に実行環境が固定されます。
速度も一番引き出せる可能性がありますが、ハードに近いところまでそれなりの
知識が必要です。
利点
・C/C++ / OpenGL ES そのままなのでソース互換性が高い(移植しやすい,理解しやすい)
・GL API をそのまま使うため機能制限が無い
・Android 2.0/API Level 5 以上対応なので多くのデバイスで実行できる
欠点
・CPU アーキテクチャに依存する。ビルド時に実行環境が固定される
・低レベルな GL API をそのまま使うためコード量が増える
・リソースアクセスやイベント時に Java と通信が必要
● NativeActivity + OpenGL ES 2.0 (NDK)
Java を使わずに開発できます。
描画は EGL + OpenGL ES 2.0 のみで、全て自力で描画する必要があります。
Android の便利な Class ライブラリを使うことができません。
ほぼゲーム用。
利点
・C/C++ / OpenGL ES そのままなのでソース互換性が高い(移植しやすい,理解しやすい)
・C/C++ だけで完結し Java コードを必要としないためボトルネックがない。
・GL API をそのまま使うため機能制限が無い
欠点
・Android 2.3/API Level 9 以上のみ
・CPU アーキテクチャに依存する。ビルド時に実行環境が固定される
・低レベルな GL API をそのまま使うためコード量が増える
・使える API が限られており、Android の豊富なライブラリが使えない
続きます。次は RenderScript Compute の速度 「Android 3.x RenderScript (7) RenderScript Compute の速度」
関連エントリ
・Android 3.x RenderScript (5) 任意の 3D モデルを描画する
・Android 3.x RenderScript (4) script で頂点を書き換える
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
2011/11/05
Android 3.x RenderScript (5) 任意の 3D モデルを描画する
RenderScript には Android3D_ff というデータフォーマットがあります。
FileA3D を使うと Android3D_ff を読み込んで Mesh を生成できるのですが、
バイナリファイルでコンバート方法もわからなかったのであきらめました。
別の方法を使うことにします。
● IndexBuffer/VertexBuffer を自分で組み立てる
script (*.rs) 側で宣言しなくても IndexBuffer の Allocation を自分で作る
方法がわかりました。
配列から直接コピーできるため初期化も簡単です。
VertexBuffer も全く同じように自分で構築することができます。
ここまで出来ればデータファイル側に頂点構造を記述しておくことができそうです。
任意の頂点形式を持ったモデルデータを読み込めるようになります。
● Shader をもっと簡単に読み込む
以前 シェーダーを読み込むために LoadResource() という関数を使いましたが
無くても読み込めることがわかりました。
setShader() に直接リソース名を渡すことができました。
でも今回は、モデルデータを読み込むために再び LoadResource() を使います。
● Model データフォーマットを作る
簡単に読み込めそうだったので JSON を使ってモデルデータを記述しました。
こんな感じでテキストとして記述します。
● JSON を読み込む
モデルデータファイルを res/raw/model00.json に置いておきます。
JSONTokener を使ってモデルを読み込み、情報を取り出して Mesh を組み立てます。
最初に Element の情報を読み込んで組み立てます。
まずは "F32_3" などの文字列を Element.F32_3() に変換するためのテーブルを
作ります。同時に頂点のサイズ (byte数) も求められるようにしておきます。
JSON の "element" の情報を読み込んで Element を作ります。
Element と頂点サイズが求まったので、この情報を元に頂点バッファを作ります。
同じように Index も読み込んで作ります。
Index が無い場合にも対応。
あとは Mesh を作るだけです。
これでモデルデータを読み込んで Mesh を作ることが出来ました。
(今回は "primitive" は参照していません。)
● Depth Buffer の利用
RenderScriptGL 作成時に Depth の最小値を与えます。
ProgramStore で depth test を有効にします。
このオブジェクトを script に渡します。
script 側では Depth Buffer のクリアを追加し、受け取った
ProgramStore (DepthStencilState) を設定します。
● 3D 描画用の ConstantBuffer を作る
今回は Mesh の頂点は書き換えませんが、Matrix を script で書き換えるため
Constant Buffer の構造体を RenderScript で宣言します。
OpenGL ES 2.0 には 3x4 matrix が無いので残念ながら RenderScript にもありません。
PView は ProjectionMatrix * ViewMatrix です。
初期値は Java から与えてみます。
シェーダーでは UNI_PView と UNI_World で参照できます。
● RenderScript で Matrix 書き換え
Java や Shader だけでなく script からも ConstantType のデータに
アクセスできます。
あらかじめ mScript.bind_vconst( vconst ) でバッファを登録しておきます。
RenderScript には Matrix 演算のための関数が用意されているため
このような geometry 演算は容易に出来ます。
●ソースリスト
これで JSON に変換した任意のモデルデータを読み込んで RenderScript で
描画できるようになりました。
Material や Geometry、Animation 情報も欲しくなります。
・flatlib_ap02c.zip ソースコード
次はまとめなど 「Android 3.x RenderScript (6) Graphics まとめ」
関連エントリ
・Android 3.x RenderScript (4) script で頂点を書き換える
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
FileA3D を使うと Android3D_ff を読み込んで Mesh を生成できるのですが、
バイナリファイルでコンバート方法もわからなかったのであきらめました。
別の方法を使うことにします。
● IndexBuffer/VertexBuffer を自分で組み立てる
script (*.rs) 側で宣言しなくても IndexBuffer の Allocation を自分で作る
方法がわかりました。
// java short[] indexdata= { 0, 1, 2, 0, 2, 3 }; Element.Builder builder= new Element.Builder( mRS ); builder.add( Element.I16( mRS ), "index" ); Allocation ibuffer= Allocation.createSized( mRS, builder.create(), indexdata.length, Allocation.USAGE_GRAPHICS_VERTEX ); ibuffer.copyFromUnchecked( indexdata );
配列から直接コピーできるため初期化も簡単です。
VertexBuffer も全く同じように自分で構築することができます。
// java float[] vertexdata= { 0f, 0f, 0f, ~ }; Element.Builder builder= new Element.Builder( mRS ); builder.add( Element.F32_3( mRS ), "position" ); builder.add( Element.F32_3( mRS ), "normal" ); builder.add( Element.F32_2( mRS ), "texcoord0" ); Element vtype= builder.create(); Allocation vbuffer= Allocation.createSized( mRS, vtype, vertexdata.length/strideSize, Allocation.USAGE_GRAPHICS_VERTEX ); vbuffer.copyFromUnchecked( vertexdata );
ここまで出来ればデータファイル側に頂点構造を記述しておくことができそうです。
任意の頂点形式を持ったモデルデータを読み込めるようになります。
● Shader をもっと簡単に読み込む
以前 シェーダーを読み込むために LoadResource() という関数を使いましたが
無くても読み込めることがわかりました。
vsh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_vsh ) ); ↓ vsh_builder.setShader( mRes, R.raw.mesh2dc_vsh );
setShader() に直接リソース名を渡すことができました。
でも今回は、モデルデータを読み込むために再び LoadResource() を使います。
● Model データフォーマットを作る
簡単に読み込めそうだったので JSON を使ってモデルデータを記述しました。
[ { "primitive" : "TRIANGLE", "element" : [ "position" , "F32_3", "normal" , "F32_3", "texcoord0", "F32_2" ], "vertex" : [ -0.500000, -0.500000, 0.500000, 0.000000, 0.000000, 1.000000, 0.375000, 1.000000, 0.500000, -0.500000, 0.500000, 0.000000, 0.000000, 1.000000, 0.625000, 1.000000, ~ ], "index" : [ 0,1,2, 0,2,3, 4,5,6, 4,6,7, 8,9,10, 8,10,11, 12,13,14, 12,14,15, 16,17,18, 16,18,19, 20,21,22, 20,22,23 ] } ]
こんな感じでテキストとして記述します。
● JSON を読み込む
モデルデータファイルを res/raw/model00.json に置いておきます。
JSONTokener を使ってモデルを読み込み、情報を取り出して Mesh を組み立てます。
// java // Mesh を作るための準備 mMeshBuilder= new Mesh.AllocationBuilder( mRS ); // リソースから model00.json を読み込む String data= LoadResource( mContext, R.raw.model00 ); try { JSONArray toparray= (JSONArray)new JSONTokener( data ).nextValue(); int length= toparray.length(); for( int i= 0 ; i< length ; i++ ){ // 読み込んだデータはこれ JSONObject object= toparray.getJSONObject( i ); // object から element, vertex, index を取り出す LoadElement( object ); LoadVertex( object ); LoadIndex( object ); } }catch( JSONException e ){ }
最初に Element の情報を読み込んで組み立てます。
まずは "F32_3" などの文字列を Element.F32_3() に変換するためのテーブルを
作ります。同時に頂点のサイズ (byte数) も求められるようにしておきます。
// java // データ形式を Element に対応付けするためのテーブルを準備する class VertexType { public Element element; public int size; public VertexType( Element e, int s ) { element= e; size= s; } } ~ // テーブルを作る HashMapmap= new HashMap (); map.put( "F32", new VertexType( Element.F32(mRS), 4 ) ); map.put( "F32_2", new VertexType( Element.F32_2(mRS), 8 ) ); map.put( "F32_3", new VertexType( Element.F32_3(mRS), 12 ) ); map.put( "F32_4", new VertexType( Element.F32_4(mRS), 16 ) ); ~
JSON の "element" の情報を読み込んで Element を作ります。
// java Element.Builder bulider= new Element.Builder( mRS ); int strideSize= 0; // JSON から element の配列を取り出す JSONArray element= object.getJSONArray( "element" ); int esize= element.length(); for( int ei= 0 ; ei< esize ;){ String name= element.getString( ei++ ); // "position" 等 String typename= element.getString( ei++ ); // "F32_3" 等 // テーブルを使ってデータ形式文字列を Element に変換する if( map.containsKey( typename ) ){ VertexType vtype= map.get( typename ); bulider.add( vtype.element, name ); // 頂点のサイズも計算しておく strideSize+= vtype.size; } } // Element を作る mElement= bulider.create(); mStirdeSize= strideSize; // 頂点あたりの byte サイズ
Element と頂点サイズが求まったので、この情報を元に頂点バッファを作ります。
// java private void LoadVertex( JSONObject object ) throws JSONException { // JSON からのデータ読み込み JSONArray array= object.getJSONArray( "vertex" ); int size= array.length(); float[] vertexdata= new float[size]; for( int i= 0 ; i< size ; i++ ){ vertexdata[i]= (float)array.getDouble( i ); } // バッファを作成して転送する Allocation vbuffer= Allocation.createSized( mRS, mElement, vertexdata.length / (mStirdeSize/4), Allocation.USAGE_GRAPHICS_VERTEX ); vbuffer.copyFromUnchecked( vertexdata ); // Mesh の要素として追加 mMeshBuilder.addVertexAllocation( vbuffer ); }
同じように Index も読み込んで作ります。
Index が無い場合にも対応。
// java private void LoadIndex( JSONObject object ) throws JSONException { if( !object.has( "index" ) ){ // index が無い場合 mMeshBuilder.addIndexSetType( Mesh.Primitive.TRIANGLE ); return; } // JSON から読み込む JSONArray array= object.getJSONArray( "index" ); int size= array.length(); short[] indexdata= new short[size]; for( int i= 0 ; i< size ; i++ ){ indexdata[i]= (short)array.getInt( i ); } // バッファを作成する Element.Builder eb= new Element.Builder( mRS ); eb.add( Element.I16( mRS ), "index" ); Allocation ibuffer= Allocation.createSized( mRS, eb.create(), indexdata.length, Allocation.USAGE_GRAPHICS_VERTEX ); ibuffer.copyFromUnchecked( indexdata ); // Mesh に追加 mMeshBuilder.addIndexSetAllocation( ibuffer, Mesh.Primitive.TRIANGLE ); }
あとは Mesh を作るだけです。
Mesh mesh= mMeshBuilder.create();
これでモデルデータを読み込んで Mesh を作ることが出来ました。
(今回は "primitive" は参照していません。)
● Depth Buffer の利用
RenderScriptGL 作成時に Depth の最小値を与えます。
// java RenderScriptGL.SurfaceConfig sc= new RenderScriptGL.SurfaceConfig(); sc.setDepth( 16, 24 ); // ← 追加 mRS= createRenderScriptGL( sc );
ProgramStore で depth test を有効にします。
このオブジェクトを script に渡します。
// java mScript.set_dsbstate( ProgramStore.BLEND_NONE_DEPTH_TEST( mRS ) );
script 側では Depth Buffer のクリアを追加し、受け取った
ProgramStore (DepthStencilState) を設定します。
// RenderScript rs_program_store dsbstate; int root() { rsgClearColor( 0.0f, 0.2f, 0.5f, 0.0f ); rsgClearDepth( 1.0f ); // ← 追加 ~ rsgBindProgramStore( dsbstate ); // 設定 ~
● 3D 描画用の ConstantBuffer を作る
今回は Mesh の頂点は書き換えませんが、Matrix を script で書き換えるため
Constant Buffer の構造体を RenderScript で宣言します。
// RenderScript typedef struct ConstantType { rs_matrix4x4 PView; rs_matrix4x4 World; } ConstantType_t; ConstantType_t* ConstantType_n; // unuse
OpenGL ES 2.0 には 3x4 matrix が無いので残念ながら RenderScript にもありません。
PView は ProjectionMatrix * ViewMatrix です。
初期値は Java から与えてみます。
// java // Uniform vconst= new ScriptField_ConstantType( mRS, 1, Allocation.USAGE_SCRIPT|Allocation.USAGE_GRAPHICS_CONSTANTS ); Matrix4f projection= new Matrix4f(); projection.loadPerspective( 30.0f, mWidth/mHeight, 0.1f, 100.0f ); Matrix4f view= new Matrix4f(); view.loadTranslate( 0.0f, 0.0f, -10.0f ); Matrix4f pview= new Matrix4f(); pview.loadMultiply( projection, view ); // ProjectionMatrix * ViewMatrix Matrix4f world= new Matrix4f(); world.loadIdentity(); // 書き込み vconst.set_PView( 0, pview, true ); vconst.set_World( 0, world, true );
シェーダーでは UNI_PView と UNI_World で参照できます。
// GLSL : mesh3d_vsh.vsh varying vec3 onormal; varying vec2 otexcoord0; void main() { vec4 wpos= UNI_World * vec4( ATTRIB_position.xyz, 1.0 ); gl_Position= UNI_PView * wpos; onormal.xyz= mat3(UNI_World) * ATTRIB_normal.xyz; otexcoord0.xy= ATTRIB_texcoord0.xy; }
● RenderScript で Matrix 書き換え
Java や Shader だけでなく script からも ConstantType のデータに
アクセスできます。
あらかじめ mScript.bind_vconst( vconst ) でバッファを登録しておきます。
RenderScript には Matrix 演算のための関数が用意されているため
このような geometry 演算は容易に出来ます。
// RenderScript : rendermesh.rs #pragma version(1) #pragma rs java_package_name(jp.flatlib.ap02c) #include "rs_graphics.rsh" typedef struct ConstantType { rs_matrix4x4 PView; rs_matrix4x4 World; } ConstantType_t; ConstantType_t* ConstantType_n; // unuse rs_program_vertex vsh; rs_program_fragment psh; rs_allocation texture; rs_sampler sampler; rs_mesh mesh; rs_program_store dsbstate; rs_program_raster rstate; ConstantType_t* vconst; static float rot= 0.0f; int root() { rsgClearColor( 0.0f, 0.2f, 0.5f, 0.0f ); rsgClearDepth( 1.0f ); // 回転させてみる rs_matrix4x4 world; rsMatrixLoadRotate( &world, rot, 0.0f, 1.0f, 0.0f ); rot+= 1.0f; if( rot >= 360.0f ){ rot-= 360.0f; } // WorldMatrix を書き換える rsMatrixLoad( &vconst->World, &world ); rsgAllocationSyncAll( rsGetAllocation( vconst ) ); // 読み込んだ mesh の描画 rsgBindSampler( psh, 0, sampler ); rsgBindTexture( psh, 0, texture ); rsgBindProgramFragment( psh ); rsgBindProgramVertex( vsh ); rsgBindProgramStore( dsbstate ); rsgBindProgramRaster( rstate ); rsgDrawMesh( mesh ); return 33; }
●ソースリスト
これで JSON に変換した任意のモデルデータを読み込んで RenderScript で
描画できるようになりました。
Material や Geometry、Animation 情報も欲しくなります。
・flatlib_ap02c.zip ソースコード
次はまとめなど 「Android 3.x RenderScript (6) Graphics まとめ」
関連エントリ
・Android 3.x RenderScript (4) script で頂点を書き換える
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
Mesh.TriangleMeshBuilder を使うと簡単に Mesh を作れますがあまり自由度が
ありません。Mesh.AllocationBuilder を使うとフォーマットやメモリの管理も
自分でコントロールできます。
● TriangleMeshBuilder の例
頂点要素の組み合わせは TriangleMeshBuilder 作成時にフラグで指定します。
● AllocationBuilder の例
頂点フォーマットを RenderScript の構造体で宣言しておきます。
Reflected class を使って頂点バッファとインデックスバッファを作ります。
インデックス側も USAGE_GRAPHICS_VERTEX を指定する点に注意してください。
また Complex ではない、Basic Element ではなぜかうまくいきませんでした。
単一の short (I16) であっても頂点と同じように構造体から定義する必要があります。
作成した頂点バッファとインデックスバッファを組み合わせて Mesh を作ります。
頂点配列の Allocation 自体を作っているので、これを script に渡して
書き換えることが可能です。
実際に 2D のメッシュでやってみます。
まずは構造体の定義と受け取る変数の宣言。
必要なリソースを作っていきます。
ScriptField_~.Item と set() を使うとパラメータを一度に書き込み出来ます。
set() の最後の引数を true にすると毎回転送が発生します。
低速なので false にして、最後に copyAll() を使えば高速に転送できるはず
なのですがどうもうまくいきません。
SDK の Sample は PointSprite (Primitive.POINT) ばかりでしたが、
この手順だと Mesh.AllocationBuilder でも Primitive.TRIANGLE + Index
を使うことができます。
↓Texture を使うので、ProgramFragment.Builder で addTexture() しておきます。
対応するシェーダー側は↓こんな感じ。
addTexture() により UNI_Tex0 という Uniform が挿入されます。
↓Bitmap から直接 Allocation を作れるので Texture の生成は簡単です。
作ったデータを script に渡します。
script 側では受け取った mesh を描画します。
その前に直接頂点を書き換えてみます。
あまり大したことはやっていません。
PointSprite ではなく Triangle の頂点を直接動かしているのがポイント。
書き換えたバッファを同期させるために rsgAllocationSyncAll() が必要です。
Graphics 用 RenderScript の中でそのまま座標演算していますが、
これはあまり良い方法ではありません。
Compute 用の RenderScript に分離して rsForEach() を使ったほうが良いでしょう。
OpenCL のように、将来的に GPU 等のハードウエアがサポートされると
考えられるからです。
また頂点の直接更新は GPU リソースの Lock が発生するため同期待が発生し
速度が遅くなる可能性があります。
ダブルバッファ等の対策が必要ですが、RenderScript での効率良いアクセス
はまだ成功していません。
取り敢えずここまで出来れば script だけでひと通り作れそうです。
・flatlib_ap02b.zip ソースコード
次は 3D の Mesh 描画を行います。 「Android 3.x RenderScript (5) 任意の 3D モデルを描画する」
関連エントリ
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
ありません。Mesh.AllocationBuilder を使うとフォーマットやメモリの管理も
自分でコントロールできます。
● TriangleMeshBuilder の例
頂点要素の組み合わせは TriangleMeshBuilder 作成時にフラグで指定します。
// java Mesh.TriangleMeshBuilder tmb= new Mesh.TriangleMeshBuilder( mRS, 2, Mesh.TriangleMeshBuilder.NORMAL|Mesh.TriangleMeshBuilder.TEXTURE_0 ); tmb.setNormal( 0f, 0f, 1f ).setTexture( 0f, 0f ).addVertex( 0f, 0f ); tmb.setNormal( 0f, 0f, 1f ).setTexture( 1f, 0f ).addVertex( 80f, 0f ); tmb.setNormal( 0f, 0f, 1f ).setTexture( 0f, 1f ).addVertex( 0f, 80f ); tmb.setNormal( 0f, 0f, 1f ).setTexture( 1f, 1f ).addVertex( 80f, 80f ); tmb.addTriangle( 0, 2, 1 ); tmb.addTriangle( 1, 2, 3 ); Mesh mesh= tmb.create( true ); // 頂点フォーマット(Element)は mesh から受け取る Element vtype= mesh.getVertexAllocation( 0 ).getType().getElement();
● AllocationBuilder の例
頂点フォーマットを RenderScript の構造体で宣言しておきます。
// RenderScript // 頂点の構造定義 typedef struct __attribute((packed, aligned(4))) VertexType { float3 position; float3 normal; float2 texcoord; } VertexType_t; VertexType_t* VertexType_n; // unuse // Index の定義 typedef struct IndexType { short index; } IndexType_t; IndexType_t* IndexType_n; // unuse
Reflected class を使って頂点バッファとインデックスバッファを作ります。
インデックス側も USAGE_GRAPHICS_VERTEX を指定する点に注意してください。
また Complex ではない、Basic Element ではなぜかうまくいきませんでした。
単一の short (I16) であっても頂点と同じように構造体から定義する必要があります。
// java // VertexBuffer (GL_ARRAY_BUFFER) ScriptFiled_VertexType vbuffer= new ScriptFiled_VertexType( mRS, 4, AllocationBuilder.USAGE_SCRIPT|Allocation.USAGE_GRAPHICS_VERTEX ); // IndexBuffer (GL_ELEMENT_ARRAY_BUFFER) ScriptFiled_IndexType ibuffer= new ScriptFiled_IndexType( mRS, 6, Allocation.USAGE_GRAPHICS_VERTEX ); // Mesh Mesh.AllocationBuilder ab= new Mesh.AllocationBuilder( mRS ); ab.addVertexAllocation( vbuffer.getAllocation() ); ab.addIndexSetAllocation( ibuffer.getAllocation(), Mesh.Primitive.TRIANGLE ); Mesh mesh= ab.create(); // 頂点フォーマット(Element)は vbuffer から取り出せる Element vtype= vbuffer.getElement();
作成した頂点バッファとインデックスバッファを組み合わせて Mesh を作ります。
頂点配列の Allocation 自体を作っているので、これを script に渡して
書き換えることが可能です。
実際に 2D のメッシュでやってみます。
まずは構造体の定義と受け取る変数の宣言。
// RenderScript // VertexBuffer の構造 typedef struct __attribute((packed, aligned(4))) VertexType2D { float2 position; float2 texcoord; } VertexType2D_t; VertexType2D_t* VertexType2D_n; // unuse // IndexBuffer の構造 typedef struct IndexType { short index; } IndexType_t; IndexType_t* IndexType_n; // unuse // VertexShader に渡す Uniform の定義 typedef struct ConstantType2D { float4 Transform; } ConstantType2D_t; ConstantType2D_t* ConstantType2D_n; // unuse rs_program_vertex vsh; rs_program_fragment psh; rs_allocation texture; rs_sampler sampler; rs_mesh mesh; VertexType2D_t* vertex2d; // 頂点アクセス用 ~
必要なリソースを作っていきます。
// java // IndexBuffer ScriptFiled_IndexType ibuffer= new ScriptField_IndexType( mRS, MAX_QUAD*6, Allocation.USAGE_GRAPHICS_VERTEX ); // インデックスをバッファに書き込む ScriptField_IndexType.Item item= new ScriptField_IndexType.Item(); for( int i= 0 ; i< MAX_QUAD ; i++ ){ int ibase= i*6; int vbase= i*4; item.index= (short)(vbase + 0); ibuffer.set( item, ibase++, true ); item.index= (short)(vbase + 2); ibuffer.set( item, ibase++, true ); item.index= (short)(vbase + 1); ibuffer.set( item, ibase++, true ); item.index= (short)(vbase + 1); ibuffer.set( item, ibase++, true ); item.index= (short)(vbase + 2); ibuffer.set( item, ibase++, true ); item.index= (short)(vbase + 3); ibuffer.set( item, ibase++, true ); }
ScriptField_~.Item と set() を使うとパラメータを一度に書き込み出来ます。
set() の最後の引数を true にすると毎回転送が発生します。
低速なので false にして、最後に copyAll() を使えば高速に転送できるはず
なのですがどうもうまくいきません。
// java // VertexBuffer ScriptField_VertexType2D vbuffer= new ScriptField_VertexType2D( mRS, MAX_QUAD*4, Allocation.USAGE_SCRIPT|Allocation.USAGE_GRAPHICS_VERTEX ); // 頂点の初期値作成 float[] vertex_src= { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f }; float[] tex_src= { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f }; ScriptField_VertexType2D.Item item= new ScriptField_VertexType2D.Item(); for( int bi= 0 ; bi< MAX_QUAD ; bi++ ){ float px= (float)Math.random() * mWidth; float py= (float)Math.random() * mHeight; for( int vi= 0 ; vi< 4 ; vi++ ){ int base= vi * 2; item.position.x= px + vertex_src[base+0] * 80.0f; item.position.y= py + vertex_src[base+1] * 80.0f; item.texcoord.x= tex_src[base+0]; item.texcoord.y= tex_src[base+1]; vbuffer.set( item, bi*4+vi, true ); } }
SDK の Sample は PointSprite (Primitive.POINT) ばかりでしたが、
この手順だと Mesh.AllocationBuilder でも Primitive.TRIANGLE + Index
を使うことができます。
// java // Mesh Mesh.AllocationBuilder builder= new Mesh.AllocationBuilder( mRS ); builder.addVertexAllocation( vbuffer.getAllocation() ); builder.addIndexSetAllocation( ibuffer.getAllocation(), Mesh.Primitive.TRIANGLE ); Mesh mesh= builder.create(); // Uniform ScriptField_ConstantType2D vconst= new ScriptField_ConstantType2D( mRS, 1, Allocation.USAGE_GRAPHICS_CONSTANTS ); vconst.set_Transform( 0, new Float4( 2.0f/mWidth, -2.0f/mHeight, -1.0f, 1.0f ), true ); // VertexShader ProgramVertex.Builder vsh_builder= new ProgramVertex.Builder( mRS ); vsh_builder.setShader( LoadResource( mContext, R.raw.mesh2d_vsh ) ); vsh_builder.addConstant( vconst.getType() ); vsh_builder.addInput( vbuffer.getElement() ); ProgramVertex vsh= vsh_builder.create(); vsh.bindConstants( vconst.getAllocation(), 0 );
↓Texture を使うので、ProgramFragment.Builder で addTexture() しておきます。
// java // PixelShader ProgramFragment.Builder psh_builder= new ProgramFragment.Builder( mRS ); psh_builder.setShader( LoadResource( mContext, R.raw.mesh2d_psh ) ); psh_builder.addTexture( Program.TextureType.TEXTURE_2D ); ProgramFragment psh= psh_builder.create();
対応するシェーダー側は↓こんな感じ。
addTexture() により UNI_Tex0 という Uniform が挿入されます。
// GLSL: mesh2d_psh.psh varying vec2 otexcoord0; void main() { gl_FragColor= texture2D( UNI_Tex0, otexcoord0 ); }
↓Bitmap から直接 Allocation を作れるので Texture の生成は簡単です。
// java // Texture Bitmap bitmap= BitmapFactory.decodeResource( mRes, R.drawable.bgimage256b ); Allocation texture= Allocation.createFromBitmap( mRS, bitmap );
作ったデータを script に渡します。
// java // Global mScript.set_vsh( vsh ); mScript.set_psh( psh ); mScript.set_texture( texture ); mScript.set_sampler( Sampler.CLAMP_LINEAR(mRS) ); mScript.set_mesh( mesh ); mScript.bind_vertex2d( vbuffer ); // mesh 内の頂点データ mRS.bindRootScript( mScript );
script 側では受け取った mesh を描画します。
その前に直接頂点を書き換えてみます。
// RenderScript: rendermesh.rs #pragma version(1) #pragma rs java_package_name(jp.flatlib.ap02b) #include "rs_graphics.rsh" #include "rs_cl.rsh" typedef struct __attribute((packed, aligned(4))) VertexType2D { float2 position; float2 texcoord; } VertexType2D_t; VertexType2D_t* VertexType2D_n; // unuse typedef struct IndexType { short index; } IndexType_t; IndexType_t* IndexType_n; // unuse typedef struct ConstantType2D { float4 Transform; } ConstantType2D_t; ConstantType2D_t* ConstantType2D_n; // unuse rs_program_vertex vsh; rs_program_fragment psh; rs_allocation texture; rs_sampler sampler; rs_mesh mesh; VertexType2D_t* vertex; // mesh 内の頂点配列を直接受け取る int root() { rsgClearColor( 0.5f, 0.5f, 0.0f, 0.0f ); // 座標計算の前準備 float2 center= { rsgGetWidth() * 0.5f, rsgGetHeight() * 0.5f }; rs_matrix4x4 rotmatrix; rsMatrixLoadTranslate( &rotmatrix, center.x, center.y, 0.0f ); rsMatrixRotate( &rotmatrix, 1.5f, 0.0f, 0.0f, 1.0f ); rsMatrixTranslate( &rotmatrix, -center.x, -center.y, 0.0f ); float4 ipos= { 0.0f, 0.0f, 0.0f, 1.0f }; // 頂点を書き換えて動かしてみる uint32_t size= rsAllocationGetDimX( rsGetAllocation( vertex ) ) >>2; VertexType2D_t* vptr= vertex; for( uint32_t i= 0 ; i< size ; i++ ){ ipos.xy= vptr->position; float4 opos= rsMatrixMultiply( &rotmatrix, ipos ); float2 pos0= opos.xy; float2 pos1= opos.xy; vptr++->position= pos0; pos1.x+= 30.0f; vptr++->position= pos1; pos0.y+= 30.0f; vptr++->position= pos0; pos1.y+= 30.0f; vptr++->position= pos1; } // HW への転送 rsgAllocationSyncAll( rsGetAllocation( vertex ) ); rsgBindSampler( psh, 0, sampler ); rsgBindTexture( psh, 0, texture ); rsgBindProgramFragment( psh ); rsgBindProgramVertex( vsh ); rsgDrawMesh( mesh ); // 書き換えた Mesh の描画 return 16; }
あまり大したことはやっていません。
PointSprite ではなく Triangle の頂点を直接動かしているのがポイント。
書き換えたバッファを同期させるために rsgAllocationSyncAll() が必要です。
Graphics 用 RenderScript の中でそのまま座標演算していますが、
これはあまり良い方法ではありません。
Compute 用の RenderScript に分離して rsForEach() を使ったほうが良いでしょう。
OpenCL のように、将来的に GPU 等のハードウエアがサポートされると
考えられるからです。
また頂点の直接更新は GPU リソースの Lock が発生するため同期待が発生し
速度が遅くなる可能性があります。
ダブルバッファ等の対策が必要ですが、RenderScript での効率良いアクセス
はまだ成功していません。
取り敢えずここまで出来れば script だけでひと通り作れそうです。
・flatlib_ap02b.zip ソースコード
次は 3D の Mesh 描画を行います。 「Android 3.x RenderScript (5) 任意の 3D モデルを描画する」
関連エントリ
・Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
RSSurfaceView に script (*.rs) を登録すると毎フレーム root() 関数が
呼び出され、描画はその中で行います。
描画に使う各種リソースは Java で生成し script に渡します。
script ではこれらのデータやパラメータを組み合わせた描画が可能です。
ジオメトリを計算してアニメーションを行ったり、データを動的に書き換えて
表示内容を更新することもできます。
コメント部分 '// class' の後ろは対応する Java class 名です。
script の Reflection class が作られるので、Java から script の変数に
容易にアクセスすることができます。
● Mesh
任意形状の描画には Mesh を使います。
Mesh は 頂点の Allocation と インデックス Allocation の集合です。
TriangleMeshBuilder を使うと Allocation を意識せずに簡単に Mesh を
作ることができます。
その代わり TriangleMeshBuilder では頂点形式が固定であまり自由度がありません。
上のサンプルでは頂点要素は「2D頂点座標(xy) + 頂点カラー」ですが頂点カラーが
float4 (F32_4) で格納されてしまいます。
頂点形式の自由度が欲しい場合、また頂点データを script で書き換える場合は
Mesh.AllocationBuilder を使います。(後述)
● シェーダーファイル GLSL
シェーダーは OpenGL ES の GLSL ES でテキストファイルです。
そのまま res/raw 以下のフォルダに入れておきます。
リソース名には拡張子がつかないので、拡張子が無くても区別できるファイル名に
しておきます。
入力頂点形式 (attribute) や Uniform の宣言は不要です。
renderscript.ProgramVertex が内部の情報を元にヘッダを挿入してくれるからです。
命名ルールは下記のようになっています。Element 名に prefix が付きます。
varying (In/Out) は自分で宣言します。
Java, RenderScript(C言語), GLSL と複数の言語を併用することになるので
慣れないと混乱しがちです。
例として vector や matrix の表現をあげると下記の通り。
C言語ベースの RenderScript, hlsl, Cg, OpenCL 等は比較的似ています。
この中では GLSL が特殊です。いろいろと。
● Uniform の宣言、データ構造
シェーダーで用いる Constant Buffer (Uniform) は好きなように作ることができます。
Element を組み立てるよりも RenderScript の reflection を使った方が簡単です。
(1) RenderScript/GLSL が参照するリソースの構造宣言は RenderScript (*.rs) で行う。
(2) RenderScript で宣言された構造は Reflection class として Java で参照できる。ScriptFiled_ が付く。
(3) リソースの生成は Java で行う。
(4) 生成したリソースの書き込みは下記の通り。
・Java 生成 → script で参照
・Java 生成 → WH で参照
・Java 生成 → script/WH 両方で参照
・Java 生成 → scriptで書き換え → WH で参照
今回の例では 2D のトランスフォーム用に float4 を渡したいので、
RenderScript で下記の宣言を行います。
これで ScriptField_ConstantType2D が作られます。
ConstantType2D_n はダミーです。
script から参照していないと Reflection class ができないためです。
Java では ScriptField_ConstantType2D をインスタンス化するだけで
ハードウエアに設定可能なメモリ領域 (Allocation) が作られます。
データの書き込みも ScriptField_ConstantType2D を通じて行うことができます。
script 側でも参照したり書き換えたい場合は、Allocation.USAGE_SCRIPT を追加し
script 内で受け取るポインタを宣言しておきます。
次に vconst2d.getAllocation() をそのポインタに bind_ します。
● ProgramVertex / ProgramFragment
リソースとして配置したシェーダーを読み込むコード。
シェーダーを読み込んで ProgramVertex/ProgramFragment を作ります。
頂点シェーダーの入力頂点形式 (attribute) は Mesh と一致させておく必要が
あります。上の例のように Mesh から vtype を取り出すことができます。
なお TriangleMeshBuilder を使った場合の Element 名は固定です。
順番と名称は次の通り: 'position', 'color', 'texture', 'normal'
Texture を用いる場合は psh_builder で AddTexture() します。
VertexShader と同じように uniform ヘッダが挿入されます。
● ProgramStore / ProgramRaster / Sampler
よく使う Object が予め用意されているので、既存のものを渡すだけで十分です。
今回の例では特に設定せず、デフォルトのまま使用しています。
●ソースコード
テクスチャを使わない 2D mesh 描画のコードはこちら。
・flatlib_ap02a.zip
最終的にはこれだけです。
● Mesh.AllocationBuilder
Mesh.TriangleMeshBuilder では制限があるので、頂点形式を自由に宣言して
script で書き換えたい場合は Mesh.AllocationBuilder を使います。
Android SDK のサンプルはどれも PointSprite なので、IndexBuffer の使い方が
わからず理解に時間がかかりました。やっと出来るようになりました。
次はそのあたりを動かしてみます。「Android 3.x RenderScript (4) script で頂点を書き換える」
関連エントリ
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
呼び出され、描画はその中で行います。
描画に使う各種リソースは Java で生成し script に渡します。
script ではこれらのデータやパラメータを組み合わせた描画が可能です。
ジオメトリを計算してアニメーションを行ったり、データを動的に書き換えて
表示内容を更新することもできます。
// rendermesh.rs #pragma version(1) #pragma rs java_package_name(jp.flatlib.ap02) #include "rs_graphics.rsh" rs_mesh mesh; // class Mesh rs_program_vertex vsh; // class ProgramVertex rs_program_fragment psh; // class ProgramFragment rs_program_store dsbstate; // class ProgramStore rs_program_raster rstate; // class ProgramRaster rs_sampler sampler; // class Sampler rs_allocation texture; // class Allocation int root() // 毎フレーム呼ばれる { rsgClearColor( 0.5f, 0.5f, 0.0f, 0.0f ); rsgBindSampler( psh, 0, sampler ); rsgBindTexture( psh, 0, texture ); rsgBindProgramFragment( psh ); rsgBindProgramVertex( vsh ); rsgBindProgramStore( dsbstate ); rsgBindProgramRaster( rstate ); rsgDrawMesh( mesh ); // ポリゴンを描画する return 33; // interval 33msec }
コメント部分 '// class' の後ろは対応する Java class 名です。
Mesh : 頂点とインデックスの集合。描画するポリゴンの集まり。 ProgramVertex : 頂点シェーダー。座標の変換を記述するプログラム。GLSL ProgramFragment : ピクセルシェーダー。描画する色を生成するプログラム。GLSL ProgramStore : レンダーステート。D3Dの DepthStencilState + BlendState ProgramRaster : レンダーステート。D3Dの RasterizerState Sampler : テクスチャフィルタ等。D3Dの SamplerState Allocation : Buffer, Texture, Uniform 等、script/HW が扱うメモリ。
script の Reflection class が作られるので、Java から script の変数に
容易にアクセスすることができます。
// java ScriptC_rendermesh script= new ScriptC_rendermesh( mRS, mRes, R.raw.rendermesh ); ~ mScript.set_mesh( mesh ); mScript.set_vsh( vsh ); mScript.set_psh( psh ); mScript.set_dsbstate( ProgramStore.BLEND_NONE_DEPTH_NONE( mRS ) ); mScript.set_rstate( ProgramRaster.CULL_NONE( mRS ) ); mScript.set_sampler( Sampler.WRAP_LINEAR( mRS ) ); mScript.set_texture( texture ); mRS.bindRootScript( script );
● Mesh
任意形状の描画には Mesh を使います。
Mesh は 頂点の Allocation と インデックス Allocation の集合です。
TriangleMeshBuilder を使うと Allocation を意識せずに簡単に Mesh を
作ることができます。
// java Mesh.TriangleMeshBuilder builder= new Mesh.TriangleMeshBuilder( mRS, 2, Mesh.TriangleMeshBuilder.COLOR ); // 4頂点の定義 builder.setColor( 1f, 0f, 0f, 1f ).addVertex( 0.0f, 0.0f ); builder.setColor( 0f, 1f, 0f, 1f ).addVertex( 200.0f, 0.0f ); builder.setColor( 0f, 0f, 1f, 1f ).addVertex( 0.0f, 200.0f ); builder.setColor( 1f, 1f, 1f, 1f ).addVertex( 200.0f, 200.0f ); // index x 2 の定義 (2ポリゴン) builder.addTriangle( 0, 2, 1 ); builder.addTriangle( 1, 2, 3 ); Mesh mesh= builder.create( true );
その代わり TriangleMeshBuilder では頂点形式が固定であまり自由度がありません。
上のサンプルでは頂点要素は「2D頂点座標(xy) + 頂点カラー」ですが頂点カラーが
float4 (F32_4) で格納されてしまいます。
頂点形式の自由度が欲しい場合、また頂点データを script で書き換える場合は
Mesh.AllocationBuilder を使います。(後述)
● シェーダーファイル GLSL
シェーダーは OpenGL ES の GLSL ES でテキストファイルです。
そのまま res/raw 以下のフォルダに入れておきます。
リソース名には拡張子がつかないので、拡張子が無くても区別できるファイル名に
しておきます。
// mesh2dc_vsh.vsh varying vec4 ocolor; void main() { gl_Position.xy= ATTRIB_position.xy * UNI_Transform.xy + UNI_Transform.zw; gl_Position.zw= vec2( 0.0, 1.0 ); ocolor.xyzw= ATTRIB_color.xyzw; }
入力頂点形式 (attribute) や Uniform の宣言は不要です。
renderscript.ProgramVertex が内部の情報を元にヘッダを挿入してくれるからです。
命名ルールは下記のようになっています。Element 名に prefix が付きます。
ATTRIB_<頂点Element> attribute UNI_<Constant> uniform UNI_<Texture> texture
varying (In/Out) は自分で宣言します。
// mesh2dc_psh.psh precision mediump float; varying vec4 ocolor; void main() { gl_FragColor.xyzw= ocolor.xyzw; }
Java, RenderScript(C言語), GLSL と複数の言語を併用することになるので
慣れないと混乱しがちです。
例として vector や matrix の表現をあげると下記の通り。
RenderScript GLSL Java Element ----------------------------------------------------- float float float F32 float2 vec2 Float2 F32_2 float3 vec3 Float3 F32_3 float4 vec4 Float4 F32_4 rs_matrix3x3 mat3 Matrix3f MATRIX_3X3 rs_matrix4x4 mat4 Matrix4f MATRIX_4X4
C言語ベースの RenderScript, hlsl, Cg, OpenCL 等は比較的似ています。
この中では GLSL が特殊です。いろいろと。
● Uniform の宣言、データ構造
シェーダーで用いる Constant Buffer (Uniform) は好きなように作ることができます。
Element を組み立てるよりも RenderScript の reflection を使った方が簡単です。
(1) RenderScript/GLSL が参照するリソースの構造宣言は RenderScript (*.rs) で行う。
(2) RenderScript で宣言された構造は Reflection class として Java で参照できる。ScriptFiled_ が付く。
(3) リソースの生成は Java で行う。
(4) 生成したリソースの書き込みは下記の通り。
・Java 生成 → script で参照
・Java 生成 → WH で参照
・Java 生成 → script/WH 両方で参照
・Java 生成 → scriptで書き換え → WH で参照
今回の例では 2D のトランスフォーム用に float4 を渡したいので、
RenderScript で下記の宣言を行います。
これで ScriptField_ConstantType2D が作られます。
// renderscript typedef struct ConstantType2D { float4 Transform; } ConstantType2D_t; ConstantType2D_t* ConstantType2D_n; // unuse
ConstantType2D_n はダミーです。
script から参照していないと Reflection class ができないためです。
Java では ScriptField_ConstantType2D をインスタンス化するだけで
ハードウエアに設定可能なメモリ領域 (Allocation) が作られます。
データの書き込みも ScriptField_ConstantType2D を通じて行うことができます。
// java // メモリ領域作成 ScriptField_ConstantType2D vconst2d= new ScriptField_ConstantType2D( mRS, 1, Allocation.USAGE_GRAPHICS_CONSTANTS ); // 値の書き込み (HW に転送可能) vconst2d.set_Transform( 0, new Float4( 2.0f/mWidth, -2.0f/mHeight, -1.0f, 1.0f ), true );
script 側でも参照したり書き換えたい場合は、Allocation.USAGE_SCRIPT を追加し
script 内で受け取るポインタを宣言しておきます。
次に vconst2d.getAllocation() をそのポインタに bind_ します。
● ProgramVertex / ProgramFragment
リソースとして配置したシェーダーを読み込むコード。
// java public static String LoadResource( Context context, int resid ) { InputStream is= context.getResources().openRawResource( resid ); byte[] buf= null; try { int size= is.available(); buf= new byte[size]; is.read( buf ); is.close(); }catch( IOException e ){ } return new String( buf ); }
シェーダーを読み込んで ProgramVertex/ProgramFragment を作ります。
// java // mesh から頂点フォーマット (Element) を取り出す Element vtype= mesh.getVertexAllocation( 0 ).getType().getElement(); // VertexShader ProgramVertex.Builder vsh_builder= new ProgramVertex.Builder( mRS ); // シェーダーファイル読み込み vsh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_vsh ) ); vsh_builder.addConstant( vconst2d.getType() ); // Uniform 登録 vsh_builder.addInput( vtype ); // Attribute 登録 ProgramVertex vsh= vsh_builder.create(); vsh.bindConstants( vconst2d.getAllocation(), 0 ); // Uniform のメモリ領域を登録
頂点シェーダーの入力頂点形式 (attribute) は Mesh と一致させておく必要が
あります。上の例のように Mesh から vtype を取り出すことができます。
なお TriangleMeshBuilder を使った場合の Element 名は固定です。
順番と名称は次の通り: 'position', 'color', 'texture', 'normal'
// java // PixelShader ProgramFragment.Builder psh_builder= new ProgramFragment.Builder( mRS ); // シェーダーファイル読み込み psh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_psh ) ); ProgramFragment psh= psh_builder.create();
Texture を用いる場合は psh_builder で AddTexture() します。
VertexShader と同じように uniform ヘッダが挿入されます。
● ProgramStore / ProgramRaster / Sampler
よく使う Object が予め用意されているので、既存のものを渡すだけで十分です。
今回の例では特に設定せず、デフォルトのまま使用しています。
●ソースコード
テクスチャを使わない 2D mesh 描画のコードはこちら。
・flatlib_ap02a.zip
// rendermesh.rs #pragma version(1) #pragma rs java_package_name(jp.flatlib.ap02a) #include "rs_graphics.rsh" typedef struct ConstantType2D { float4 Transform; } ConstantType2D_t; ConstantType2D_t* ConstantType2D_n; // unuse rs_program_vertex vsh; rs_program_fragment psh; rs_mesh mesh; int root() { rsgClearColor( 0.5f, 0.5f, 0.0f, 0.0f ); rsgBindProgramFragment( psh ); rsgBindProgramVertex( vsh ); rsgDrawMesh( mesh ); return 33; }
// SimpleView.java package jp.flatlib.ap02a; import android.content.Context; import android.content.res.Resources; import android.renderscript.RSSurfaceView; import android.renderscript.RenderScriptGL; import android.renderscript.Float4; import android.renderscript.Allocation; import android.renderscript.ProgramVertex; import android.renderscript.ProgramFragment; import android.renderscript.Mesh; import android.view.SurfaceHolder; import java.io.InputStream; import java.io.IOException; public class SimpleView extends RSSurfaceView { private RenderScriptGL mRS; private Resources mRes; private Context mContext; private float mWidth; private float mHeight; private ScriptField_ConstantType2D a_vconst2d; public SimpleView( Context context ) { super( context ); createRS(); } public static String LoadResource( Context context, int resid ) { InputStream is= context.getResources().openRawResource( resid ); byte[] buf= null; try { int size= is.available(); buf= new byte[size]; is.read( buf ); is.close(); }catch( IOException e ){ } return new String( buf ); } private void createRS() { if( mRS != null ){ return; } RenderScriptGL.SurfaceConfig sc= new RenderScriptGL.SurfaceConfig(); mRS= createRenderScriptGL( sc ); mContext= getContext(); mRes= mContext.getResources(); } private void setupRS() { createRS(); ScriptC_rendermesh mScript= new ScriptC_rendermesh( mRS, mRes, R.raw.rendermesh ); // Mesh Mesh.TriangleMeshBuilder builder= new Mesh.TriangleMeshBuilder( mRS, 2, Mesh.TriangleMeshBuilder.COLOR ); builder.setColor( 1f, 0f, 0f, 1f ).addVertex( 10.0f, 10.0f ); builder.setColor( 0f, 1f, 0f, 1f ).addVertex( 280.0f, 10.0f ); builder.setColor( 0f, 0f, 1f, 1f ).addVertex( 10.0f, 280.0f ); builder.setColor( 1f, 1f, 1f, 1f ).addVertex( 280.0f, 280.0f ); builder.addTriangle( 0, 2, 1 ); builder.addTriangle( 1, 2, 3 ); Mesh mesh= builder.create( true ); // Uniform a_vconst2d= new ScriptField_ConstantType2D( mRS, 1, Allocation.USAGE_GRAPHICS_CONSTANTS ); a_vconst2d.set_Transform( 0, new Float4( 2.0f/mWidth, -2.0f/mHeight, -1.0f, 1.0f ), true ); // VertexShader ProgramVertex.Builder vsh_builder= new ProgramVertex.Builder( mRS ); vsh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_vsh ) ); vsh_builder.addConstant( a_vconst2d.getType() ); vsh_builder.addInput( mesh.getVertexAllocation( 0 ).getType().getElement() ); ProgramVertex vsh= vsh_builder.create(); vsh.bindConstants( a_vconst2d.getAllocation(), 0 ); // PixelShader ProgramFragment.Builder psh_builder= new ProgramFragment.Builder( mRS ); psh_builder.setShader( LoadResource( mContext, R.raw.mesh2dc_psh ) ); ProgramFragment psh= psh_builder.create(); // Global mScript.set_vsh( vsh ); mScript.set_psh( psh ); mScript.set_mesh( mesh ); mRS.bindRootScript( mScript ); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); createRS(); } @Override public void onDetachedFromWindow() { if( mRS != null ){ mRS= null; destroyRenderScriptGL(); } } @Override public void surfaceChanged( SurfaceHolder holder, int format, int w, int h ) { super.surfaceChanged( holder, format, w, h ); mWidth= w; mHeight= h; setupRS(); } }
最終的にはこれだけです。
● Mesh.AllocationBuilder
Mesh.TriangleMeshBuilder では制限があるので、頂点形式を自由に宣言して
script で書き換えたい場合は Mesh.AllocationBuilder を使います。
Android SDK のサンプルはどれも PointSprite なので、IndexBuffer の使い方が
わからず理解に時間がかかりました。やっと出来るようになりました。
次はそのあたりを動かしてみます。「Android 3.x RenderScript (4) script で頂点を書き換える」
関連エントリ
・Android 3.x RenderScript (2) 描画と Allocation
・Android 3.x RenderScript (1)
2011/11/02
Android 3.x RenderScript (2) 描画と Allocation
RenderScript には Compute 用と Graphics 用の 2種類あります。
前回のサンプルは Compute 用です。
下記のように root 関数の宣言が異なります。
・Compute
ForEach 命令によりデータの数だけ root() が呼ばれる。
Shader のような実行方法。
・Graphics
RSSurfaceView を使い bindRootScript() で登録すると毎フレーム
root() が呼ばれる。
1フレーム分の描画セットアップコードを記述できる。
RenderScript にはいくつか組み込まれた描画命令があります。
内蔵の固定シェーダーを用いて描画を行なっているようです。
簡単に描画できる反面、大量に描画する用途には向いていません。
デバッグやテスト時には便利そうです。使ってみます。
特に何もしない Activity。SimpleView を呼び出しているだけ。
View では RSSurfaceView を使い renderloop.rs を bind します。
これで renderloop.rs の root() が呼ばれるようになります。
Graphics 用 RenderScript では 1フレーム分の描画命令を記述します。
戻り値は描画の更新間隔です。msec
任意メッシュを使った描画、GLSL シェーダーの利用、RenderScript による
動的な頂点生成を行うにはもう少し複雑な初期化手順が必要となります。
その前に RenderScript のメモリについて整理します。
● Allocation
Allocation は RenderScript が扱うメモリ確保します。
Java のメモリを直接触ることは出来ず、転送には専用の命令が必要です。
Allocation は (2)/(3) の両方を管理します。
Allocation で確保したメモリは RenderScript でアクセスでき、
GPU Resource としてマップすることが可能です。
任意のタイミングで HW への転送や同期が行われます。
GPU ハードウエアリソースにマップする場合は確保時に USAGE フラグで
用途を指定します。
この指定方法は Direct3D API に非常によく似ています。名称も。
(1) から (2)/(3) への転送は容易ですが逆方向は簡単ではありません。
● Element と Type
Shader 等 GPU がアクセスするリソースのフォーマットを指定します。
Element は構造体の定義です。
データ型とメンバ変数名の組み合わせでできています。
例えば RGBA8888 256x26 の 2D テクスチャは、I8_4 の Basic Element が
256x256 の 2次元に並んだ構造として Type で表現できます。
● Element/Type Reflection
render.rs の中で、下記のように構造体として頂点フォーマットを定義すると
自動的に class ScriptFiled_VertexType が作られます。
(VertexType_n はダミーです。何らかの参照がないと class が作られないため。)
この場合の Element のイメージは↓こんな感じ。
ScriptFiled_VertexType の中では構造に従った Element や Type が生成される
ので、これを使って GPU 用のメモリを Allocation することができます。
ScriptFiled_VertexType は中で Allocation を作ってくれるので↓これで OK
続きます 「Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)」
関連エントリ
・Android 3.x RenderScript (1)
前回のサンプルは Compute 用です。
下記のように root 関数の宣言が異なります。
Compute : void root( const T* input, T* output ) Graphics: int root()
・Compute
ForEach 命令によりデータの数だけ root() が呼ばれる。
Shader のような実行方法。
・Graphics
RSSurfaceView を使い bindRootScript() で登録すると毎フレーム
root() が呼ばれる。
1フレーム分の描画セットアップコードを記述できる。
RenderScript にはいくつか組み込まれた描画命令があります。
内蔵の固定シェーダーを用いて描画を行なっているようです。
簡単に描画できる反面、大量に描画する用途には向いていません。
デバッグやテスト時には便利そうです。使ってみます。
特に何もしない Activity。SimpleView を呼び出しているだけ。
// SimpleActivity.java package jp.flatlib.ap02; import android.app.Activity; import android.os.Bundle; public class SimpleActivity extends Activity { private SimpleView mSimpleView= null; @Override public void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); mSimpleView= new SimpleView( this ); setContentView( mSimpleView ); } @Override protected void onPause() { super.onPause(); mSimpleView.pause(); } @Override protected void onResume() { super.onResume(); mSimpleView.resume(); } }
View では RSSurfaceView を使い renderloop.rs を bind します。
これで renderloop.rs の root() が呼ばれるようになります。
// SimpleView.java package jp.flatlib.ap02; import android.content.Context; import android.content.res.Resources; import android.renderscript.RSSurfaceView; import android.renderscript.RenderScriptGL; public class SimpleView extends RSSurfaceView { private RenderScriptGL mRS; private ScriptC_renderloop mScript; public SimpleView( Context context ) { super( context ); initRS(); } private void initRS() { if( mRS != null ){ return; } // RenderScriptGL を作る RenderScriptGL.SurfaceConfig sc= new RenderScriptGL.SurfaceConfig(); mRS= createRenderScriptGL( sc ); Resources res= getContext().getResources(); // renderloop.rs を登録する mScript= new ScriptC_renderloop( mRS, res, R.raw.renderloop ); mRS.bindRootScript( mScript ); } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); initRS(); } @Override public void onDetachedFromWindow() { if( mRS != null ){ mRS= null; destroyRenderScriptGL(); } } }
Graphics 用 RenderScript では 1フレーム分の描画命令を記述します。
戻り値は描画の更新間隔です。msec
// renderloop.rs #pragma version(1) #pragma rs java_package_name(jp.flatlib.ap02) #include "rs_graphics.rsh" int root() { // 背景クリア rsgClearColor( 1.0f, 0.5f, 0.0f, 0.0f ); // 塗りつぶし四角形描画 rsgDrawRect( 100.0f, 200.0f, 150.0f, 250.0f, 0.0f ); // 文字列描画 rsgFontColor( 1.0f, 1.0f, 1.0f, 1.0f ); rsgDrawText( "RenderScript test", 50.0f, 50.0f ); return 33; // 30fps }
任意メッシュを使った描画、GLSL シェーダーの利用、RenderScript による
動的な頂点生成を行うにはもう少し複雑な初期化手順が必要となります。
その前に RenderScript のメモリについて整理します。
● Allocation
Allocation は RenderScript が扱うメモリ確保します。
Java のメモリを直接触ることは出来ず、転送には専用の命令が必要です。
(1) Java Heap Java (2) Native Heap RenderScript (3) GPU Resource GPU
Allocation は (2)/(3) の両方を管理します。
Allocation で確保したメモリは RenderScript でアクセスでき、
GPU Resource としてマップすることが可能です。
任意のタイミングで HW への転送や同期が行われます。
GPU ハードウエアリソースにマップする場合は確保時に USAGE フラグで
用途を指定します。
この指定方法は Direct3D API に非常によく似ています。名称も。
Allocation.USAGE_GRAPHICS_CONSTANTS Uniform Allocation.USAGE_GRAPHICS_RENDER_TARGET (Android 4.0 API Lv14 以上で対応) Allocation.USAGE_GRAPHICS_TEXTURE Texture Allocation.USAGE_GRAPHICS_VERTEX Buffer (Vertex/Index) Allocation.USAGE_SCRIPT Script
(1) から (2)/(3) への転送は容易ですが逆方向は簡単ではありません。
● Element と Type
Shader 等 GPU がアクセスするリソースのフォーマットを指定します。
Element 頂点フォーマット、ピクセルフォーマット等 Type Element の配列。Uniform や Texture の構造を定義
Element は構造体の定義です。
データ型とメンバ変数名の組み合わせでできています。
例えば RGBA8888 256x26 の 2D テクスチャは、I8_4 の Basic Element が
256x256 の 2次元に並んだ構造として Type で表現できます。
● Element/Type Reflection
render.rs の中で、下記のように構造体として頂点フォーマットを定義すると
自動的に class ScriptFiled_VertexType が作られます。
// render.rs typedef struct VertexType { float3 position; float3 normal; float2 texcoord; } VertexType_t; VertexType_t* VertexType_n; // unuse
(VertexType_n はダミーです。何らかの参照がないと class が作られないため。)
この場合の Element のイメージは↓こんな感じ。
Element: F32_3 "position" F32_3 "normal" F32_2 "texcoord"
ScriptFiled_VertexType の中では構造に従った Element や Type が生成される
ので、これを使って GPU 用のメモリを Allocation することができます。
ScriptFiled_VertexType vtype= new ScriptFiled_VertexType( rs, 100 ); Element element= vtype.getElement(); // Element の参照 Allocation vbuffer= Allocation.createTyped( rs, vtype.getType(), Allocation.USAGE_GRAPHICS_VERTEX ); // Type を使った確保
ScriptFiled_VertexType は中で Allocation を作ってくれるので↓これで OK
ScriptFiled_VertexType vtype= new ScriptFiled_VertexType( rs, 100, Allocation.USAGE_GRAPHICS_VERTEX ); Allocation vbuffer= vtype.getAllocation();
続きます 「Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)」
関連エントリ
・Android 3.x RenderScript (1)
2011/11/01
Android 3.x RenderScript (1)
Android 3.0 Honeycomb (API Level 11) から RenderScript が
使えるようになりました。
ハードウエア機能を用いた高速な描画を行うための仕組みです。
これまでも Java + OpenGL ES とか NDK + OpenGL ES など、ハードウエア
機能を活用できる描画手段がありました。
RenderScript は全く新しい描画のためのフレームワークとなっています。
RenderScript の特徴は、NDK のように Cコンパイラによる高速動作が
可能なこと。そして NDK と違い CPU アーキテクチャに依存しないので、
生成したバイナリの互換性が保たれることです。
また開発環境も統合されており Eclipse 上でビルド可能です。
同時に Java からアクセスするための Reflected layer class も作られます。
RenderScript から扱う描画 API は OpenGL ES の上位に位置し、
リソースの扱いなど簡略化されています。
欠点としては以下のとおり。
・Android の中でしか使えない
NDK(C/C++) + OpenGL ES 2.0 の利点はソースコードの汎用性が非常に
高いことです。NDK は Windows, Linux, iOS 等とソースを共有可能ですが
RenderScript は今のところ Android 環境でしか使えません。
・描画 API
描画 API は OpenGL ES をカプセル化したもので、OpenGL の全機能が
そのまま使えるわけではないようです。
・ドキュメントが少ない
サンプルソースを見ないとわからない仕様がいろいろあります。
描画なしの簡単なコードを走らせてみます。
RenderScript のエントリ関数は root() です。
run() の中の rsForEach() は第一引数で与えられた script をデータの数だけ
呼び出します。与えた script の中の root() 関数が呼ばれるわけです。
ソースツリーに入れると simple.rs はバイトコードにコンパイルされ、
同時に class ScriptC_simple が作られます。
この class には global 変数へのアクセスや関数呼び出しのエントリも
含まれています。
上のコードの run() の呼び出しは script.invoke_run() です。
実際に呼び出してみます。
演算するためのバッファ (Allocation) を float * 100 個分作成し、
istream/ostream に bind します。
初期値を渡して RenderScript で実行した演算結果を受け取ってみます。
RenderScript の問題など
先日 OS 4.0 対応 SDK r14 がリリースされたばかりですが、バグ修正のため
r15 が出ています。
API Level 13 以前の RenderScript project が動かなかった問題が修正され
たようです。これで Android 3.0 向け Sample も動くようになりました。
・Download the Android SDK
*.rs をビルドすると class を生成しますが、このソース中に元ファイルの
パスを埋め込んでしまいます。
Windows の場合パスの区切りが '\' なので、u で始まるフォルダ名が
あると不正な unicode とみなされエラーとなります。
例えば C:\home\users の '\u' 等。
取り敢えず生成されたソースのエラー行を削除すればコンパイルできます。
script を更新したあと実行時にエラーが出る場合は Project を一旦
clean してリビルドした方が良いかもしれません。
続きます 「Android 3.x RenderScript (2) 描画と Allocation」
使えるようになりました。
ハードウエア機能を用いた高速な描画を行うための仕組みです。
これまでも Java + OpenGL ES とか NDK + OpenGL ES など、ハードウエア
機能を活用できる描画手段がありました。
RenderScript は全く新しい描画のためのフレームワークとなっています。
RenderScript の特徴は、NDK のように Cコンパイラによる高速動作が
可能なこと。そして NDK と違い CPU アーキテクチャに依存しないので、
生成したバイナリの互換性が保たれることです。
また開発環境も統合されており Eclipse 上でビルド可能です。
同時に Java からアクセスするための Reflected layer class も作られます。
RenderScript から扱う描画 API は OpenGL ES の上位に位置し、
リソースの扱いなど簡略化されています。
欠点としては以下のとおり。
・Android の中でしか使えない
NDK(C/C++) + OpenGL ES 2.0 の利点はソースコードの汎用性が非常に
高いことです。NDK は Windows, Linux, iOS 等とソースを共有可能ですが
RenderScript は今のところ Android 環境でしか使えません。
・描画 API
描画 API は OpenGL ES をカプセル化したもので、OpenGL の全機能が
そのまま使えるわけではないようです。
・ドキュメントが少ない
サンプルソースを見ないとわからない仕様がいろいろあります。
描画なしの簡単なコードを走らせてみます。
// simple.rs #pragma version(1) #pragma rs java_package_name(jp.flatlib.ap02) #include "rs_cl.rsh" void root( const float* vin, float* vout ) { *vout= *vin * 2.0f; } float* istream; float* ostream; rs_script script; void run() { rsForEach( script, rsGetAllocation( istream ), rsGetAllocation( ostream ), 0 ); }
RenderScript のエントリ関数は root() です。
run() の中の rsForEach() は第一引数で与えられた script をデータの数だけ
呼び出します。与えた script の中の root() 関数が呼ばれるわけです。
ソースツリーに入れると simple.rs はバイトコードにコンパイルされ、
同時に class ScriptC_simple が作られます。
// java ScriptC_simple script= new ScriptC_simple( rs, res, R.raw.simple );
この class には global 変数へのアクセスや関数呼び出しのエントリも
含まれています。
上のコードの run() の呼び出しは script.invoke_run() です。
class ScriptC_ファイル名 Script そのもの class ScriptFiled_構造体名 中で宣言した構造体 R.raw.ファイル名 バイトコードのリソース名 invoke_関数名() 関数呼び出し set_変数() global 変数への書き込み get_変数() 前回 set した値を参照 bind_ポインタ() データ領域の割り当て
実際に呼び出してみます。
演算するためのバッファ (Allocation) を float * 100 個分作成し、
istream/ostream に bind します。
// java public void rstest( Context context ) { RenderScript rs= RenderScript.create( context ); ScriptC_simple script= new ScriptC_simple( rs, context.getResources(), R.raw.simple ); // メモリ領域の作成 float x 100 Allocation a_in= Allocation.createSized( rs, Element.F32(rs), 100, Allocation.USAGE_SCRIPT ); Allocation a_out= Allocation.createSized( rs, Element.F32(rs), 100, Allocation.USAGE_SCRIPT ); // global 変数に書き込み script.bind_istream( a_in ); script.bind_ostream( a_out ); script.set_script( script ); // 実行 script.invoke_run(); }
初期値を渡して RenderScript で実行した演算結果を受け取ってみます。
// java public void rstest( Context context ) { RenderScript rs= RenderScript.create( context ); Resources res= context.getResources(); // script ScriptC_simple script= new ScriptC_simple( rs, res, R.raw.simple ); // メモリ領域の作成 float x 100 Allocation a_in= Allocation.createSized( rs, Element.F32(rs), 100, Allocation.USAGE_SCRIPT ); Allocation a_out= Allocation.createSized( rs, Element.F32(rs), 100, Allocation.USAGE_SCRIPT ); // 初期値を書き込む float[] srcbuf= new float[100]; for( int i= 0 ; i< 100 ; i++ ){ srcbuf[i]= i; } a_in.copyFrom( srcbuf ); // global 変数に書き込み script.bind_istream( a_in ); script.bind_ostream( a_out ); script.set_script( script ); // 実行 script.invoke_run(); // 結果を受け取る float[] destbuf= new float[100]; a_out.copyTo( destbuf ); for( int i= 0 ; i< 100 ; i++ ){ Log.i( "rs", "a_out=" + destbuf[i] ); } }
RenderScript の問題など
先日 OS 4.0 対応 SDK r14 がリリースされたばかりですが、バグ修正のため
r15 が出ています。
API Level 13 以前の RenderScript project が動かなかった問題が修正され
たようです。これで Android 3.0 向け Sample も動くようになりました。
・Download the Android SDK
*.rs をビルドすると class を生成しますが、このソース中に元ファイルの
パスを埋め込んでしまいます。
Windows の場合パスの区切りが '\' なので、u で始まるフォルダ名が
あると不正な unicode とみなされエラーとなります。
例えば C:\home\users の '\u' 等。
取り敢えず生成されたソースのエラー行を削除すればコンパイルできます。
script を更新したあと実行時にエラーが出る場合は Project を一旦
clean してリビルドした方が良いかもしれません。
続きます 「Android 3.x RenderScript (2) 描画と Allocation」