如何写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

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