2012年3月12日 星期一

Android 開發教學筆記 - 研究 Renderscript 以 Gravity 為例


From: http://code.google.com/p/renderscript-examples/wiki/Gravity


這是一個重力特效的範例。看過 Renderscript HelloWorld 的架構後,很快就能了解 Gravity,他們倆是一樣的架構,只是畫圖的部份不一樣。


這邊有四個檔案 Gravity.java、GravityRS.java、GravityView.java 和 gravity.rs,以 GravityRS.java 和 gravity.rs 最為重要,因此挑這兩個檔案研究一下:


gravity.rs:


(gTouchX, gTouchY) 座標資訊


用來決定重力中心位置,這會從螢幕上的 TouchEvent 後,慢慢傳遞到這兩個變數,所以在畫面上任一點點一下,則改變重力中心位置


Point 資料結構(delta、座標和顏色)


用來紀錄每一個畫在螢幕上的點資訊,包括座標、顏色,而 delta 則是用來計算與重力中心距離相關的數值,在畫面上則是用來決定靠近重力中心的速率,也就是座標變化量


void initParticles()


用來初始化 Point 位置,首先先得知有多少 Point,接著得知 Width 跟 Height 後,把所有 Point 座標用亂數建立出來,並設定顏色,而靠近重力中心的座標變化量就先定為 0


int root()


計算各 Point 與重力中心點 (gTouchX, gTouchY) 的位置,並決定該 Point 移動到重力中心點的座標變化量(越接近移動越快)。最後,則是透過 rsgDrawMesh(partMesh); 畫出各個 Point,並且 return 1 代表 1ms 更新畫面一次,也代表座標會重算一次


GravityRS.java:


void init(RenderScriptGL rs, Resources res, int width, int height) 裡頭包括決定 Point 個數、用 Mesh 結構把 Point 集中起來(方便畫圖),剩下的則是相關的初始化動作。


Android 開發教學筆記 - 研究 Renderscript 以 HelloWorld 為例

ImportSample


此例是一個很簡單的 Renderscript 的 HelloWorld,程式跑起來後,螢幕上只會顯示一個 "Hello World!" 字串,並且隨著你點擊螢幕的位置,該字串就會在那個位置上顯示。


使用方式:


[File]->[Project]->[Android]->[Android Sample Project]->[Android 4.0]->[RenderScript > HelloWorld]


AddSampleProject


接著簡述架構,共有 4 個檔案需留意:


HelloWorld.java


HelloWorld.java 就像一般 Android app 中的角色,為一個 Activity,裡頭新增一個 HelloWorldView 後,再把它加到自身的 View 身上。除此之外,則是對 Android app 的程式流程資訊(onResume、onPause)傳給 HelloWorldView 處理。


HelloWorldView.java


HelloWorldView.java 是一個繼承於 RSSurfaceView 的物件,用來負責初始化 Renderscript 所需的環境資源,以及接收 View 上頭的事件資訊進行處理,例如 onTouchEvent 等,而此例則是接收 TouchEvent 的位置後,把資訊傳遞給 Renderscript 以決定畫字串的位置。


初始化 Renderscript 運行的環境:


RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig();
RenderScriptGL mRS = createRenderScriptGL(sc);
// Create an instance of the script that does the rendering
HelloWorldRS mRender = new HelloWorldRS();
HelloWorldRS mRender.init(mRS, getResources());


更新字串位置:


if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        mRender.onActionDown((int)ev.getX(), (int)ev.getY());
        return true;
}


HelloWorldRS.java


void init(RenderScriptGL rs, Resources res)


負責將 HelloWorldView 初始化 Renderscript 所需的執行環境資源接收起來,用全域變數紀錄。接著執行自身函數 void initRS()。


void initRS()


初始化 mScript 變數,將 Java 與 Renderscript 接起來。其中 mScript = ScriptC_helloworld 代表要使用 helloworld.rs 程式。初始化完後,再 bind 到 Renderscript 執行環境。


void onActionDown(int x, int y)


把座標資訊傳遞給 mScript(helloworld.rs) 使用。


HelloWorld.rs


HelloWorld.rs 的工作主要就只是依據指定的 (gTouchX, gTouchY) 座標把 "Hello World!" 字串印出。負責印出的函式是 int root(void),回傳的整數代表更新螢幕的時間(ms),如回傳 20ms 代表每 20ms 作一次 root 內容函式。


From: http://developer.android.com/guide/topics/renderscript/graphics.html
A root() function. This is the main worker function for your Renderscript and calls Renderscript graphics functions to render scenes. This function is called every time a frame refresh occurs, which is specified as its return value. A 0 (zero) specified for the return value says to only render the frame when a property of the scene that you are rendering changes. A non-zero positive integer specifies the refresh rate of the frame in milliseconds.


colorCode


因此,想要體會 20ms 執行 root 內容函式的話,那更新一下 helloworld.rs 片段程式碼,將 rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f); 更新為 rsgFontColor(rsRand(1.0f), rsRand(1.0f), rsRand(1.0f), 1.0f); 如此一來,就可以看到字串顏色不斷得變化囉。(該程式碼只是字型顏色改成亂數產生而已)


最後一提,不曉得這是不是一個很傷硬體的範例?程式執行沒多久後,我那塊 PandaBoard 就會掛掉 :P 或許 root 裡頭改成回傳 200(ms) 也適合,因為 0.2 秒的反應速度對人來說還 ok 囉。


Android 開發教學筆記 - 學習 Renderscript 的足跡


From: Graphics Renderscript overview


接觸 Renderscript 一陣子了,盡管官方文件顯少,但最佳的學習方式就是大量的程式碼閱讀,久了就會熟習為啥有 A 還是 B 的架構。據說 Renderscript 一開始是要設計給圖形運算的,以高效能、跨平台為出發點,不知不覺就設計出可以平行運算的架構,所以學習 Renderscript 別忘了繪圖運算跟平行處理這兩個方向。


關於 Renderscript 相關程式碼:



我本身一開始是先接觸 HelloCompute 的,看懂之後又對 Carousel 的效果著迷,在沒有圖學背景,悄悄地啃食完了,深深覺得數學真的很奧妙。之後閒暇之餘,又看了 Fountain 、HelloWorld 、 Gravity 和 Balls 等範例,漸漸地就能夠把 Renderscript 雛形給建立好。


回憶起來,我覺得比較好的學習曲線:



  • 平行運算


    • HelloCompute> Balls


  • 繪圖運算


    • HelloWorld > Gravity > Fountain > Carousel 



從 HelloWorld 的架構下,就能夠搞懂怎樣初始化一些繪圖的架構,例如 int root() 回傳的數值可代表畫面更新的頻率等,而 HelloCompute 則是專門做平行運算的架構。之所以把 Balls 擺在平行運算是因為裡頭有一個在 Renderscript 中呼叫 rsForEach 啦,而 HelloCompute 是在 Java 端呼叫 mScript.forEach_root ,更簡單的範例請參考 Renderscript Part 2 吧,算是把 HelloCompute 從 mScript.forEach_root 快速改寫成 rsForEach 的方式。


除此之外,最近也有一篇 R. Jason Sams 寫的一篇 Levels in Renderscript ,也是不錯的效能測試的文章。


2012年3月11日 星期日

走讀台灣 系列書籍


圖片來源:Pubu電子書城 - 走讀台灣 - 嘉義市


記得過年時剛好在 iPad 上嘗試使用 Pubu 電子書閱讀器,覺得這種用「圖片」方式瀏覽傳統書籍,在網路夠快的情境下,感覺其實沒有那麼糟。


最近想說想多了解一下自己居住的城市,所以就找了「走讀台灣」系列書籍,有豐富的圖片跟文字史料,我想,跟旅遊書籍差不多了吧!只是在網路上找了很久,才發現「走讀台灣」只在一些地方可以購買:



根據一位國中老師的分享,這系列似乎是課後補充教材?難怪很難購買,應該沒多人會想去買學生上課用的書吧 XD


最後,我是在 Pubu 電子書城購買 120 元的版本(DRM-free),這個版本就是國外行之有年的 Social-DRM 版本!簡單的說就是在電子書上的每一頁嵌入購買者的相關資訊,如 Email 或帳號資訊,所以,購買後若要下載還要等一會兒,我猜是系統正在動態產生這些圖檔(嵌有一小段個人資訊)。


說真的,最後產出的電子書只是「圖檔」湊起來的,說真的還是有點小落寞啦 :P 畢竟直接買實體書就會送電子書了,這時候這種圖檔式電子書就顯得遜色不少。而令我眼睛一亮的 EPUB 格式卻都只是圖檔而已,這下可好,螢幕小的閱讀裝置肯定很痛苦,完完全全地為 EPUB 格式產生出毫無用途的 EPUB 電子書而已。


2012年3月8日 星期四

Android 開發教學筆記 - Renderscript Parallel Computing 之 Define a structure for your data

雖然網路上有許多 Renderscript 處理繪圖運算的範例,但它不只可以作繪圖加速還可以作平行運算,在 PandaBoard ES Rev B 板子上,發現雙核心 CPU 的架構下,似乎是沒問題的。然而,網路上場看得範例都是處理圖檔,讀檔的資料結構很簡單,就是最基本的 Bitmap 而已,於是花了不少功夫推敲,才找到如何定義自己的資料結構進行平行運算!


作法很簡單,先把你想要的資料結構定義在 Renderscript (myscript.rs) 檔案內,如 C 語言的 structure:


#pragma version(1)
#pragma rs java_package_name(com.example.android.rs.MyParallelCompute)


#include "rs_graphics.rsh"


struct MyDataIn {
        int a;
        int b;
};


//struct MyDataOut {
//         int sum;
//};
//void root(const struct MyDataIn *in, struct MyDataOut* out) {


void root(const struct MyDataIn *in, int* out) {
        //out->sum = (int)a+b;
        *out = in->a + in->b;
        rsDebug("myscript a=", (int) in->a);
        rsDebug("myscript b=", (int) in->b);
}


如此一來,編譯後自動產生 ScriptField_MyDataIn.java (gen/com.example.android.rs.MyParallelCompute裡),之後在 Java 端撰寫程式時,使用這個 class 來包裝你的資料:


private void createScript() {
        mRS = RenderScript.create(this);

        int myDataCount = 5;


        ScriptField_MyDataIn in = new ScriptField_MyDataIn(mRS,myDataCount);
        //ScriptField_MyDataOut out = new ScriptField_MyDataOut(mRS,myDataCount,myDataCount);


        // initial data
        for( int i=0 ; i<myDataCount ; ++i) {
                in.set_a(i, i, false);
                in.set_b(i, i*2, false);
        }
        in.copyAll();


        //Allocation dataOut = Allocation.createTyped(mRS, out.getType());
        Allocation dataOut = Allocation.createSized(mRS, Element.I32(mRS), myDataCount);


        // call renderscript
        mScript = new ScriptC_myscript(mRS, getResources(), R.raw.myscript);
        mScript.forEach_root(in.getAllocation(),dataOut);


        //byte []x = new byte[ ScriptField_MyDataOut.Item.sizeof * myDataCount ];
        //dataOut.copyTo(x);


        int[] x = new int[myDataCount];
        dataOut.copyTo(x);


        // result data
        for( int i=0 ; i<myDataCount ; ++i) {
                //Log.e("MyParalle","(a,b,sum)=("+in.get_a(i)+"+"+in.get_b(i)+"="+out.get_sum(i)+")");
                Log.e("MyParalle","(a,b,sum)=("+in.get_a(i)+"+"+in.get_b(i)+"="+x[i]+")");
        }
}


此例很簡單,使用平行運算把輸入的資料(struct MyDataIn)進行相加後輸出(int)。紅色是把資料轉成 RS 的輸入,綠色則是用來儲存運算結果,而藍色則是把綠色取得的資料,轉成在 Java 常用的結構。


幾個小筆記:



  • 用 forEach_root 進行平行運算,輸入的資料個數要跟輸出個數一樣才能使用,在此資料個數就是 myDataCount,而從 Java 傳遞給 forEach_root 的參數輸入跟輸出必須是 Allocation 型態

  • 想要自訂結構,請在 *.rs 先定義(如 struct MyDataIn),接著編譯後,則可以再 Java 中使用(如 ScriptField_MyDataIn)

  • 在 Java 中使用 ScriptField_* 進行資料初始化後,想要把它變成有效的 Allocation 時,記得先做 copyAll(),不然則是每次設值時,最後一個 copyNow 參數設定成 true,詳情起看 ScriptField_*.java 查閱實作

  • 運算後的資料也是 Allocation,必須把它轉成自己的資料結構,由於 Allocation 目前只支援輸出(copyTo)成 int [], short [], float [], byte[] 和 Bitmap 等,所以建議先以這些結構來設計,不然輸出還要花心力去做轉換,希望之後的架構可以支援直接輸出成自訂結構

  • Allocation 初始化有不少 Element.XXX 函式可以使用,例如 rs 用 void root( uchar4 *in ) 的話,那就用 Element.U8_4(mRS) 來對應