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

Android 3.x RenderScript (2) 描画と Allocation

RenderScript には Compute 用と Graphics 用の 2種類あります。
前回のサンプルは 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 256×26 の 2D テクスチャは、I8_4 の Basic Element が
256×256 の 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)

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 の全機能が
 そのまま使えるわけではないようです。

・ドキュメントが少ない

 サンプルソースを見ないとわからない仕様がいろいろあります。

描画なしの簡単なコードを走らせてみます。

// 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」