RenderScript概述
RenderScript(渲染脚本)提供用C语言(C99标准)编写的原生级高性能的计算API。Renderscript让你的应用程序有能力跨越所有可用的处理器内核来自动的平行的运行各种操作。它还提供了对不同类型的处理的支持,如CPU、GPU或DSP等。Renderscript对于图形处理、数学模型或其他任何需要大量的数学计算的应用程序都使用有用的。 另外,不需要编写代码你就能够访问所有这些功能来支持不同的架构或不同数量的处理器内核。也不需要针对不同的处理器类型来编译你的应用程序,因为Renderscript代码是在设备上运行时被编译的。
注意:早期的Renderscript版本包含了一个实验性的图形引擎组件。这个组件限制被Android4.1弃用了(rs_graphics.rsh中的大多数API和android.renderscript中对应的API)。如果你有使用Renderscript来渲染图形的应用程序,强烈推荐你把代码转换到另外的Android图形渲染选项。
Renderscript(渲染脚本)是Google全新加入的一个标志性功能,是一个新的API,旨在带来更高的性能的下级API给开发者,它带有高性能的
3D渲染和计算操作,这可以很大程度的提高他们的应用程序的性能。
Renderscript 已经被使用到了一些东西中,比如 Honeycomb的动态壁纸中以及复杂的 YouTube和
Book 应用中,这个内建的 Renderscript是非常出 {MOD}的,就像 Google说的“接近极端”,它将提高你的设备上执行本地代码的性能。从目前的情况来看这是非常不错的,与现存的
NDK 不同,最终产品将可以跨平台工作。
RenderScript系统结构
Renderscript运行时的操作是原生级别的,并且依然需要跟Android的虚拟机(VM)进行通信,因此创建一个Renderscript应用程序的方法不同于纯粹的虚拟机应用程序。除了你为程序编写的所需要的Renderscript代码之外,使用Renderscript的应用程序依然是一个运行在虚拟机(VM)中的应用程序,不管你使用它做什么,Renderscript依然保留它的平台独立性,因此不必编写多架构(例如:ARM v5、ARM v7、x86)的Renderscript代码。 Renderscript系统采用了一个控制和从属的架构,在这个架构中低级别的Renderscript运行时代码是由运行在虚拟机(VM)中高级别的Android来控制的。Android VM依然保留所有的对内存管理和分配给Renderscript运行时的绑定内存的控制,因此Renderscript的代码能够访问它。Android框架使用异步的方式调用Renderscript,并且调用会被放到消息队列中,直到它被处理。下图显示了Renderscript系统的结构:
在使用Renderscript时,在Renderscript运行时和Android框架代码之间有三个层次来确保API的通信:
1. Renderscript运行时API,它允许执行应用程序所需要的计算。
2. 反射层API是一组从Renderscript运行代码中反射出来的类。它是围绕Renderscript代码的一个基本的封装,这个封装允许Android框架和Renderscript运行时进行交互。Android编译工具在编译期间会自动的生成这个层次的类。这些类跟NDK代码一样不需要编写JNI代码。
3. Android框架层,它调用反射层来访问Renderscript运行时。
Renderscript的这种结构的主要优点是:
1. 便捷:Renderscript被设计层可运行在不同处理器(CPU、GPU和DSP的实例)架构的很多设备上。它所支持的所有这些架构,都不是针对每个特定设备的,因为它的代码会在运行时在设备上被编译和缓存。
2. 高效:Renderscript通过跨越设备上的过个内核,用并行的方式,提供了高性能的计算API。
3. 易用:Renderscript在可能的情况下,简化了开发,如取消了JNI代码。
主要缺点是:
1. 开发的复杂性:Renderscript引入了一组新的需要你学习的API;
2. 调试的可见性:Renderscript可能在主CPU以外的处理器(如GPU)上执行(后续的发布计划中),因此如果发生这种事情,调试会变的更加困难。
有关这些层次是如何在一起工作的,请看“高级Renderscript”(http://developer.android.com/guide/topics/renderscript/advanced.html)
创建Renderscript
Renderscript扩大了设备上可用的处理器内核的范围。这种能力是通过名叫rsForEach()(或者是Android框架级别下的forEach_root()方法)方法来获得的。它会自动的区分访问设备设备上可用的处理器内核的工作。目前,Renderscript只能利用CPU内核的优势,但是在将来,它们会能够运行在其他类型的处理器上,如GPU和DSP等。
实现一个Renderscript要涉及创建一个包含Renderscript代码的.rs文件和在Android框架级别下用forEach_root()方法调用该文件(或者是在Renderscript级别下用rsForEach()函数调用该文件)。下图介绍了如何建立一个典型的Renderscript:
RenderScript的使用
Renderscript是android平台上进行高性能计算的框架。Renderscript主要面向并行计算,虽然它对计算密集型工作也是有益的。Renderscript在运行时将在设备上可用的处理器间平衡负载,比如多核CPU,GPU或者DSP,它让你专注于算法而不是平衡负载。RenderScript对图像处理,计算摄影学,计算机视觉方面的应用非常有用。
在开始学习Renderscript前,你应该理解两个主要概念:
1 高性能的计算核心由支持C99协议的语言书写。(也就是C语言)
2 一套java API 用来管理Renderscript资源,并控制核心计算的执行。
写Renderscript核心
一个Renderscript核心一般写在.rs文件中,在[工程目录]/src/ 目录下;每一个.rs文件成为一个脚本。每一个脚本包含一系列的内核,函数,变量。通常包含
1 一个编译声明Renderscript核心语言的版本(#pragma version(1))。
目前可用值只有1.
2 一个编译声明这个脚本对应的java类的包名
(#pragma rs java_package_name(com.example.app)).
3 一些可调用的函数。
一个可调用的函数是一个单线程的Renderscript函数,你可以再java代码中调用它并传递任意多的参数。它通常用在较大的处理管道里初始化设置或者一系列计算。
4 一些全局变量。
一个Renderscript全局变量等价于C语言中的一个全局变量。你可以再java代码中访问Renderscript全局变量,它们通常被用来向Renderscript核心传递参数。
5 一些计算核心。
一个计算核心是在Allocation里的Element间执行的平行的函数。一个简单的核心如下:
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核心而不是可调用函数。第二个特性是 in参数和它的类型,在Renderscript核心里第一个参数会根据传入内核平台的Allocation被自动填入。默认的,核心将在每一个Element执行一次时在整个Allocation中运行(???不懂 译者注)。 第三个注意点是核心的返回类型,这个返回值将被自动的写入到输出Allocation的适当的位置。Renderscript会在运行时确保输入和输出的Allocation的Element类型互相匹配;如果不匹配,会抛出异常。
一个核心可能有一个输入Allocation或/和一个输出Allocation。也可能有多个输入输出Allocation。如果需要多个输入输出Allocation,这些对象应该同re_allocations脚本全局变量绑定,并且通过rsGetElementAt_type() / rsSetElementAt_type() 在核心或可调用函数中访问。
一个核心可以使用x,y和z参数来访问当前执行的坐标。这些参数是可选的,但是类型必须是uint32_t。
6 可选的 init() 函数。
Init() 韩式是一个特殊的可调用函数,它在脚本初始化时执行。
7 一些静态全局变量和函数。
静态的全局变量与普通的全局变量不同的地方是它不能从java代码中设定。静态函数是一个标准的C函数,可以再核心和脚本中的可调用方法中调用,但是不能被java API访问。如果全局变量或者函数不需要在java代码中使用,强烈建议声明成静态的。
设置浮点精度
你可以在脚本里控制浮点数的精度。这样做在不要求使用IEEE 754-2008 标准(默认使用)时是很有用的。下面的编译指示可以设置不同的浮点精度:
1 #pragma rs_fp_full (不指定时默认的设置):使用IEEE 754-2008 标准的精度。
2 #pragma rs_fp_relaxed 不严格的使用IEEE 754-2008 的标准,允许少量的精度误差。
3 #pragma rs_fp_imprecise 不需要严格的精度要求,允许#pragma rs_fp_relaxed下的所有情况,还包括:
-0.0会返回+0.0
INF 和 NAN的操作部明确
….(不懂。。。译者注)
大多数应用可以使用#pragma rs_fp_relaxed。
访问Renderscript API
在android应用里使用Renderscript有两种方式
1 android.renderscript 这个包在运行Android3.0(API level 11)的设备上可用,这是原始的API,目前没有被更新。
2 android.support.v8.renderscript 这个包可以通过共享库在运行Android2.2(API level 8)或以上版本的设备上使用。
强烈建议使用第二种方式,因为它包含了最新的更新,并且支持更多的设备。
使用Renderscript共享库API
为了使用共享库的Renderscript API,你需要安装你的开发环境。你需要下面的SDK工具:
- Android SDK Tools revision 22.2 or higher
- Android SDK Build-tools revision 18.1.0 or higher
在eclipse工程中使用共享库,你需要:
1 安装需要的SDK和编译工具
2打开工程根目录下的project.properties文件
3 添加下面的内容
renderscript.target=18
renderscript.support.mode=true
sdk.buildtools=18.1.0
4 在类中导入共享库的类
import android.support.v8.renderscript.*;
在java代码中使用Renderscript
在java代码中使用Renderscript依赖android.renderscript 或者android.support.v8.renderscript中的API。大部分程序遵循如下的使用方式:
1 初始化Renderscript context。Renderscript context通过create(Context)创建,它能保证Renderscript能被使用,并且提供了一个对象来控制所有后面创建的Renderscript对象的生命周期。创建这个context可能是一个耗时的操作,因为可能要在硬件的不同位置创建资源;通常,一个程序同时只能有一个Renderscript context。
2 创建至少一个Allocation来传递给脚本。一个Allocation是一个Renderscript对象,它存储了固定数量的数据。脚本里的核心将Allocation对象作为输入和输出,核心可以在绑定脚本全局变量时通过rsGetElementAt_type() 和rsSetElementType_type()来访问Allocation对象。Allocation对象可以通过数组从java代码传递给Renderscript代码,反之亦然。Allocation对象通常用createTyped(RenderScript,
Type) 或者 createFromBitmap(RenderScript, Bitmap)创建。
3 创建你所需的任何脚本。有两种脚本可被创建:
ScriptC : 这是上面”写Renderscript脚本”部分介绍的用户自定义脚本。Renderscript编译器会为每一个脚本创建一个java类,可以方便从java代码中访问脚本;这个类会被命名成ScriptC_filename。比如:如果上面的核心位于inver.rs中,并且一个Renderscript context已经被创建,在java代码中初始化脚本的代码如下:
ScriptC_invert invert = new ScriptC_invert(mRenderScript);
ScriptIntrinsic : 这是嵌入在Renderscript核心中的通用操作,比如高斯模糊,卷积,图像混合。更多信息请参看 ScriptInstrinsic.
4 在Allocation中的数据。除了android.renderscript创建的Allocation以外,一个空的Allocation将被创建,填充一个Allocation,请使用Allocation的copy方法。
5 设置必要的脚步全局变量。全局变量可以使用ScriptC_filename里的set_globalname来设置。例如,要想设置一个叫elements的整形变量,使用java方法set_elements(int)。Renderscript对象也可以在核心中设置,例如,叫做lookup的rs_allocation变量可以通过set_lookup(Allocation)函数设置。
6加载适当的核心。使用ScriptC_filename中的forEach_kernelname()函数可以加载一个核心。加载过程是异步的,加载的顺序和启动的顺序是一致的。根据传入内核的参数的不同,这个函数会携带一个或两个Allocation。默认的,一个核心会在整个输入输出Allocation中执行;为了在一系列Allocation中执行,给forEach函数的最后一个参数传递恰当的Script.LaunchOptions。可调用函数可以使用ScriptC_filename中的invoke_functionname调用。
7 从Allocation对象中拷贝数据。为了在java代码中访问Allocation中的数据,需要使用Allocation的copy方法拷贝到java缓冲区中。这些方法会必要的时候在异步内核和函数调用间做同步。
8 销毁Renderscript context。可以使用destroy()方法销毁Renderscript context,或者允许Renderscript context对象被垃圾回收。这样使用这个context中的任何对象将抛出异常。
RenderScript简单示例
示例一
下面介绍如何创建一个简单的Renderscript,并且要在一个Android应用程序中使用它。这个例子使用了SDK开发指南中提供的HelloCompute Renderscript示例。 创建Renderscript文件
Renderscript代码要保留在
/src/目录中的*.rs和*.rsh文件中。代码中包含了计算的逻辑和所有必要的变量和指针的声明。通常,每个*.rs文件要包含下列项目:
1. 编译指示声明(#pragma rs java_package_name(package.name)),它声明了该Renderscript反射所对应的*.java类名;
2. 编译指示声明(#pragma version(1)),它声明了你要使用的Renderscript的版本(目前只能是1);
3. 一个名叫root()的主函数,该root()函数被rsForEach函数调用,并允许它调用Renderscript代码和在有效的多内核中执行。root()函数必须返回void并且要接收下列参数:
A. 分配给Renderscript的输入和输出使用的内存的指针。在Android3.2(API level 13)平台以前的版本中同时需要这两个指针。Android4.0(API Level 14)以后只分配其中之一就可以了。
B. 下列参数是可选的,但是如果使用它们就必须同时提供它们:
除了必要的内存分配之外,一个Renderscript执行计算所可能需要的用户定义数据的指针,它能够指向一个简单的原始类型的数据,也可以指向一个复杂的结构体。 用户定义数据的大小。
4. 一个可选的init()方法。这个方法允许再root()方法运行之前做一些初始化的工作,如初始化变量等。这个函数运行一次,并且在Renderscript启动时,Renderscript中其他工作被执行之前,该方法会被自动的调用。
5. 在Renderscript代码中要使用的任何变量、指针和结构体(如果需要,能够在*.rsh文件中声明)。
下列代码显示了mono.rs文件是如何实现的:
#pragma version(1)
#pragma rs java_package_name(com.example.android.rs.hellocompute)
//multipliers to convert a RGB colors to black and white const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
void root(const uchar4 *v_in, uchar4 *v_out) { //unpack a color to a float4
float4 f4 = rsUnpackColor8888(*v_in);
//take the dot product of the color and the multiplier float3 mono = dot(f4.rgb, gMonoMult); //repack the float to a color
*v_out = rsPackColorTo8888(mono); }
调用Renderscript代码
你能够通过由实例化的类(ScriptC_script_name)来创建一个Renderscript对象从Android框架代码中调用Renderscript。这个类包含了一个forEach_root()方法,它会调用rsForeach()方法。你能够传递给它与Renderscript运行时级别调用相同的参数。这种技术允许你的Android应用程序把高精度的数学计算转交给Renderscript。 在Android框架层次调用Renderscript的方法:
1. 在你的Android框架代码中分配Renderscript所需要的内存。对于Androi3.2(API Level 13)
以前的版本,需要分配输入和输出内存。Android4.0(API Level 14)以后的平台版本只需要分配其中之一的内存或两个都分配。 2. 创建ScriptC_scritp_name类的一个实例。
3. 调用forEach_root()方法,并传入分配的内存、Renderscript和其他的可选的用户定义的数据。输出内存中将会包含Renderscript的输出结果。
以下示例来自HellCompute示例,它处理一张位图,并输出它的黑白版本。CreateScript()方法安装前面描述的步骤来执行。这个方法调用Renderscript对象,执行mono.rs脚本,把最终的处理结果位图保存在输出的内存中,然后把处理后的位图显示在屏幕上:
package com.example.android.rs.hellocompute;
import android.app.Activity; import android.os.Bundle;
import android.graphics.BitmapFactory; import android.graphics.Bitmap;
import android.renderscript.RenderScript; import android.renderscript.Allocation; import android.widget.ImageView;
public class HelloCompute extends Activity { private Bitmap mBitmapIn; private Bitmap mBitmapOut;
private RenderScript mRS;
private Allocation mInAllocation; private Allocation mOutAllocation;
private ScriptC_mono mScript;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
mBitmapIn = loadBitmap(R.drawable.data);
mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(),
mBitmapIn.getConfig());
ImageView in = (ImageView) findViewById(R.id.displayin); in.setImageBitmap(mBitmapIn);
ImageView out = (ImageView) findViewById(R.id.displayout); out.setImageBitmap(mBitmapOut);
createScript(); }
private void createScript() {
mRS = RenderScript.create(this);
mInAllocation = Allocation.createFromBitmap(mRS, mBitmapIn, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
mOutAllocation = Allocation.createTyped(mRS, mInAllocation.getType());
mScript = new ScriptC_mono(mRS, getResources(), R.raw.mono); mScript.forEach_root(mInAllocation, mOutAllocation); mOutAllocation.copyTo(mBitmapOut); }
private Bitmap loadBitmap(int resource) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeResource(getResources(), resource, options); } }
以下是从另一个Renderscript文件中调用Renderscript的方法:
1. 在Android框架代码中分配由Renderscript所需要的内存。对于Android3.2平台和之前
的版本,要同时分配输入和输出内存。Android4.0平台版本之后可以根据需要来分配输入和输出内存。
2. 调用rsForEach()方法,并传入分配的内存和可选的用户定义的数据。输出内存中会包含
Renderscript的输出结果。
rs_script script;
rs_allocation in_allocation; rs_allocation out_allocation; UserData_t data; ...
rsForEach(script, in_allocation, out_allocation, &data, sizeof(data));
在这个例子中,假定在Android框架层脚本和内存已经被分配和绑定,并且UserData_t是一个被事前声明的结构。把这个结构的指针和它的大小传递一个rsForEach()方法,这是一个可选的参数。如果你的Renderscript需要一些输入内存中之外的信息,就可以使用这个参数。
示例二
转自http://bbs.51cto.com/thread-1063602-1.html###
下面以Google提供的api demo HelloWorld为例,进行说明。这个程序的效果是根据用户在界面上的点击位置,绘制出"Hello World!" 字符串
首先,看下它的文件结构:
从上面可以看出,主要包含4个文件,那么我们就来一个个分析这4个文件。
1. helloworld.rs 自己的注释会写在里面
01
#pragma version(1)
02
03
04
05
06
#pragma rs java_package_name(com.example.android.rs.helloworld)
07
08
09
10
#include "rs_graphics.rsh"
11
12
13
14
15
int
gTouchX;
16
int
gTouchY;
17
18
19
20
void
init() {
21
gTouchX = 50.0f;
22
gTouchY = 50.0f;
23
}
24
25
26
int
root(
int
launchID) {
27
28
29
30
rsgClearColor(0.0f, 0.0f, 0.0f, 0.0f);
31
32
33
rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
34
35
36
37
rsgDrawText(
"Hello World!"
, gTouchX, gTouchY);
38
39
40
41
42
return
20;
43
}
ok.. 我们编写这个文件之后,就会在gen目录下,生成一个ScriptC_helloworld.java文件,如:
2. 编写HelloWorldRS.java文件
01
package
com.example.android.rs.helloworld;
02
03
import
android.content.res.Resources;
04
import
android.renderscript.*;
05
06
07
public
class
HelloWorldRS {
08
private
Resources mRes;
09
private
RenderScriptGL mRS;
10
11
12
private
ScriptC_helloworld mScript;
13
14
public
HelloWorldRS() {
15
}
16
17
18
19
public
void
init(RenderScriptGL rs, Resources res) {
20
mRS = rs;
21
mRes = res;
22
initRS();
23
}
24
25
26
public
void
onActionDown(
int
x,
int
y) {
27
mScript.set_gTouchX(x);
28
mScript.set_gTouchY(y);
29
}
30
31
private
void
initRS() {
32
mScript =
new
ScriptC_helloworld(mRS, mRes, R.raw.helloworld);
33
mRS.bindRootScript(mScript);
34
}
35
}
第2步的话,我觉得我们可以简单的理解成对第1步生成ScriptC_helloworld.java对象的封装
3. HelloWorldView.java
我们Android里面显示的话,肯定是View对象,RenderScript为我们编写了一个类RSSurfaceView.java,然后我们自己的View只要扩展自这个RSSurfaceView.java即可。
01
package
com.example.android.rs.helloworld;
02
03
import
android.renderscript.RSSurfaceView;
04
import
android.renderscript.RenderScriptGL;
05
06
import
android.content.Context;
07
import
android.view.MotionEvent;
08
09
public
class
HelloWorldView
extends
RSSurfaceView {
10
11
private
RenderScriptGL mRS;
12
13
private
HelloWorldRS mRender;
14
15
public
HelloWorldView(Context context) {
16
super
(context);
17
ensureRenderScript();
18
}
19
20
private
void
ensureRenderScript() {
21
if
(mRS ==
null
) {
22
23
24
RenderScriptGL.SurfaceConfig sc =
new
RenderScriptGL.SurfaceConfig();
25
mRS = createRenderScriptGL(sc);
26
27
mRender =
new
HelloWorldRS();
28
mRender.init(mRS, getResources());
29
}
30
}
31
33
protected
void
onAttachedToWindow() {
34
super
.onAttachedToWindow();
35
ensureRenderScript();
36
}
37
39
protected
void
onDetachedFromWindow() {
40
41
mRender =
null
;
42
if
(mRS !=
null
) {
43
mRS =
null
;
44
destroyRenderScriptGL();
45
}
46
}
47
48
50
public
boolean
onTouchEvent(MotionEvent ev) {
51
52
if
(ev.getAction() == MotionEvent.ACTION_DOWN) {
53
mRender.onActionDown((
int
)ev.getX(), (
int
)ev.getY());
54
return
true
;
55
}
56
57
return
false
;
58
}
59
}
这里,主要是定义了一个自己的View,然后重写了一些生命周期方法,最后是onTouchEvent方法,当用户点击屏幕的时候,把点击的x,y值告诉helloworld.rs的x,y值
第4步
到了最后一步了,当然是定义我们的Activity
01
package
com.example.android.rs.helloworld;
02
03
import
android.app.Activity;
04
import
android.os.Bundle;
05
06
07
public
class
HelloWorld
extends
Activity {
08
09
10
private
HelloWorldView mView;
11
13
public
void
onCreate(Bundle icicle) {
14
super
.onCreate(icicle);
15
16
17
mView =
new
HelloWorldView(
this
);
18
setContentView(mView);
19
}
20
22
protected
void
onResume() {
23
24
25
super
.onResume();
26
mView.resume();
27
}
28
30
protected
void
onPause() {
31
32
33
super
.onPause();
34
mView.pause();
35
}
36
37
}
这一步也是非常好理解,把View设置成Activity显示的界面,然后就是一些生命周期方法。
参考官网
http://developer.android.com/guide/topics/renderscript/index.html
http://developer.android.com/guide/topics/renderscript/compute.html
http://developer.android.com/guide/topics/renderscript/advanced.html
http://developer.android.com/guide/topics/renderscript/reference.html