如何寫RenderScript應用

簡介

RenderScript本質是封裝了Native OpenGL2.0接口,使界面渲染不需要通過Android虛擬機,從而達到近似Native OpenGL的渲染效率。除此之外,RenderScript名爲“Script”,是一種腳本語言。這表明了它比Native OpenGL擁有更好的可移植性,能在不同處理器和GPU上運行。爲了做到這一點,其實是有一個叫做Low LevelVirtual Machine(LLVM)的東西。它負責在運行時將RenderScript腳本實時編譯爲本地執行的二進制代碼,從而保證了腳本的可移植性。

由於RenderScript在Android 3.0才公開文檔,而我們現在用的是Android 2.3.4。所以有許多API是不同的,文檔極度缺乏。本文主要介紹了RenderScript應用的一般編寫架構及簡單原理。由於時間倉促、水平有限,講得非常淺顯。後面隨着學習的深入,應該還有更深入的總結。

Android應用架構

       RenderScript渲染的圖像要在應用中呈現出來,就必須有一套特有的應用架構來打通通道和做顯示容器。這個架構在應用層上主要涉及三個類:Activity、RSSurfaceView和RenderScriptGL。

       如果你寫過AndroidOpenGL程序,你會發現這個結構非常類似。RenderScriptGL具體是負責配置、執行RenderScript腳本,實現圖像渲染。但是圖像渲染一定需要一個容器用於呈現,這就需要RSSurfaceView。RSSurfaceView繼承自SurfaceView,我們主要是需要SurfaceView得到一個Surface作爲渲染的容器。然後就可以將這個SurfaceView放在Activity中,一個應用就成型了。

       具體來說,就是Activity通過setContentView將RSSurfaceView的子類作爲顯示內容。RSSurfaceView通過成員變量SurfaceHolder的getSurface方法獲得一個Surface。RenderScriptGL通過contextSetSurface方法,將Surface與RenderScript綁定。Android應用的架構就構建好了。

注意:和Android 3.0不同,Android2.3.4應用工程中的.rs腳本代碼並沒有放在src目錄下,而是作爲raw資源,放在raw文件夾下。

RenderScript基本作圖流程

       要實現RenderScript作畫,在Java層最重要的是RenderScriptGL類。除此之外就是.rs的腳本文件了。

       RenderScript實現一個最基本的紋理作圖,主要分爲三個步驟:初始化狀態、綁定.rs腳本並初始化、初始化作圖的視圖模式。

初始化狀態

       OpenGL是一個狀態機的概念,它的控制都是通過使能某個狀態實現的。因此,爲了使用OpenGL,我們在作圖前需要對一些狀態進行初始化。在這裏具體是指初始化以下三個類:ProgramVertex、ProgramFragment和ProgramStore。我們初始化的方法,是構建一個這樣的類的Builder實例,配置好對應的值,然後創建這個類的實力。最後調用setName方法爲該對象取好名字,以便在.rs文件中引用。

       ProgramVertex

       和定點數組相關的狀態設置,包括紋理矩陣的開啓。一般通過以下代碼初始化:

ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);

mPVBackground = pvb.create(); 

mPVBackground.setName("PVBackground");

       ProgramFragment

       紋理相關的狀態設置,我們可以通過它,上傳紋理。一般代碼如下:

ProgramFragment.Builder b = new ProgramFragment.Builder(mRS);

mPFBackground = b.create();

mPFBackground.setName("PFBackground");

ProgramFragment.Builder builder = new ProgramFragment.Builder(mRS);

builder.setTexture(ProgramFragment.Builder.EnvMode.MODULATE,

                          ProgramFragment.Builder.Format.RGBA, 0);

ProgramFragment mPfTexture = builder.create();

mPfTexture.setName("PFImages");

Allocation texture = loadTextureARGB(R.drawable.iceatlas, "bubble");

texture.uploadToTexture(0);

       ProgramStore

       其他一些屬性狀態設置。例如開啓深度緩衝、抗鋸齒、紋理是否支持透明色等。一般設置如下:

ProgramStore.Builder b = new ProgramStore.Builder(mRS, null, null);

b.setDepthFunc(ALWAYS);

b.setBlendFunc(BlendSrcFunc.ONE, BlendDstFunc.ONE_MINUS_SRC_ALPHA);

b.setDitherEnable(false);

b.setDepthMask(true);

mPSBackground = b.create();

mPSBackground.setName("PSBackground");

綁定.rs腳本,並初始化

       RenderScript的Java代碼,主要工作是初始化狀態和一些數據,而實際作圖的工作則在.rs腳本文件中定義。那麼如何讓腳本文件和Java代碼綁定在一起呢?這主要是通過RenderScriptGL 、ScriptC.Builder、Script這三個類實現的。

       首先,我們用RenderScriptGL對象構造一個ScriptC.Builder的實例。有了這個實例,我們就可以將.rs腳本文件設置進來:

ScriptC.Builder sb = new ScriptC.Builder(mRS);

sb.setScript(mRes, R.raw.oppo_rs);

sb.setRoot(true);

       然後,我們就可以通過ScriptC.Builder對象生成一個Script實例。

Script script = sb.create();

       另外,我們還可以設置一些作圖的屬性和數據。例如ClearColor、ClearDepth或者傳遞Allocation數據等。

script.setClearColor(0.0f,0.0f, 0.0f, 1f);

初始化作圖視圖模式

       在OpenGL 1.x中我們瞭解到,OpenGL作圖有兩種視圖模式:一種是透視視圖,一種是正交視圖。在初始化中,我們有必要顯示地指明我們使用的視圖模式。代碼示意如下:

ProgramVertex.MatrixAllocation mPVA = new ProgramVertex.MatrixAllocation(mRS);

mPVBackground.bindAllocation(mPVA);

//mPVA.setupProjectionNormalized(width, height);

mPVA.setupOrthoNormalized(width, height);

script.bindAllocation(mPVA.mAlloc, 0);

       其中setupProjectionNormalized是設置爲透視視圖,setupOrthoNormalized設置爲正交視圖。Normalized的意思是“歸一化”。是指將作圖的座標上下設置爲-1和1,高度按照長寬比確定。

RenderScript腳本基本寫法

       RenderScript作圖的最重要的部分在.rs腳本中。因此,我們除了以上的工作之外,還得明白如何寫RenderScript腳本。

       RenderScript腳本後綴爲.rs,在Android 2.3.4應用程序工程中,放在raw文件夾下。腳本語言語法使用C語言C99標準語法,不過能調用的庫做了較多限制。只能使用作圖和數學運算等Android提供的庫,不能使用C語言標準庫(原因是爲了保證代碼在絕大多數CPU、GPU上都能運行)。

預編譯指令

#pragma stateVertex(PVBackground)

#pragma stateFragment(PFBackground)

#pragma stateStore(PSBackground)

       這三句預編譯指令,指定了綁定的Java層初始化的狀態數組。相當於在代碼中執行:

bindProgramVertex(NAMED_PVBackground);

bindProgramFragment(NAMED_PFBackground);

bindProgramStroe(NAMED_PSBackgound);

執行入口

       Android 2.3.4中RenderScript腳本的入口是main函數,原型爲:

int main(intlaunchID)

基本函數

       RenderScript腳本的函數有不少,但是缺乏文檔說明。很多隻能結合源碼頭文件和demo等進行分析猜、試出來。當然它的函數命名比較規則,可以猜出一部分。關於腳本能使用的函數,一般在frameworks\base\libs\rs路徑及frameworks\base\libs\rs\scriptc路徑下的.h和.rsh頭文件中進行聲明。

       對於普通紋理貼圖,我們涉及到的函數不多,都是最基本的幾個函數。主要有:

       void color(float, float,float, float);

       設置作圖顏色,如果是紋理則作用不大。參數按順序分別是R、G、B、A。

       bindProgramFragment(NAMED_PFImages);

    bindTexture(NAMED_PFImages, 0,NAMED_bubble);

       綁定紋理。Script腳本中引用Java層的有名字的資源,可以直接用NAMED_javaName來使用。

       drawRect(-0.64f, -0.64f, 0.64f,0.64f, 0.0f);

       畫矩形,貼紋理。參數按順序,分別是left、right、top、bottom、z。需要注意的是,由於我們在視圖時採用了“歸一化”,因此,surface作圖區間的高爲-1~1,寬度按與高的比例得出。需要注意z方向的取值。Java框架中設置視圖模式的幾個方法原型示意如下:

public void setupOrthoWindow(int w, int h){

        mProjection.loadOrtho(0,w, h,0, -1,1);

        mAlloc.subData1D(PROJECTION_OFFSET, 16,mProjection.mMat);

}

 

public void setupOrthoNormalized(int w,int h) {

        // range -1,1 in the narrow axis.

        if(w > h) {

            float aspect = ((float)w) / h;

            mProjection.loadOrtho(-aspect,aspect,  -1,1, -1,1);

        } else {

            float aspect = ((float)h) / w;

            mProjection.loadOrtho(-1,1,-aspect,aspect,  -1,1);

        }

        mAlloc.subData1D(PROJECTION_OFFSET, 16,mProjection.mMat);

    }

 

public void setupProjectionNormalized(intw, int h) {

        // range -1,1 in the narrow axis at z = 0.

        ……

        if(w > h) {

            float aspect = ((float)w) / h;

            m1.loadFrustum(-aspect,aspect,  -1,1, 1,100);

        } else {

            float aspect = ((float)h) / w;

            m1.loadFrustum(-1,1,-aspect,aspect, 1,100);

        }

        ……

        mAlloc.subData1D(PROJECTION_OFFSET, 16,mProjection.mMat);

    }

因此,在正交視圖中,z的取值在-1到1之間。而在透視視圖中,z的取值應該在1到100之間。

RenderScript數據傳遞

       RenderScript分爲不同的層,我們可見的至少包括了java層和.rs腳本層。它們之間數據的傳遞是不可避免的。那麼數據如何在它們之間傳遞呢?

       .rs腳本的執行有可能是CPU也有可能是GPU,因此它所真正使用的數據存儲區,我們應該假設爲顯存空間。因此,RenderScript在一般情況下,涉及到數據傳遞的,都應該是由java層傳遞給.rs腳本,而不要反向傳遞。

       RenderScript的數據傳遞機制有專門的一套接口規範,我們應該嚴格按照這套接口來進行數據傳遞。這套接口主要涉及以下一些類:Type、Allocation、ScriptC.BuilderScriptScript。

       數據傳遞的原理分爲兩部分:聲明數據類型和傳遞內存數據。具體步驟大致如下:

1、  將java層需要傳遞的數據封裝爲一個靜態內部類,注意成員均爲基本數據類型。

2、  再將這個類綁定到一個腳本能識別的自定義Type上,並將這個Type在ScriptC.Builder中聲明(聲明必須在RenderScriptGL綁定Script之前)。

3、  構建好java層數據類的實例,並初始化。

4、  Alloaction於數據類型綁定。

5、  通過Allocation將數據類對象內存綁定到一個腳本能識別的自定義Allocation對象上。

6、  將此Alloaction綁定到script上。

7、  在.rs腳本中引用。

對照代碼示意如下:

聲明

Type mStateType;

Allocation mState;

WorldState mWorldState;

(ScriptC.Builder)sb.setType(mStateType, “State”, 0);

構建

mStateType = Type.createFromClass(mRs,WorldState.class, 1, “WorldState”);

mState = Allocation.createTyped(mRs,mStateType);

mWorldState構建和初始化;

傳遞

Script.bindAllocation(mState, 0);

mState.data(mWorldState);

.rs腳本中使用

State->member

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章