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) 來對應


5 則留言:

  1. 很好的例子~ 非常感谢博主大神!!!!!
    我也从网上找了很多不实用的例子,你这个是现实国内最好的入门文章

    回覆刪除
    回覆
    1. 千万不要用sdk.buildtools 19.0.0,编译出来的东西错的~ 运行不了
      换回17.0.0就好了

      刪除
    2. 原來如此 Orz 這篇筆記已經接近一年半前的了,我也非常久沒用了。有機會再研究 sdk.buildtools 19 版吧,大部分架構的更新通常就會一直更新下去,舊版的容易被捨棄掉。

      或許 Renderscript 還沒蓬勃發展,所以留一下的文件不多。
      祝你研究順利 :)

      刪除
    3. 想不到昨天发的留言,今天就有人回复了!我是刚研究RenderScript,上两天android sdk升级到4.4,然后buildtools升级到19就不行了....蛋疼得要命....幸好在未升级前运行过sdk sample那个computer demo,知道是编译问题。不然我还一直以为是我的.rs有语法问题,自问C语言也学得不烂....

      刪除
  2. 作者已經移除這則留言。

    回覆刪除