2014年1月4日 星期六

Android Renderscript 平行運算實作

Android在Honeycomb(3.x,API等級11)版的時候正式導入了Renderscript,它是針對高性能3D渲染和運算的新框架。Renderscript設計有三個主要目標:(出處)
可移植性(Portability):應用程序代碼需要能夠在所有設備使用的那些完全不同的Android 硬體上執行。
性能(Performance):上述的可移植性的限制範圍內,追求更多的性能。
可用性(Usability):簡化開發人員開發程式過程。
簡單來講,RS不需要像NDK一樣的複雜就可以寫出擁有比Java還要好的性能程式。

RenderScript是Android系統上的一種腳本(Script)語言,也就是說撰寫程式時除了Java部分外,還要寫一個附檔名為 rs 的腳本。在編譯上,我們所熟知Java部分會透過Java Compiler編譯成ByteCode,然後透過Dex Compiler編譯成Dalvik可執行的dex檔,而rs檔則透過 llvm-rs-cc Compiler一方面編譯成bc檔案與前面所述之dex檔合併於apk檔;另一方面則產生java程式碼的結構中產生相對應的 object。



上述內容中所敘述的 llvm-rs-cc編譯器是基於LLVM編譯器,也因為採用了LLVM(Low Level Virtual Machine)編譯器的關係等於增加了異質多核心的平行運算功能。

因此,Google 在發佈Android 4.2的時候同時在Renderscript Computation中說明了支援GPU 運算的功能。(見Renderscript Computation說明)

一般RenderScript (以下簡稱RS) 大多是用來圖形處理,本文主要實作一個簡單的a+b的範例來完成Renderscript 平行運算。

開發環境:
Windows 8.1 64bit
Android 4.x版以上手機/平板或模擬器
Eclipse 3.7.2 + ADT 22.3.0
注意的地方Android SDK Build-tools目前建議用18.0.1以下,因為18.0.1以上在編譯RS程式後會遇到很多設備無法執行的情況出現,待新版改善此問題後再使用新版的Build-tools。(見下圖)

建立專案:

程式內容:
由於輸出以Logcat為主,所以只要改Java及增加一個add.rs檔案,其他按照預設即可。

MainActivity.java內容:

package edu.nkut.hellorenderscript;

import android.app.Activity;
import android.os.Bundle;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.util.Log;

public class MainActivity extends Activity {
    private RenderScript mRS;
    private Allocation DataOut;
    private ScriptC_add mScript;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        createScript();
    }

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

        int RNGE = 10;
        ScriptField_MyDataIn mIn = new ScriptField_MyDataIn(mRS, RNGE);

        // 定義RS輸入資料
        for (int i = 0; i < RNGE; ++i) {
            mIn.set_a(i, i, false);
            mIn.set_b(i, i*2, false);
        }
        mIn.copyAll();

        DataOut = Allocation.createSized(mRS, Element.I32(mRS), RNGE);

        // 呼叫 renderscript
        mScript = new ScriptC_add(mRS, getResources(), R.raw.add);
        mScript.forEach_root(mIn.getAllocation(), DataOut);

        // 輸出結果
int[] Out = new int[RNGE];
        DataOut.copyTo(Out);

        for (int i = 0; i < RNGE; ++i) {
            Log.i("MyParallel", "(a,b,sum)=("+mIn.get_a(i)+"+"+mIn.get_b(i)+"="+Out[i]+")");
        }
    }
}

程式說明:
31~35行定義了RS輸入的資料
40行定義了要執行的RS檔案,其中 ScriptC_add以及 R.raw.add 兩處的 add要跟 rs檔名一樣。
41行定義了使用forEach_root執行RS的平行運算。

add.rs檔案內容:

#pragma version(1)
#pragma rs java_package_name(edu.nkut.hellorenderscript)

struct MyDataIn {
        int a;
        int b;
};

void root(const struct MyDataIn *m_in, int *m_out) {
 
     *m_out = m_in->a + m_in->b;

    rsDebug("RS a=", (int) m_in->a);
    rsDebug("RS b=", (int) m_in->b);
}

程式說明:
1行宣告了腳本使用的RenderScript版本。
2行宣告了腳本的Java package name。(注意這個地方要與Java檔案第一行的Package名稱一樣)
4~7行定義了RS輸入的資料
11行執行了a+b動作
13~14行rsDebug是RS輸出資訊到Logcat


對應到本文第一張圖內容,
ScriptC_add.java編譯後會出現在 gen目錄內。內容定義的類別可以讓程式呼叫腳本函式。
add.bc編譯後會出現在 bin/res/bc/raw目錄內。這就是LLVM編譯出來的檔案。
add.d編譯後會出現在 bin/rsDeps目錄內。列出add.bc檔案相依關係。

執行結果: (Logcat)

4 則留言:

  1. rs檔案要怎麼加入呢?
    我新增一個婦檔名為.rs的檔案
    不過卻顯示錯誤

    回覆刪除
  2. 意思是不管怎樣,這個專案都需要有c++的支持吧?

    回覆刪除