2011/06/24

Titanium Meetup Tokyo #5 参加

6/21に開催されたTitanium Meetup Tokyo #5に参加してきました。

基本的に、各々がモクモクとコーディングをする(だけの)会なのですが、@masuidriveさんを捕まえてTitaniumの深いところまで質問できるのが、個人的には魅力的だと思いました。

ちょうどいま、Titaniumを使って初めて本格的なアプリを作ってて、幾つか質問したいことも溜まってたし、今回は開催地がオフィスの近くということもあって、いろいろ好都合でした。

ってことで、恥を忍んで、質問したことをここに晒そうと思います。

1. Toolbarのあるウィンドウから無いウィンドウに遷移すると黒背景が見える
app.jsでbackgroundColorを黒に設定してました・・・orz お恥ずかしい

2. 階層の深いウィンドウから元のウィンドウに一気に戻る
下図のように、どんどん階層の深いウィンドウに潜って行って、一気に一番はじめのウィンドウに戻りたい時ってありますよね?でも、どうやればいいんでしょう?


これは、増井さんも良いやり方がわからないとのことでした。でも、必要性はわかっていただけたと思うので、そのうちFrameworkでサポートされるかも知れませんね?こういったリクエストを直接なかの人に伝えられるというのも、こういう会合に参加するメリットだと自分は思いますよ。

で、自分は、一番はじめのウィンドウにカスタムイベントを飛ばして、そのハンドラーの中で直下の子ウィンドウをクローズすることで、ムリヤリ実現していたのですが、増井さんもいまはそれしかなかろうということでした。

まぁ、これでやってやれないことは無いのですが、やってみるとわかりますが、元に戻るのに結構時間がかかります。一瞬ハングったかと思うくらい・・・多分、内部ではいろいろ後始末に追われているんでしょうね(汗

3. アプリケーションがバックグラウンドに回った/戻った時のイベント
アプリケーションが動作中に電話がかかってきたなどで、バックグラウンドに回ることがあるじゃないですか?あるいは、ユーザーがホームボタンを押したとか。そういう時、あるいは逆にバックグラウンドからまた復帰した時のイベントは無いのか?ということを最後にお尋ねしました。

これに関しては、ドキュメント化されていないけど、Ti.App.addEventListener()で以下のイベント設定すれば取れるということでした。

pauseアプリがバックグラウンドに回った
resumeアプリがバックグラウンドから復帰

流石にこれは、ドキュメントに書いておかなアカンやろ(笑

Code strong,

2011/06/20

Flasherの為のRenderScript入門

■RenderScriptとは?
RenderScriptはAndroid OS3.0から導入された、ハイパフォーマンスな3Dグラフィックスのレンダリングや演算をC言語(C99の構文)で記述できるAPIです。

これまでも、ハイパフォーマンスなレンダリングを行う手段として、OpenGLをNDKで直接叩くという方法がありましたが、RenderScriptはLLVMで一旦中間コードにコンパイルされたものがアプリケーションに付随され、実行時にそれぞれの環境向けに最適化されたマシンコードに更にコンパイルして実行するという仕組みになっております。

その結果、使用しているCPUやGPUの種類によらず、同一のソースコードでそれぞれの環境に置いて効率の良いパフォーマンスを発揮しますが、当然のこととして、特定の環境向けにカリカリにチューニングされたOpenGLのパフォーマンスには及びません。

ただ、iPhoneと違って、様々なプラットフォーム上に展開しているAndroid OSのデバイスとしては、環境依存を吸収する手段として、なかなか面白い仕組みでは無いでしょうか?

■RenderScriptシステムのレイヤー構造
RenderScriptのシステムは、以下の3つのレイヤーから構成されます。

・Native RenderScript layer
このレイヤーはレンダリングや演算の為のRenderScriptそのものです。上に書いたように、C言語の構文で記述しますが、 RenderScriptは汎用的なCPUのみならず、GPU上でも実行する場合もあり得るので、標準的なCのライブラリーがすべて使えるわけではありません。

Native RenderScriptライブラリーで提供されている主な機能は下記。
  1.  膨大な数の演算関数
  2. ベクターや行列等の基本データ型に変換するルーチン
  3. ログ取得の為の関数
  4. グラフィックスのレンダリング関数
  5. メモリー領域確保の為の関数
  6. 2元、3元、4元ベクトル等の、RenderScriptシステムをサポートするデータ型や構造
・Reflected layer
Reflected layerは、AndroidVMからnative RenderScriptコードにアクセスする為の レイヤーで、Androidのビルドツールが自動生成します。その為、Reflected layerは、Android frameworkからRenderScript内の関数や変数にアクセスする為のエントリーポイントを提供します。

・Android framework layer
Android framework layerは、RenderScript APIや通常のAndroid framework APIから構成され、あなたのアプリケーションのActivityのライフサイクルやメモリーの管理などをします。また、このレイヤーで、Reflected layerを介して、ユーザーの操作をnative RenderScriptのコードに関連づけたりもします。

と、かなり前置きが長くなりましたが、詳しくはここのドキュメントを読んでみてください。Flasher的にはC/C++で記述し、同様にLLVMでActionScriptに変換するAlchemyを使うのと、ノリは似ているかも知れませんね。

今回、RenderScriptを使ってFlashでよく使われるParticleのデモを実装してみたいと思いますが、題材としてWonderflの重力マウス(さらに軽量化してみた)を使用したいと思います。

■重力マウスをRenderScriptに移植
重力マウスを移植するにあたって、以下の4つのファイルを記述します。
Gravity.javaGravityアプリのActivityを記述
GravityView.javaRenderScript用のSurfaceViewを記述
GravityRS.javaRenderScript用のエントリーポイントクラスを記述
gravity.rsRenderScriptのコードを記述

Gravity.java
package com.android.sample;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class Gravity extends Activity {
    private GravityView mView;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        mView = new GravityView(this); // RSSurfaceViewの作成
        setContentView(mView);
    }
}
特に何も難しいことはしていません。Activityの中で、アプリケーションで使用するViewを設定していますが、今回はRSSurfaceViewを継承したGravityView(後述)を作成してます。

GravityView.java
package com.android.sample;

import android.renderscript.RSSurfaceView;
import android.renderscript.RenderScript;
import android.renderscript.RenderScriptGL;

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.MotionEvent;

public class GravityView extends RSSurfaceView {

    public GravityView(Context context) {
        super(context);
    }

    private RenderScriptGL mRS; // グラフィックス出力の為にスクリプト等
                                // を付与するコンテクスト

    private GravityRS mRender;  // エントリーポイントクラス

    // Surfaceの構成が変わると呼ばれるハンドラー
    public void surfaceChanged(SurfaceHolder holder, int format,
                               int w, int h) {
        super.surfaceChanged(holder, format, w, h);
        if (mRS == null) {
            // グラフィックスのピクセルフォーマットを指定する為のクラス
            RenderScriptGL.SurfaceConfig sc
                = new RenderScriptGL.SurfaceConfig();
            mRS = createRenderScriptGL(sc);
            mRS.setSurface(holder, w, h); //osのsurfaceをバインド

            // エントリーポイントクラスを作成
            mRender = new GravityRS();
            mRender.init(mRS, getResources(), w, h); // 初期化
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        // 後始末
        if (mRS != null) {
            mRS = null;
            destroyRenderScriptGL();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        // エントリーポイントクラスにタッチした座標、指圧と
        // IDの情報を渡す
        // IDはマルチタッチがサポートされている場合に、
        // タッチした指を識別する為のID
        mRender.newTouchPosition(ev.getX(0), ev.getY(0),
            ev.getPressure(0), ev.getPointerId(0));        
        return true;
    }
}
RenderScriptでレンダリングした内容を表示する為のSurfaceViewクラスです。

surfaceChanged()メソッド内でRenderScriptのコンテキストを生成し、Surfaceをバインドしています。スクリプトのバインディングは、後述するエントリーポイントクラス(GravityRs)の方で行なっています。

また、タッチイベントが発生した際も、エントリーポイントクラスに座標等のタッチに関連する情報を渡します。このように、ユーザーの操作によって発生したイベントは基本的にエントリーポイントクラスに情報を渡して、エントリーポイントクラス側で処理をしてもらいます。

GravityRS.java
package com.android.sample;

import android.content.res.Resources;
import android.renderscript.*;

public class GravityRS {
    public static final int PART_COUNT = 50000; // 総パーティクル数

    public GravityRS() {
    }

    private Resources mRes;
    private RenderScriptGL mRS;      // レンダリングのコンテクスト
    private ScriptC_gravity mScript; // スクリプトファイルへの
                                     // エントリークラス

    // 初期化関数
    public void init(RenderScriptGL rs, Resources res,
                     int width, int height) {
        mRS = rs;
        mRes = res;

        // GLSLコードを用いないシンプルなフラグメントシェーダーを作成
        ProgramFragmentFixedFunction.Builder pfb
            = new ProgramFragmentFixedFunction.Builder(rs);

    // バーテックスプログラムから指定された色を使用する
        pfb.setVaryingColor(true);

        // フラグメントシェーダーをレンダリングコンテクストにバインド
        rs.bindProgramFragment(pfb.create());
        
        // スクリプト内のPoint構造体×パーティクル数の分だけ
        // メモリー領域を確保する
        ScriptField_Point points = new ScriptField_Point(mRS,
                                                         PART_COUNT);
        
        // RenderScriptで表示する幾何学データのコンテナを作成する
        // オブジェクト
        Mesh.AllocationBuilder smb = new Mesh.AllocationBuilder(mRS);

        // pointsを頂点データとしてオブジェクトに追加
        smb.addVertexAllocation(points.getAllocation());

        // データの種類を点座標に設定
        smb.addIndexSetType(Mesh.Primitive.POINT);

        // コンテナを作成
        Mesh sm = smb.create();

        // スクリプトファイルのエントリークラスを生成
        mScript = new ScriptC_gravity(mRS, mRes, R.raw.gravity);

        // スクリプト内のpartMeshにMeshをセット
        mScript.set_partMesh(sm);

        // pointsとスクリプト内のpointをバインド
        mScript.bind_point(points);

        // レンダリングコンテクストにスクリプトをバインド
        mRS.bindRootScript(mScript); 
    }

    // タッチ情報のアップデート
    public void newTouchPosition(float x, float y, float pressure,
                                 int id) {
        mScript.set_gTouchX(x); // スクリプト内のgTouchXにX座標をセット
        mScript.set_gTouchY(y); // スクリプト内のgTouchYにY座標をセット
    }
}
一見、複雑なことをやっているようですが、まずフラグメントシェーダーの設定し、それから座標データを格納するメモリー領域の確保をしてます。その後、ビルドツールで自動生成されるエントリーポイントのクラスをインスタンス化して、スクリプト内の変数やポインターと確保したメモリー領域の関連付けを行ってます。

そして最後に、レンダリングコンテクストにスクリプトをバインドして実行、という感じでしょうか?

gravity.rs
#pragma version(1)
#pragma rs java_package_name(com.android.sample)
#pragma stateFragment(parent)

#include "rs_graphics.rsh"

// staticな変数はエントリーポイントが生成されない
static int initialized = 0;

rs_mesh partMesh; // 描画するデータのコンテナ

// タッチ座標
float gTouchX = 0.f; // X座標
float gTouchY = 0.f; // Y座標

// パーティクルの構造体
typedef struct __attribute__((packed, aligned(4))) Point {
    float2 delta;
    float2 position;
    uchar4 color;
} Point_t;
Point_t *point;

// パーティクルの初期化
void initParticles()
{
    // データサイズを取得
    int size = rsAllocationGetDimX(rsGetAllocation(point));

    float width = rsgGetWidth();   // サーフェイスの幅
    float height = rsgGetHeight(); // サーフェイスの高さ

    uchar4 c = rsPackColorTo8888(0.9f, 0.0f, 0.9f); // 色

    Point_t *p = point;
    for (int i = 0; i < size; i++) {
        p->position.x = rsRand(width);  // 乱数でX座標をセット
        p->position.y = rsRand(height); // 乱数でY座標をセット
        p->delta.x = 0;
        p->delta.y = 0;
        p->color = c;
        p++;
    }
    initialized = 1; // 初期化完了
}

/*
 * root()は描画する毎に呼ばれる
 */
int root() {
    // 初回実行時に初期化
    if (!initialized) {
        initParticles();
    }

    float width = rsgGetWidth(); 
    float height = rsgGetHeight();

    // 指定色でレンダリングサーフェイスをクリア
    rsgClearColor(0.f, 0.f, 0.f, 1.f);
    
    int size = rsAllocationGetDimX(rsGetAllocation(point));

    Point_t *p = point;
    for (int i = 0; i < size; i++) {
        float diff_x = gTouchX - p->position.x;
        float diff_y = gTouchY - p->position.y;
        float acc = 50.f/(diff_x * diff_x + diff_y * diff_y);
        float acc_x = acc * diff_x;
        float acc_y = acc * diff_y;
        p->delta.x += acc_x;
        p->delta.y += acc_y;
        p->position.x += p->delta.x;
        p->position.y += p->delta.y;
        p->delta.x *= 0.96;
        p->delta.y *= 0.96;
        if (p->position.x > width) {
            p->position.x = 0;
        } else if (p->position.x < 0) {
            p->position.x = width;
        }
        if (p->position.y > height) {
            p->position.y = 0;
        } else if (p->position.y < 0) {
            p->position.y = height;
        }
        p++;
    }    
 
    rsgDrawMesh(partMesh); // 座標データ列の描画
    
    return 1; // おおよそ1ms後に再描画
}
最後にいよいよRenderScriptの本体です。

まず、Point構造体がパーティクルのデータ構造を構成しており、初回実行時だけ、initParticles()関数内で、パーティクルの座標や色のデータの初期化を行います。

そして、root()関数が描画毎に呼ばれる関数で、Flasher的にはenterFrameのイベントハンドラーのようなものと思えば、理解がしやすいでしょうか?この中のfor文で各パーティクルの座標データをアップデートしております。ここのコードは重力マウスのコードをほぼ、そのまま丸パ◯リ(笑)

ActionScriptでは、動作スピードを上げる為に、リンクリストを形成しておりましたが、ここではリンクリストは使用しないで、単純に構造体のポインターをfor文で要素の数だけ回すという処理になってます。

パーティクルの座標データの更新が終わったら、rsgDrawMesh()という関数で、コンテナ内のデータを元に描画を行います。

最後に、このファイル中に出てきたRenderScript関連の関数をまとめておきます。
rsGetAllocation()VM側で確保したメモリー領域を返す
rsAllocationGetDimX()メモリー領域のX方向の大きさを返す。今回、データは1次元の配列なので、要素数を返すことと同じか?
rsgGetWidth()サーフェースの幅を返す
rsgGetHeight()サーフェースの高さを返す
rsPackColorTo8888()R, G, B値から色コード(8888形式)を生成
rsRand()乱数値を生成
rsgDrawMesh()コンテナのデータを元に描画

■RenderScriptのパフォーマンス
RenderScriptを使うことで、どれくらいのパフォーマンスが出たのでしょうか?PC上では重力マウスは、楽に60fps出ていますが、手持ちのXOOMのブラウザ経由では、20fpsほどしか出ません。

今回、RenderScriptにはfpsを計測する機能を実装していないので、厳密にはどれくらいのフレームレートが出ているのかはわかりませんが、 フルスクリーン(解像度で4倍以上)で、尚且つパーティクルの数を50000に増やしても、目視で30fpsはゆうに出ているように感じました。

なので、少なく見積もっても5倍以上の性能差はありそうです。モバイル版のFlash Playerに早くStage3Dが搭載されることが待たれますね。

実際のアプリケーションの動作の動画を以下に貼りつけておきます。


■まとめ
難しいことは全て理解しなくとも(私も半分くらいしか理解できてない)、上記ファイルをテンプレートに、RenderScriptファイル中のfor文の中身(パーティクルの動作をつけている部分)を適当に書き換えれば、簡単に遊べると思いますので、腕に覚えのあるFlasherの方に是非お試しいただければと思います。

まぁ、遊ぶにはAndroid 3.xの実機が必要にはなりますがね・・・(^_^;

2011/06/12

書評:Titanium Mobileで開発するiPhone/Androidアプリ


6/10に世界初のTitanium Mobile本こと「Titanium Mobileで開発するiPhone/Androidアプリ」が発売されました。

レビューをほんの少しお手伝いさせていただいた関係で、ご献本いただきましたので、軽く中身をご紹介させていただきます。

第1章はTitanium Mobileを開発したAppcelerator社の紹介やツールの将来展望、そして気になる価格体系の情報など。そしてそして、最後に例のSection3.3問題に関して。昨年のいま頃は、もうこれからどうなるの?って感じで、個人的にTitaniumに対するモチベーションもかなり削がれた時期だったのですが、まさか1年経って開発環境としてこんなに注目を集め、入門書まで刊行されることになるとは、感慨もひとしおですね。

第2章は開発環境のインストールから、Hello worldまで。Titanium Mobileは開発環境もターゲットもマルチプラットフォームに対応しているので、すべての環境を網羅するのは大変だったと思うのですが、非常に丁寧に解説されてます。

第3〜5章は、具体的なアプリの開発を通じて、より詳細にTitanium Mobileの使い方を学べます。Twitterクライアントを題材にしているので、多くの方に非常に参考になる内容では無いでしょうか?また3-4章でスクリプトファイルの分割に関しての解説があるのですが、個人的にはTitanium Mobileでアプリを作る際にわりと肝になる部分ではないかと思いますよ。

第6章は、Titanium Mobile APIリファレンスとなってます。本家Appceleratorのサイトよりも詳細で、豊富なコードサンプルも掲載されており、本書の実に1/3ほどのボリュームをこのAPIリファレンスに費やしています。簡易リファレンスと書いてあるのは単なる著者のご謙遜でしょうね(笑)。このAPIリファレンスの為だけに本書を購入しても良いくらいです。

そして、充実のAppendixへと本書は続きますが、これより先は実際に手に取ってみて皆さんの方で是非ご確認してみてください。拙作のXib2Jsも紹介されてて、うれしい限りです(何でも、Appcelerator社の研修テキストブックにも紹介されてるとか?)。

ってことで、これから始める方にも、既にTitanium Mobileを開発に使っている方にもお薦めの一冊だと思います。

Code strong,