由于使用 RenderScript 的应用仍在 Android VM 内部运行,因此您可以访问您熟悉的全部框架 API,但在适当情况下可以使用 RenderScript。为了促进框架和 RenderScript 运行时之间的这种交互,还存在一个中间代码层来促进这两个代码级别之间的通信和内存管理。本文档将更详细地介绍这些不同的代码层以及 Android VM 和 RenderScript 运行时之间如何共享内存。
RenderScript 运行时层
您的 RenderScript 代码在紧凑且定义明确的运行时层中编译和执行。RenderScript 运行时 API 支持密集计算,该计算可移植且可自动扩展到处理器上可用内核的数量。
注意: NDK 中的标准 C 函数必须保证在 CPU 上运行,因此 RenderScript 无法访问这些库,因为 RenderScript 旨在在不同类型的处理器上运行。
您在 Android 项目的 src/
目录中的 .rs
和 .rsh
文件中定义您的 RenderScript 代码。代码由作为 Android 构建的一部分运行的 llvm
编译器编译成中间字节码。当您的应用在设备上运行时,字节码随后由驻留在设备上的另一个 llvm
编译器(即时)编译成机器码。机器码针对设备进行了优化,并且还会缓存,因此随后使用启用 RenderScript 的应用不会重新编译字节码。
RenderScript 运行时库的一些关键功能包括
- 内存分配请求功能
- 大量数学函数,其中许多常用例程都具有标量和向量类型重载版本。加法、乘法、点积和叉积等运算可用,以及原子算术和比较函数。
- 用于基本数据类型和向量、矩阵例程以及日期和时间例程的转换例程
- 数据类型和结构以支持 RenderScript 系统,例如用于定义二维、三维或四维向量的 Vector 类型。
- 日志记录功能
有关可用函数的更多信息,请参阅 RenderScript 运行时 API 参考。
反射层
反射层是一组由 Android 构建工具生成的类,用于允许从 Android 框架访问 RenderScript 运行时。此层还提供方法和构造函数,允许您为在 RenderScript 代码中定义的指针分配和处理内存。以下列表描述了反射的主要组件。
- 您创建的每个
.rs
文件都会生成一个名为project_root/gen/package/name/ScriptC_renderscript_filename
的类,其类型为ScriptC
。此文件是.rs
文件的.java
版本,您可以从 Android 框架中调用它。此类包含从.rs
文件中反射的以下项目。- 非静态函数
- 非静态全局 RenderScript 变量。为每个变量生成访问器方法,因此您可以从 Android 框架读取和写入 RenderScript 变量。如果全局变量在 RenderScript 运行时层初始化,则使用这些值来初始化 Android 框架层中的相应值。如果全局变量标记为
const
,则不会生成set
方法。请参阅 此处 获取更多详细信息。 - 全局指针
- 一个
struct
会反射到其自己的类中,该类名为project_root/gen/package/name/ScriptField_struct_name
,它扩展了Script.FieldBase
。此类表示struct
的数组,允许您为一个或多个此struct
的实例分配内存。
函数
函数会反射到脚本类本身,位于 project_root/gen/package/name/ScriptC_renderscript_filename
中。例如,如果您在 RenderScript 代码中定义以下函数:
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; }
则会生成以下 Java 代码:
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 系统被设计为异步的。当您的 Android 框架代码调用 RenderScript 时,该调用会被排队并在可能时执行。此限制允许 RenderScript 系统在没有持续中断的情况下运行并提高效率。如果允许函数有返回值,则调用将阻塞,直到返回值返回。
如果希望 RenderScript 代码将值发送回 Android 框架,请使用 rsSendToClient()
函数。
变量
受支持类型的变量会反射到脚本类本身,位于 project_root/gen/package/name/ScriptC_renderscript_filename
中。为每个变量生成一组访问器方法。例如,如果您在 RenderScript 代码中定义以下变量:
uint32_t unsignedInteger = 1;
则会生成以下 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; }
结构体
结构体会反射到其自己的类中,位于 <project_root>/gen/com/example/renderscript/ScriptField_struct_name
中。此类表示 struct
的数组,并允许您为指定数量的 struct
分配内存。例如,如果您定义以下结构体:
typedef struct Point { float2 position; float size; } Point_t;
则会在 ScriptField_Point.java
中生成以下代码:
package com.example.android.rs.hellocompute; import android.renderscript.*; import android.content.res.Resources; /** * @hide */ 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()/* count */); 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 */); } }
生成的代码为您提供便利,以便为 RenderScript 运行时请求的结构体分配内存,并与内存中的 struct
进行交互。每个 struct
的类都定义了以下方法和构造函数:
- 允许您分配内存的重载构造函数。
ScriptField_struct_name(RenderScript rs, int count)
构造函数允许您使用count
参数定义要为其分配内存的结构体的数量。ScriptField_struct_name(RenderScript rs, int count, int usages)
构造函数定义了一个额外的参数usages
,它允许您指定此内存分配的内存空间。有四种内存空间可能性:USAGE_SCRIPT
:在脚本内存空间中分配。如果未指定内存空间,则这是默认内存空间。USAGE_GRAPHICS_TEXTURE
:在 GPU 的纹理内存空间中分配。USAGE_GRAPHICS_VERTEX
:在 GPU 的顶点内存空间中分配。USAGE_GRAPHICS_CONSTANTS
:在 GPU 的常量内存空间中分配,各种程序对象使用该空间。
您可以使用按位
OR
运算符指定多个内存空间。这样做会通知 RenderScript 运行时您打算在指定的内存空间中访问数据。以下示例在脚本和顶点内存空间中为自定义数据类型分配内存:Kotlin
val touchPoints: ScriptField_Point = ScriptField_Point( myRenderScript, 2, Allocation.USAGE_SCRIPT or Allocation.USAGE_GRAPHICS_VERTEX )
Java
ScriptField_Point touchPoints = new ScriptField_Point(myRenderScript, 2, Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_VERTEX);
- 一个静态嵌套类
Item
允许您以对象的形式创建struct
的实例。如果在 Android 代码中使用struct
更合理,则此嵌套类很有用。完成对对象的处理后,您可以通过调用set(Item i, int index, boolean copyNow)
并将Item
设置为数组中的所需位置来将对象推送到分配的内存中。RenderScript 运行时可以自动访问新写入的内存。 - 访问器方法,用于获取和设置结构体中每个字段的值。每个访问器方法都有一个
index
参数,用于指定要读取或写入的数组中的struct
。每个 setter 方法也都有一个copyNow
参数,该参数指定是否立即将此内存同步到 RenderScript 运行时。要同步尚未同步的任何内存,请调用copyAll()
。 createElement()
方法创建内存中结构体的描述。此描述用于分配包含一个或多个元素的内存。resize()
的工作方式类似于 C 中的realloc()
,允许您扩展先前分配的内存,同时保留先前创建的当前值。copyAll()
将在框架级别设置的内存与 RenderScript 运行时同步。当您在成员上调用 set 访问器方法时,可以指定一个可选的copyNow
布尔参数。指定true
会在调用方法时同步内存。如果指定 false,则可以调用一次copyAll()
,它会同步所有尚未同步的属性的内存。
指针
全局指针会反射到脚本类本身,位于 project_root/gen/package/name/ScriptC_renderscript_filename
中。您可以声明指向 struct
或任何受支持的 RenderScript 类型的指针,但 struct
不能包含指针或嵌套数组。例如,如果您定义以下指向 struct
和 int32_t
的指针:
typedef struct Point { float2 position; float size; } Point_t; Point_t *touchPoints; int32_t *intPointer;
则会生成以下 Java 代码:
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()
方法)。 bind_pointer_name
方法允许您将 Android VM 中分配的内存绑定到 RenderScript 运行时(您不能在 .rs
文件中分配内存)。有关更多信息,请参阅 使用已分配内存。
内存分配 API
使用 RenderScript 的应用程序仍在 Android VM 中运行。但是,实际的 RenderScript 代码以原生方式运行,并且需要访问在 Android VM 中分配的内存。为此,您必须将 VM 中分配的内存附加到 RenderScript 运行时。此过程称为绑定,允许 RenderScript 运行时无缝地处理它请求但无法显式分配的内存。最终结果与您在 C 中调用 malloc
基本相同。额外的好处是 Android VM 可以执行垃圾回收以及与 RenderScript 运行时层共享内存。绑定仅适用于动态分配的内存。静态分配的内存在编译时会自动为您的 RenderScript 代码创建。有关内存分配方式的更多信息,请参阅 图 1。
为了支持此内存分配系统,有一组 API 允许 Android VM 分配内存并提供与 malloc
调用类似的功能。这些类本质上描述了如何分配内存,并执行分配。为了更好地理解这些类的工作原理,可以将其与简单的 malloc
调用相关联,该调用可能如下所示:
array = (int *)malloc(sizeof(int)*10);
malloc
调用可以分解为两个部分:正在分配的内存的大小(sizeof(int)
),以及应分配该内存的单元数(10)。Android 框架也为这两个部分提供了类,以及一个表示 malloc
本身的类。
Element
类表示 malloc
调用的(sizeof(int)
)部分,并封装内存分配的一个单元,例如单个浮点值或结构体。 Type
类封装了 Element
和要分配的元素数量(在我们的示例中为 10)。您可以将 Type
视为 Element
的数组。 Allocation
类根据给定的 Type
执行实际的内存分配,并表示实际分配的内存。
在大多数情况下,您不需要直接调用这些内存分配 API。反射层类会生成代码来自动使用这些 API,您只需调用在反射层类之一中声明的构造函数,然后将生成的内存 Allocation
绑定到 RenderScript 即可。在某些情况下,您可能希望直接使用这些类来自己分配内存,例如从资源加载位图或当您想要为指向基本类型的指针分配内存时。您可以在 将内存分配和绑定到 RenderScript 部分中了解如何执行此操作。下表更详细地描述了这三个内存管理类:
Android 对象类型 | 描述 |
---|---|
Element |
Element 描述内存分配的一个单元,并且可以有两种形式:基本或复杂。 基本元素包含任何有效 RenderScript 数据类型的单个数据组件。基本元素数据类型的示例包括单个 复杂元素包含一个基本元素列表,并由您在 RenderScript 代码中声明的 |
类型 |
类型是一个内存分配模板,由一个元素和一个或多个维度组成。它描述了内存的布局(基本上是 一个类型包含五个维度:X、Y、Z、LOD(细节级别)和 Faces(立方体贴图的面)。您可以将 X、Y、Z 维度设置为可用内存限制内的任何正整数。单维度分配的 X 维度大于零,而 Y 和 Z 维度为零表示不存在。例如,x=10、y=1 的分配被认为是二维的,而 x=10、y=0 被认为是一维的。LOD 和 Faces 维度是布尔值,表示存在或不存在。 |
分配 |
分配根据 分配数据主要通过两种方式上传:类型检查和类型未检查。对于简单的数组,有一些 |
使用内存
您在 RenderScript 中声明的非静态全局变量在编译时分配内存。您可以在 RenderScript 代码中直接使用这些变量,而无需在 Android 框架层为其分配内存。Android 框架层还可以通过反射层类中生成的提供的访问器方法访问这些变量。如果这些变量在 RenderScript 运行时层初始化,则这些值将用于初始化 Android 框架层中的相应值。如果全局变量被标记为 const,则不会生成set
方法。请查看此处了解更多详细信息。
注意:如果您正在使用某些包含指针的 RenderScript 结构,例如rs_program_fragment
和rs_allocation
,则必须首先获取相应 Android 框架类的对象,然后调用该结构的set
方法以将内存绑定到 RenderScript 运行时。您不能在 RenderScript 运行时层直接操作这些结构。此限制不适用于包含指针的用户定义结构,因为它们首先不能导出到反射层类。如果您尝试声明包含指针的非静态全局结构,则会生成编译器错误。
RenderScript 也支持指针,但您必须在 Android 框架代码中显式分配内存。当您在.rs
文件中声明全局指针时,您通过相应的反射层类分配内存,并将该内存绑定到原生 RenderScript 层。您可以从 Android 框架层以及 RenderScript 层与该内存进行交互,这为您提供了在最合适的层修改变量的灵活性。
为 RenderScript 分配和绑定动态内存
要分配动态内存,您需要调用Script.FieldBase
类的构造函数,这是最常见的方法。另一种方法是手动创建Allocation
,这对于诸如基本类型指针之类的事情是必需的。为了简单起见,只要可用,您应该使用Script.FieldBase
类的构造函数。获取内存分配后,调用指针的反射bind
方法以将已分配的内存绑定到 RenderScript 运行时。
以下示例为基本类型指针intPointer
和指向结构的指针touchPoints
分配内存。它还将内存绑定到 RenderScript。
Kotlin
private lateinit var myRenderScript: RenderScript private lateinit var script: ScriptC_example private lateinit var resources: Resources public fun init(rs: RenderScript, res: Resources) { myRenderScript = rs resources = res // allocate memory for the struct pointer, calling the constructor val touchPoints = ScriptField_Point(myRenderScript, 2) // Create an element manually and allocate memory for the int pointer val intPointer: Allocation = Allocation.createSized(myRenderScript, Element.I32(myRenderScript), 2) // create an instance of the RenderScript, pointing it to the bytecode resource script = ScriptC_point(myRenderScript/*, resources, R.raw.example*/) // bind the struct and int pointers to the RenderScript script.bind_touchPoints(touchPoints) script.bind_intPointer(intPointer) ... }
Java
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 script = new ScriptC_example(myRenderScript, resources, R.raw.example); // bind the struct and int pointers to the RenderScript script.bind_touchPoints(touchPoints); script.bind_intPointer(intPointer); ... }
读写内存
您可以在 RenderScript 运行时和 Android 框架层读取和写入静态和动态分配的内存。
静态分配的内存在 RenderScript 运行时级别有一个单向通信限制。当 RenderScript 代码更改变量的值时,出于效率目的,它不会传回给 Android 框架层。从 Android 框架设置的最后一个值始终在调用get
方法期间返回。但是,当 Android 框架代码修改变量时,该更改可以自动传达给 RenderScript 运行时或稍后同步。如果您需要将数据从 RenderScript 运行时发送到 Android 框架层,您可以使用rsSendToClient()
函数来克服此限制。
在使用动态分配的内存时,如果使用其关联的指针修改了内存分配,则 RenderScript 运行时层中的任何更改都会传播回 Android 框架层。在 Android 框架层修改对象会立即将该更改传播回 RenderScript 运行时层。
读取和写入全局变量
读取和写入全局变量是一个简单的过程。您可以在 Android 框架级别使用访问器方法,或在 RenderScript 代码中直接设置它们。请记住,您在 RenderScript 代码中做出的任何更改都不会传播回 Android 框架层(请查看此处了解更多详细信息)。
例如,给定在名为rsfile.rs
的文件中声明的以下结构
typedef struct Point { int x; int y; } Point_t; Point_t point;
您可以像这样直接在rsfile.rs
中为结构分配值。这些值不会传播回 Android 框架级别
point.x = 1; point.y = 1;
您可以在 Android 框架层像这样为结构分配值。这些值会异步传播回 RenderScript 运行时级别
Kotlin
val script: ScriptC_rsfile = ... ... script._point = ScriptField_Point.Item().apply { x = 1 y = 1 }
Java
ScriptC_rsfile script; ... Item i = new ScriptField_Point.Item(); i.x = 1; i.y = 1; script.set_point(i);
您可以在 RenderScript 代码中像这样读取值
rsDebug("Printing out a Point", point.x, point.y);
您可以使用以下代码在 Android 框架层读取值。请记住,此代码仅在 Android 框架级别设置了值时才会返回值。如果您仅在 RenderScript 运行时级别设置了值,则会得到空指针异常
Kotlin
Log.i("TAGNAME", "Printing out a Point: ${mScript._point.x} ${mScript._point.y}") println("${point.x} ${point.y}")
Java
Log.i("TAGNAME", "Printing out a Point: " + script.get_point().x + " " + script.get_point().y); System.out.println(point.get_x() + " " + point.get_y());
读取和写入全局指针
假设已在 Android 框架级别分配了内存并绑定到 RenderScript 运行时,您可以通过使用该指针的get
和set
方法从 Android 框架级别读取和写入内存。在 RenderScript 运行时层,您可以像往常一样使用指针读取和写入内存,并且更改会传播回 Android 框架层,这与静态分配的内存不同。
例如,给定在名为rsfile.rs
的文件中指向struct
的以下指针
typedef struct Point { int x; int y; } Point_t; Point_t *point;
假设您已在 Android 框架层分配了内存,您可以像往常一样访问struct
中的值。您通过其指针变量对结构进行的任何更改都会自动提供给 Android 框架层
Kotlin
point[index].apply { x = 1 y = 1 }
Java
point[index].x = 1; point[index].y = 1;
您也可以在 Android 框架层读取和写入指针的值
Kotlin
val i = ScriptField_Point.Item().apply { x = 100 y = 100 } val p = ScriptField_Point(rs, 1).apply { set(i, 0, true) } script.bind_point(p) p.get_x(0) //read x and y from index 0 p.get_y(0)
Java
ScriptField_Point p = new ScriptField_Point(rs, 1); Item i = new ScriptField_Point.Item(); i.x=100; i.y = 100; p.set(i, 0, true); script.bind_point(p); p.get_x(0); //read x and y from index 0 p.get_y(0);
一旦内存已绑定,您就不必每次更改值时都重新将内存绑定到 RenderScript 运行时。