RenderScript高级篇
因为使用RenderScript的应用程序仍然运行在Android虚拟机中,您可以访问所有您熟悉的api, 在适当的时候可以使用RenderScript,促进之间的交互和RenderScript运行时,中间层代码也存在促进沟通和两个层次之间的内存管理问题。本文档进入更详细的关于这些不同层次的代码以及如何在Android虚拟机之间共享内存和RenderScript运行时。
1. RenderScript Runtime 层
你RenderScript代码编译和执行在一个紧凑的和良好定义的运行时层。RenderScript提供了一系列在核心处理器上支持便携式密集计算和自动伸缩的API。
标准的C函数在NDK必须保证在一个CPU上运行,所以RenderScript不能访问这些库,因为RenderScript是为了在不同类型的处理器上运行。
你可以在src/目录下面定义你的.rs文件和.rsh文件, 这些代码通过llvm编译器编译成中间字节码并作为Android构建的一部分在编译器上面运行。当你的应用程序运行在一个设备,然后编译字节码(即时)由另一个llvmcompiler机器代码驻留在设备上。设备的机器代码优化和缓存,所以后续使用RenderScript启用应用程序不重新编译字节码。
RenderScript Runtime库的一些关键特性包括:
1.1内存分配请求的特性
1.2大量的数学函数标量和矢量。,如添加、复制、点积,以及原子运算和比较功能。
1.3原始数据类型转换,比如例程向量、矩阵的例程,日期和时间的例程
1.4数据类型和结构以支持RenderScript系统,如向量类型定义two-、three-、或four-vectors。
1.5日志记录功能
2.反射层
反射层是有android build tools生成的一系列类,以便能够通过android框架层去访问RenderScript,这一层还提供了方法和构造函数允许您为您在RenderScript代码当中定义的指针分配和操作内存空间。下面的列表描述了反射的主要组件:
2.1 每一个.rs文件会被映射到project_root/gen/package/name/ScriptC_renderscript
_filename.java,这个文件是.rs文件的对应文件,这个java文件包含了从.rs反射过来的如下项目:
A. 非static的函数
B. 非static的全局变量,并生成了相应的setter/getter方法,可以在java层通过这些方法访问和设置这些变量,如果一个全局的变量在RenderScript当中初始化过,这个初始化操作会在java层的构造方法当中进行,如果全局变量声明为const,那么只会在java层生成setter方法
2.2 一个struct 会被反射到project_root/gen/package/name/ScriptField_struct_name.java
当中,这个类继承自Script.FieldBase,这个类代表了struct的一个队列,并允许开发为其分配内存空间
2.3 函数
函数会被映射到java类当中,比如,在.rs文件当中定义了如下的方法:
void touch(float x, float y, float pressure, int id) { if (id >= 10) { return; } touchPos[id].x = x; touchPos[id].y = y; touchPressure[id] = pressure; }
那么会在映射类当中生成如下代码:
public void invoke_touch(float x, float y, float pressure, int id) { FieldPacker touch_fp = new FieldPacker(16); touch_fp.addF32(x); touch_fp.addF32(y); touch_fp.addF32(pressure); touch_fp.addI32(id); invoke(mExportFuncIdx_touch, touch_fp); }
函数是没有返回值的,因为RenderScript系统被设计成异步的。当你的Java代码调用RenderScript,这些调用的操作会尽可能执行。这一限制允许RenderScript系统功能没有持续的中断并增加效率。如果允许函数有返回值,调用会阻塞,直到返回的值。
如果你想要RenderScript
代码将值发送回Android框架,使用rsSendToClient()
函数。
2.4 变量
变量会被反射到反射的java文件当中,例如,如果在rs文件当中定义了如下变量
uint32_t unsignedInteger = 1;
就会在java文件当中生成如下java代码
private long mExportVar_unsignedInteger; public void set_unsignedInteger(long v){ mExportVar_unsignedInteger = v; setVar(mExportVarIdx_unsignedInteger, v); } public long get_unsignedInteger(){ return mExportVar_unsignedInteger; }
2.5 结构体
typedef struct Point { float2 position; float size; } Point_t;
会在gen目录下面生成对应的java文件:ScriptField_Point.java
package com.example.android.rs.hellocompute; import android.renderscript.*; import android.content.res.Resources; public class ScriptField_Point extends android.renderscript.Script.FieldBase { static public class Item { public static final int sizeof = 12; Float2 position; float size; Item() { position = new Float2(); } } private Item mItemArray[]; private FieldPacker mIOBuffer; public static Element createElement(RenderScript rs) { Element.Builder eb = new Element.Builder(rs); eb.add(Element.F32_2(rs), "position"); eb.add(Element.F32(rs), "size"); return eb.create(); } public ScriptField_Point(RenderScript rs, int count) { mItemArray = null; mIOBuffer = null; mElement = createElement(rs); init(rs, count); } public ScriptField_Point(RenderScript rs, int count, int usages) { mItemArray = null; mIOBuffer = null; mElement = createElement(rs); init(rs, count, usages); } private void copyToArray(Item i, int index) { if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()); mIOBuffer.reset(index * Item.sizeof); mIOBuffer.addF32(i.position); mIOBuffer.addF32(i.size); } public void set(Item i, int index, boolean copyNow) { if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */]; mItemArray[index] = i; if (copyNow) { copyToArray(i, index); mAllocation.setFromFieldPacker(index, mIOBuffer); } } public Item get(int index) { if (mItemArray == null) return null; return mItemArray[index]; } public void set_position(int index, Float2 v, boolean copyNow) { if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */); if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */]; if (mItemArray[index] == null) mItemArray[index] = new Item(); mItemArray[index].position = v; if (copyNow) { mIOBuffer.reset(index * Item.sizeof); mIOBuffer.addF32(v); FieldPacker fp = new FieldPacker(8); fp.addF32(v); mAllocation.setFromFieldPacker(index, 0, fp); } } public void set_size(int index, float v, boolean copyNow) { if (mIOBuffer == null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */); if (mItemArray == null) mItemArray = new Item[getType().getX() /* count */]; if (mItemArray[index] == null) mItemArray[index] = new Item(); mItemArray[index].size = v; if (copyNow) { mIOBuffer.reset(index * Item.sizeof + 8); mIOBuffer.addF32(v); FieldPacker fp = new FieldPacker(4); fp.addF32(v); mAllocation.setFromFieldPacker(index, 1, fp); } } public Float2 get_position(int index) { if (mItemArray == null) return null; return mItemArray[index].position; } public float get_size(int index) { if (mItemArray == null) return 0; return mItemArray[index].size; } public void copyAll() { for (int ct = 0; ct < mItemArray.length; ct++) copyToArray(mItemArray[ct], ct); mAllocation.setFromFieldPacker(0, mIOBuffer); } public void resize(int newSize) { if (mItemArray != null) { int oldSize = mItemArray.length; int copySize = Math.min(oldSize, newSize); if (newSize == oldSize) return; Item ni[] = new Item[newSize]; System.arraycopy(mItemArray, 0, ni, 0, copySize); mItemArray = ni; } mAllocation.resize(newSize); if (mIOBuffer != null) mIOBuffer = new FieldPacker(Item.sizeof * getType().getX()/* count */); } }
这些生成的代码提供给开发者作为一个为机构体分配内存的便捷方式,每一个结构体类定义了如下的方法和结构体
2.5.1 重载的构造函数允许您分配内存。
ScriptField_struct_name(RenderScript rs,int count)
构造函数允许您定义结构体的数量(count)。ScriptField_struct_name(RenderScript rs,int,int usages)
构造函数定义了一个额外的参数, usages,允许您指定内存分配的内存空间。有四个内存空间的可能性:
• USAGE_SCRIPT
: 脚本中分配内存空间。如果你不指定一个内存空间这是默认的内存空间。
• USAGE_GRAPHICS_TEXTURE
: GPU纹理内存空间的分配.
• USAGE_GRAPHICS_VERTEX
: 顶点的GPU内存空间分配.
• USAGE_GRAPHICS_CONSTANTS
: 分配Gpu当中的常量内存空间,
您可以指定多个内存空间通过按位或运算。这样做通知RenderScript层,您打算访问的数据在指定的内存空间。下面的示例为自定义数据类型分配内存脚本和顶点内存空间:
ScriptField_Point touchPoints = new ScriptField_Point(myRenderScript, 2, Allocation.USAGE_SCRIPT|Allocation.USAGE_GRAPHICS_VERTEX);
2.5.2 静态嵌套类,Item,允许您创建一个strut的实例。
这种嵌套类是有用的,如果更有意义的工作在Android代码结构。当你完成操纵对象,可以把对象分配内存通过调用set(Item i,int index,Boolean copyNow)和设置数组中的项到所需的位置。RenderScript会自动访问新的写内存。
2.5.3 访问器方法来get和set结构中的每个字段的值。
这些访问器方法中的每一个都有一个索引参数来指定数组中的结构,你想读或写。每个setter方法还有一个copyNow参数,指定是否要立即同步这momory到RenderScript。同步所有未同步的内存空间,可以调用copyAll()方法。
2.5.4 createElement() 方法创建了结构体在内存当中的一段描述,这个描述被用来分配包含一个或多个element的内存空间
2.5.5 resize()所起的作用就像在C语言中的realloc(),允许您扩展以前分配的内存,维持先前创建的当前值。
2.5.6 copyAll()在JAVA层同步内存到RS层。
当你调用一组访问器方法成员,有一个可选的copyNow布尔参数。指定true则在调用该方法的时候立即进行同步法。false,你可以调用copyAll(),它会同步还没有同步的所有属性。
2.6 指针
指针会被反射到project_root/gen/package/name/ScriptC_renderscript_filename
文件当中,你可以声明一个结构体的指针,或者任何RenderScript支持的指针类型,但是一个结构体不能包含指针,或者嵌套的数组,比如,如果你声明了下面的指针:
typedef struct Point { float2 position; float size; } Point_t; Point_t *touchPoints; int32_t *intPointer;
接下来会生成如下的代码
private ScriptField_Point mExportVar_touchPoints; public void bind_touchPoints(ScriptField_Point v) { mExportVar_touchPoints = v; if (v == null) bindAllocation(null, mExportVarIdx_touchPoints); else bindAllocation(v.getAllocation(), mExportVarIdx_touchPoints); } public ScriptField_Point get_touchPoints() { return mExportVar_touchPoints; } private Allocation mExportVar_intPointer; public void bind_intPointer(Allocation v) { mExportVar_intPointer = v; if (v == null) bindAllocation(null, mExportVarIdx_intPointer); else bindAllocation(v, mExportVarIdx_intPointer); } public Allocation get_intPointer() { return mExportVar_intPointer; }
一个get方法和一个名为bind_pointer_name的特殊方法生成了,这里并没有生成set方法,这个方法允许你绑定android虚拟机分配的内存空间到RenderScript(开发者不能在rs文件当中分配存储空间)
2.7内存空间分配api
使用Renderscript的应用仍然运行在Android虚拟机中。实际的Renderscript代码在本地运行,需要访问分配在Android虚拟机内存。要做到这一点,你必须把vm的内存分配给 Renderscript运行。这个过程,称为绑定,允许Renderscript运行时它请求的内存,但不能明确无缝工作分配。最终的结果基本上与在c当中调用malloc()相同,有一些额外的好处是,android 虚拟机能够自动清理内存的垃圾,以及能够与RenderScript共享内存。结合只对于动态分配的内存是必要的。静态分配的内存在编译时会自动为你创建的renderScript代码。
为了支持这个内存分配系统,有一系列的api允许android虚拟机分配内存,并提供与malloc相似的功能,这些类描述了内存应该如何分配以及如何回收这些内存,为了更好地理解这些类是如何工作的,比较c当中的malloc是一个有用的方法:
array=(int*)malloc(sizeof(int)*10);
malloc方法可以被分成两个部分,一是sizeof(int)内存被分配出来,二是多少个这样的单位应该被分配出来(10),android当中的java层为这两个部分提供了类的api,并且提供了代表malloc本身的类
在java当中,Element类代表了(sizeof(int))部分,比如一个单独的float类型数据或者一个结构体,类Type包含了Element和需要分配的elements的数量(即10的部分),你可以将Type看作是Element的一个数组,而Allocation类基于Type 类做了实际的内存分配工作,并且代表了实际的分配内存。
在多数的情况下,你不必直接去调用这些内存分配api,那些反射的java类会自动使用这些api,需要你去进行内存分配的只是去调用这些反射的构造方法,并且将代表分配内存空间的Allocation绑定到RendersScript上面,但在有些场景当中,你还是需要使用这些类进行手动的内存空间分配,比如从资源文件当中加载一张bitmap或者为一些原始类型的指针分配内存空间,下面的说明描述了三种内存管理类:
2.7.1 Element
一个Element描述内存分配的一个单元,可以有两种形式:基本或复杂的。
一个基本的Element包含任何有效的RenderScript数据类型的数据单组。基本元素的数据类型的例子包括一个浮点值,一个float4向量,或一个单一的rgb-565颜色。
复杂的Elements包含一个基本elements的列表,并从你在RenderScript代码中声明的结构体中创建,例如一次分配可以包含在内存中依次排列的多个结构体。每个结构体被认为是它自己的元素,而不是每个数据类型的内部结构。
2.7.2 Type
Type是一个内存分配模板包括一元和一个或多个维度。它描述了内存布局(基本上是一个Elements的数组)但不分配它所描述的内存数据。
一个type包括五个维度:X,Y,Z,LOD(层次细节),和Faces(一个立方体地图)。你可以指定x,y,z维度为在可用内存的限制的任意正整数的值。一个单一的维度配置X尺寸大于零,而Y和Z尺寸为零表示不存在。例如,分配内存时(X = 10,Y = 1)是二维而x = 10,y = 0是一维的。LOD和Faces的尺寸是布尔值,指示存在或不存在。
2.7.3 Allocation
Allocation提供基于内存描述(Type)应用内存。分配的内存中可以同时存在很多的内存空间。如果内存在某一个空间被修改,你必须显式地同步内存,以便它在所有的内存空间当中保持最新。
分配的数据有两种主要方式上传:type checked
和type unchecked
。简单的数组有copyfrom()
函数将数组从Android系统并将它复制到本地存储层。unchecked
的变量允许Android系统复制在结构体数组,因为它不支持结构体。例如,如果有一个分配N个floats的数组,数据包含在一个float[n ]
数组或byte[ N×4 ]
阵列可以复制。
2.8 在内存上运行
在Renderscript中声明的非静态的全局变量是在编译时分配的内存。您可以在你的Renderscript代码中直接使用这些变量而不需要在JAVA层为它们分配内存。Android框架层还具有访问这些变的访问方法。如果这些变量在Renderscript运行时层初始化,这些值在反射类的构造方法当中就会被初始化。如果全局变量被标记为const,那么不会生成相应的setter方法。
如果你使用了某些包含指针的rs结构体,比如rs_program_fragment/ rs_allocation,你就必须首先取得一个在java层与之对应的类,然后调用set方法绑定内存空间到rs层,这个限制对于用户自定义的包含指针的结构并不适用,因为他们不能生成对应的反射类,如果你试着声明一个非静态的、包含指针的全局结构,编译器将会报编译错误
rs也有对指针的支持,但是你必须在java层明确地给其分配内存空间,当你在rs文件当中生命了一个全局的指针,你就必须在对应的java反射类当中为其分配内存空间,并且将内存绑定到rs层,然后你就可以在java层或者rs层来对该段内存进行修改了
2.9 分配并绑定动态内存到RenderScript
分配动态内存,你通常需要调用Script.FIeldBase的构造方法,另外一个选择时手动地创建一个 Allocation,这需要一些额外的东西,比如一些原始类型的指针,简单起见,最好适用FieldBase的构造方法,获取到一段内存空间之后,调用bind方法将分配的内存绑定到RS上面
下面的例子分别分配了内存空间给一个原始类型的指针、intPointer、一个结构体的指针,touchPoints并将分配的内存绑定到Rs上面:
private RenderScript myRenderScript; private ScriptC_example script; private Resources resources; public void init(RenderScript rs, Resources res) { myRenderScript = rs; resources = res; //allocate memory for the struct pointer, calling the constructor ScriptField_Point touchPoints = new ScriptField_Point(myRenderScript, 2); //Create an element manually and allocate memory for the int pointer intPointer = Allocation.createSized(myRenderScript, Element.I32(myRenderScript), 2); //create an instance of the RenderScript, pointing it to the bytecode resource mScript = new ScriptC_example(myRenderScript, resources, R.raw.example); //bind the struct and int pointers to the RenderScript mScript.bind_touchPoints(touchPoints); script.bind_intPointer(intPointer); ... }
2.10 读写数据到内存当中
重点内容你可以在rs层和java层读写静态或者动态分配的内存
重点内容静态分配的内存是在Renderscript层单向通信的。当Renderscript代码改变变量的值,它不会传递回Android的java层。当调用get方法的时候,通过java层最后设置的值将被返回。然而,当Android框架修改一个变量,这是可以自动改变rs层的相应的数据或在稍后同步。如果你需要从Renderscript运行时发送数据到Android框架层,你可以使用rsSendToClient()函数来克服这一局限性。
重点内容使用动态内存分配时,如果你修改了内存分配使用相关的指针,在Renderscript运行时层的任何更改会传播回Android框架层。在java 层修改了某个对象,会立即将这个修改传回到rs层
3. 读写全局变量
读写全局变量是一个非常简单的过程,你能够通过java层的一些访问器方法进行访问,或者在rs代码当中对他们进行直接设置,永远记住,在rs代码当中进行的任何改变不会传递到java层
比如,在rsfile.rs当中定义了如下的结构体
typedef struct Point { int x; int y; } Point_t; Point_t point;
可以在rsfile.rs文件当中直接对 point赋值,这些值是不会回传到java层的
point.x = 1; point.y = 1;
也可以在java层对数据进行赋值,而这些值将立即传递到rs层:
ScriptC_rsfile mScript; ... Item i = new ScriptField_Point.Item(); i.x = 1; i.y = 1; mScript.set_point(i);
可以通过如下的方式读取相应的值:
rsDebug("Printing out a Point", point.x, point.y);
当然也可以在java层进行读取,记住,如果只是在rs对point进行了值的设置,那么下面的get方法将会得到的是null
Log.i("TAGNAME", "Printing out a Point: " + mScript.get_point().x + " " + mScript.get_point().y); System.out.println(point.get_x() + " " + point.get_y());
4. 读写全局指针
假定在java层已经为指针分配了内存空间,并且将其绑定到了rs层,那么我们就可以通过get/set方法读写这部分的内存数据了,你可以通过操作指针的一般方式来进行读写操作,并且这些操作会传回到java层,这是与静态内存空间分配不同的
比如:rsfile.rs
typedef struct Point { int x; int y; } Point_t; Point_t *point;
假定你已经在java层分配了内存空间,那么你可以通过struct的一般方式进行值的访问,在rs层的任意改变都将会影响到java层
point[index].x = 1; point[index].y = 1;
你也可以在java层对指针进行相应的访问;
ScriptField_Point p = new ScriptField_Point(mRS, 1); Item i = new ScriptField_Point.Item(); i.x=100; i.y = 100; p.set(i, 0, true); mScript.bind_point(p); points.get_x(0); //read x and y from index 0 points.get_x(0);
一旦内存空间被绑定,在每次数据改变的时候就不必每次再去绑定了