月別アーカイブ: 2011年11月

Android 3.x RenderScript (7) RenderScript Compute の速度

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万頂点分演算します。

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)

Android 3.x RenderScript (6) Graphics まとめ

Android には様々な描画手段が用意されています。
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)

Android 3.x RenderScript (5) 任意の 3D モデルを描画する

RenderScript には Android3D_ff というデータフォーマットがあります。
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;
    }
}
~

// テーブルを作る
HashMap  map= 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)

Android 3.x RenderScript (4) script で頂点を書き換える

Mesh.TriangleMeshBuilder を使うと簡単に Mesh を作れますがあまり自由度が
ありません。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)

Android 3.x RenderScript (3) 独自シェーダーの割り当てとメッシュの描画(2D)

RSSurfaceView に script (*.rs) を登録すると毎フレーム root() 関数が
呼び出され、描画はその中で行います。
描画に使う各種リソースは 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)