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)