一、概述
RenderScript是一个在高性能Android运行计算密集型任务的框架。RenderScript主要面向并行处理的密集型计算,虽然串行计算密集型工作负载可以受益。Renderscript运行时将所有设备上可用的处理器并行处理工作,如多核cpu、gpu,或DSPS,这样一来,开发者就能只关注表达算法,而不是安排并行算法或负载平衡。RenderScript对进行图像处理,计算摄影或计算机视觉相关的应用程序特别有用。
学习RenderScript之前,有两个概念必须明白:
1. 高性能计算内核都基于C99语言。
2.使用Java API来管理RenderScript的生命周期、资源和控制内核执行。
二、编写一个RenderScript内核
1.简介
RenderScript内核通常是存在rs文件当中,rs文件在在< project_root > / src /
目录中,每个rs文件称为一个脚本。每个脚本包含自己的内核,函数和变量。一个脚本可以包含:
1.1 编译指示声明(# pragma version(1))
,声明RenderScript内核的版本在这个脚本中使用的语言。目前,1是唯一有效的值。
1.2 编译指示声明(# pragma rs java_package_name(com.example.app))
声明从这个脚本反射的Java类的包名。注意你的rs文件必须是您的应用程序包的一部分,而不是在一个库项目当中。
1.3 一些调用函数。一个调用函数是一个单线程渲染脚本函数,你可以从Java代码调用任意参数。这些通常用于初始设置,或在一个更大的处理管道进行串行计算。
1.4 一些数量的全局变量。脚本的全局变量相当于一个可以从Java代码访问的全局变量,而这些通常用于参数传递RenderScript
内核。
1.5一些计算内核。内核是一个在每个Element
上,通过Allocation
并行执行的函数。
一个简单的内核可能看起来像下面的:
uchar4 __attribute__((kernel)) invert(uchar4 in, uint32_t x, uint32_t y) {
uchar4 out = in;
out.r = 255 - in.r;
out.g = 255 - in.g;
out.b = 255 - in.b;
return out;
}
在很多方面,这是一个标准的C函数,第一个显著特点是应用于函数原型的__attribute__((kernel))
。这表明这个函数是一个RenderScript
内核而不是一个调用函数。下一个特点是在这个函数的形式参数及其类型,在RenderScript
内核当中,这是一个特殊的参数形式,这些参数会根据传递给内核启动的输入Allocation
填充进来。默认情况下,内核运行依赖于整个Allocation
和该Allocation
中一的每个Element
。第三个显著特点是内核的返回类型,返回的值从内核自动写入输出Allocation
的适当位置。渲染脚本运行时检查,以确保输入和输出的元素类型分配匹配内核的原型;如果他们不匹配,就会抛出一个异常。
内核可能一个input Allocation
,一个output Allocation
,或两者兼而有之。内核可能没有超过一个输入和一个输出。如果需要多个输入或输出,应该将这些对象绑定脚本文件当中的tors_allocation
全局变量,从内核访问或调用函数通过rsGetElementAt_type()orrsSetElementAt_type()
。
一个内核可以访问当前执行使用的坐标x,y,和z参数。这些参数都是可选的,但必须uint32_t
参数的类型。
一个可选的init()
函数。一个init()函数是一种特殊类型的调用函数,运行脚本时需要首先实例化。这使得一些计算发生在脚本自动创建的时候。
一些静态全局变量和函数的脚本。一个静态的全局变量相当于一个全局变量,唯一的区别是,静态的全局变量不能从java代码当中进行设置,而普通的全局变量可以。一个静态函数是一个标准的C函数,可以从任何内核调用或调用函数在脚本中但不暴露于Java API。如果一个全局变量或函数不需要从Java代码调用,强烈建议那些被声明为静态的。
1.6 设置浮点精度(floating point precision)
在脚本中,您可以控制所需的水平的浮点精度。这是有用的,如果完整的IEEE 754 - 2008标准(默认情况下使用)不是必需的。下面的语法可以设置不同的浮点精度等级:
# pragma rs_fp_full(默认),如果没有指定,浮点精度由IEEE 754 - 754标准确定。
# pragma rs_fp_relaxed——应用程序不需要严格的IEEE 754 – 2008标准,可以容忍不是那么精确。这种模式使flush-to-zero denorms和round-towards-zero。
# pragma rs_fp_imprecise——应用程序没有严格的精度要求。这种模式使一切rs_fp_relaxed连同以下:
操作导致结果是-0.0的,返回+0.0
操作INF和NAN是未定义的。
大多数应用程序都可以使用rs_fp_relaxed,没有任何副作用。这对于那些只能通过降低精度来实现额外的优化的程序架构来说,是非常有益的(如CPU SIMD指令)。
三、访问RenderScript的API
在开发Android应用程序时,您可以通过两种方式来访问RenderScript的API:
1 android.renderscript——这个类的API包可用设备上运行Android 3.0(API级别11)和更高。
2 android.support.v8.renderscript——在这个包中可用的API支持库,它允许您使用设备上运行Android 2.2(API级别8)和更高。
我们强烈建议使用支持库api来访问RenderScript
,因为他们提供了一个广泛的设备兼容。针对特定版本的Android
开发人员可以使用Android.renderscript
,如果必要的话。
1. 使用RenderScript支持库api
为了使用支持库RenderScript api,您必须配置您的开发环境能够访问它们。
Android SDK Tools版本22.2或更高版本
Android SDK Build-tools修订18.1.0或更高
Eclipse当中需要以下三步:
第一步、确保你有安装必需的Android SDK版本和构建工具版本。
第二步、在project.properties当中加入下面的内容
renderscript.target=18
renderscript.support.mode=true
sdk.buildtools=18.1.0
第三步,在.java文件当中进行使用
import android.support.v8.renderscript.*;
project.properties
设置控制Android构建过程中的特定行为:
3.1renderscript.target——指定生成的字节码版本。我们建议您设置这个值为最高的可用的API级别,同时设置 renderscript.support.mode = true
。这个设置的有效值是任何整数值从11到最近发布的API级别。如果你的最低SDK版本中指定您的应用程序清单文件设置为较高的值,这个值将被忽略,并被设置为清单文件当中的minSdkVersion。
3.2 renderscript.support.mode——当运行的设备不支持指定的renderscript.target
,是否回滚到可兼容版本
3.3 sdk.buildtools - ——Android SDK的版本使用构建工具。这个值应该设置为18.1.0或更高。如果没有指定这个选项,默认是安装的最高版本
Android Studio当中,需要进行如下方式:
第一步、在app的build.gradle当中添加如下代码
sourceSets {
main {
dirs.each { dir ->
java.srcDirs "src/${dir}/java"
res.srcDirs "src/${dir}/res"
}
}
androidTest.setRoot('tests')
androidTest.java.srcDirs = ['tests/src']
defaultConfig {
renderscriptTargetApi 18
renderscriptSupportModeEnabled true
}
}
第二步、添加依赖
compile files('renderscript-v8.jar')
2. 从Java代码当中调用RenderScript的API
从java代码当中调用RenderScriptAPI,主要是android.renderscript
包和android.support.v8.renderscript
包下面的类,大多数应用程序都遵循相同的使用模式:
2.1.初始化一个RenderScript Context
。RenderScript Context
通过create(Context)
创建,确保RenderScript
可以使用,并提供一个对象来控制所有后续RenderScript
对象的生命周期。你应该考虑上下文创建一个潜在的长期运行的操作,因为它可以在不同的硬件上创建资源,它不应该在应用程序的关键路径(如果可能的话)。通常,应用程序将只有一个RenderScript Context
。
2.2.创建至少一个Allocation
,并传递给脚本文件,Allocation
提供固定长度的存储空间,renderscript
将Allocation
作为它的输入和输出,并且,Allocation
对象在内核当中,如果通过绑定成为全局变量,还可以通过rsGetElementAt_type()
和rsSetElementAt_type()
来进行访问,Allocation对象允许以数组形式从java代码传递到renderscript
代码当中,反之亦然,Allocation
的创建通常是通createTyped(Renderscript,Type)
或者createFromBitmap(RenderScript,Bitmap)
来实现的
3.创建必须的脚本文件,使用renderscript脚本时,有两种脚本文件是可选的
3.1 ScriptC:就像上面编写的脚本文件一样,用户可以自定义自己的脚本文件,每一个脚本文件都有一个java反射类与之对应,以便于通过java代码来调用脚本文件,这个反射类的名称是ScriptC_filename
,比如,如果上面的脚本文件命名为invert.rs,并且已经有一个名为mRs的RenderScript Context
存在了,那么可以通过如下方式创建一个对应的java类:
ScriptC_invert invert=new ScriptC_invert(mRs);
3.2 ScriptIntrinsic
:这些RenderScript
内核定义了常用的渲染操作,比如高斯模糊、卷积和图像融合,具体的信息,可以查阅ScriptIntrinsic
的子类
4. 用数据来填充Allocation
除了通过androoid.renderscript
来创建之外,一个Allocation
在他第一次创建时候会被空数据填充,用数据填充Allocation
,可以使用Allocation
当中的某个copy方法来实现
5.设置一些必要的脚本全局变量
这些脚本全局变量可以通过ScriptC_filename.java类当中set_globeName()
来设置值,比如,为了给一个名为elements的int型全局变量设置值,可以通set_elements(int)
,RenderScript
对象同样可以在内核当中被设置,比如,rs_allocation
当中的lookup可以通过set_lookup(Allocation)
来进行设置
6.启动适当的核。
脚本文件当中的内核(实际是一个方法),在映射java类当中命名为forEach_kernelname()
,非内核的可调用方法同样可以使用invoke_functionname()
来进行调用
7.从Allocation
对象当中复制出数据
在java代码当中,为了从一个Allocation
当中访问数据,必须将数据复制到一个java缓冲区当中,此时可以通过Allocation
的copy方法实现,这些函数将同步与异步启动必要的内核和函数。
8.关闭RenderScript Context
RenderScript
上下文可以通过destroy()被摧毁或通过允许RenderScript Context
对象被垃圾收集,这将导致进一步使用RenderScriptContext
的任何对象在这种情况下抛出异常。
四、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);
一旦内存空间被绑定,在每次数据改变的时候就不必每次再去绑定了
关于RenderScript的代码示例点此下载