Renderscript基础篇
一、简介
RenderScript是Android平台上用于运行计算密集任务的框架。RenderScript主要是面向数据并行计算,当然,RenderScript中使用串行计算效率也很好。RenderScript是一种低级的高性能编程语言,用于3D渲染和处理密集型计算(3D播放等和关于CPU密集型的计算)。一直以来Android在绘图性能的表现一直差强人意,引入NDK之后才有所改善,而在Honeycomb中发布了RenderScript这一杀手级在Framework后,大大的增加了Android本地语言的执行能力和计算 RenderScript 是一种低级的高性能编程语言,用于3D渲染和处理密集型计算(3D播放等和关于CPU密集型的计算)。一直以来Android 在绘图性能的表现一直差强人意,引入NDK之后才有所改善,而在Honeycomb 中发布了RenderScript 这一杀手级在Framework 后,大大的增加了Android 本地语言的执行能力和计算能力。
二、Android Renderscript API 简介
1、Renderscript 类
This class provides access to a RenderScript context, which controls RenderScript initialization, resource management, and teardown. An instance of the RenderScript class must be created before any other RS objects can be created.
在使用Renderscript的其他类实例之前必须先创建Renderscript实例。
创建方法: public static RenderScript create (Context ctx) eg: RenderScript rs = RenderScript.create(getApplicationContext());
同样在使用完必须进行销毁。
销毁方法:public void destroy () eg:rs.destroy();
这里只说一下最常用的 ,其他方法请查阅API。
2、Allocation 类
This class provides the primary method through which data is passed to and from RenderScript kernels. An Allocation provides the backing store for a given Type
. An Allocation also contains a set of usage flags that denote how the Allocation could be used.
此类适用于进行内存分配的。在使用Renderscript 中的函数接口进行参数传递时必须将变量类型转化为Allocation类型。
转化方法:如下 根据需要创建。
eg:Allocation allIn = Allocation.createFromBitmap(rs, bitmap);
Allocation allOut = Allocation.createFromBitmap(rs, outBitmap);
3、ScriptIntrinsic 类
Base class for all Intrinsic scripts. An intrinsic a script which implements a pre-defined function. Intrinsics are provided to provide effecient implemtations of common operations. Not intended for direct use.
rs中 脚本的基类,用于脚本的定义、共同操作和实现。
4、ScriptIntrinsicBlend 类
Intrinsic kernels for blending two Allocation
objects.
用于对两个分配对象进行合成, 具体的合成效果根据不同的接口实现。 本质是 对于图片各像素点的运算。
在使用方法接口之前,必须的使用blend的静态方法去创建一个blend实例去调用个其他方法。
创建实例方法 create(RenderScript rs, Element e)
U8_4(RenderScript)
5、ScriptIntrinsicBlur 类
Intrinsic Gausian blur filter. Applies a gaussian blur of the specified radius to all elements of an allocation.
用于实现图像的高斯模糊,根据不同的模糊半径实现不同程度的模糊。
首先创建一个ScriptIntrinsicBlur实例 利用实例去调用不同的方法
实例创建: create(RenderScript rs, Element e)
三、 rs脚本
1、概述
一个RenderScript内核通常是驻留在
-
编译声明(#pragma version(1)),声明脚本文件所使用的内核语言版本,目前1是唯一的有效值。
-
编译声明(#pragma rs java_package_name(com.example.app)),声明脚本文件关联Java类的包名。请注意,你的.rs文件必须是应用程序包的一部分,而不是一个library项目。
-
一些可调用函数。可调用函数是一个单线程的RenderScript函数,你可以通过参数从java代码进行调用。这些往往对于初始设置或者更大处理管道的串行计算非常有用。
-
一些全局变量。全局脚本变量其实等同于C语言变量。你可以通过java代码访问这些全局脚本变量,这些变量往往是作为参数传递到RenderScript内核。
-
一些计算内核。一个内核是通过并行函数去处理每一个Allocation中的Element。
2、举例说明
首先,在代码目录下(即包目录下)创建rs文件,取名可以任意,我们新建一个hello.rs文件:
#pragma version(1) #pragma rs java_package_name(com.hc.renderscript) uchar4 __attribute__((kernel)) invert(uchar4 in) { uchar4 out = in; out.r =255- in.r; out.g = 255-in.g; out.b = 255-in.b; return out; }
(1) 逐行解读:
首先#pragma是给编译器看的,#pragma version(1)
是指版本号,目前只能选择1,没有更高的版本了。
#pragma rs java_package_name(com.hc.renderscript)
是告诉编译器,包的名称。因为每个rs文件都会自动生成对应的Java代码,比如,我们新建的hello.rs文件,会自动生成ScriptC_hello类,因此,我们需要在rs声明包的名称。
接下来比较重要的关键字__attribute__((kernel))
,它跟函数放在一起,用于声明这个函数是个RenderScript核心函数,而不是一个可调用的函数。什么意思呢?其实可以这样理解,就是这个函数不是个普通函数,是用于并行计算的函数。我们不能显式调用,它是RenderScript内部调用的函数。既然我们不能显式调用,那该怎么调用呢?
我们继续看到invert函数,这个函数有个uchar4
类型,不用想,肯定表示占用4个字节,每个字节表示的取值范围0~255。但是接下来的事情就很奇怪了,uchar4 in
中直接可以用in.r
、in.g
、in.b
分别取出rgb颜色。从RenderScript Basics Tutorial这篇文章可以知道,还可以通过x
、y
、z
、w
分别取出对应的第1、2、3、4个字节。也就是说,in.x
与in.r
都是一个意思.
(2)当我们创建一个rs脚本文件时,系统会自动生成一个相对应的同名Java类文件。
类文件中包含有对脚本中变量操作的接口。然后我们可以在我们的Java文件中创建脚本同名类文件实例去调用其中操纵脚本变量的方法接口。
(3)特点
脚本中的参数返回和传递类型是 Allocation形式。这个函数的形式参数及其类型,在RenderScript
内核当中,这是一个特殊的参数形式,这些参数会根据传递给内核启动的输入Allocation
填充进来。默认情况下,内核运行依赖于整个Allocation
和该Allocation
中一的每个Element
。
内核的返回类型,返回的值从内核自动写入输出Allocation
的适当位置。
内核可能一个input Allocation
,一个output Allocation
,或两者兼而有之。内核可能没有超过一个输入和一个输出。如果需要多个输入或输出,应该将这些对象绑定脚本文件当中的tors_allocation
全局变量,从内核访问或调用函数通过rsGetElementAt_type(),rsSetElementAt_type()
。 即 在脚本中创建 tors_allocation 变量,此变量会同步到脚本同名java类中,并在Java类中创建rsGetElementAt_type()和rsSetElementAt_type()接口去操纵tors_allocation全局变量。
#pragma version(1)
#pragma rs java_package_name(com.example.renderscript)
rs_allocation i;
rs_allocation j;
void root(const uchar4 *in, uchar4 *out, uint32_t x, uint32_t y) {
uchar4 usrc = rsGetElementAt_uchar4(i,x,y);
uchar4 usrc = rsGetElementAt_uchar4(j,x,y);
}
一个内核可以访问当前执行使用的坐标x,y,和z参数。这些参数都是可选的,但必须uint32_t
参数的类型。
一个可选的init()
函数。一个init()函数是一种特殊类型的调用函数,运行脚本时需要首先实例化。这使得一些计算发生在脚本自动创建的时候。
一些静态全局变量和函数的脚本。一个静态的全局变量相当于一个全局变量,唯一的区别是,静态的全局变量不能从java代码当中进行设置,而普通的全局变量可以。一个静态函数是一个标准的C函数,可以从任何内核调用或调用函数在脚本中但不暴露于Java API。如果一个全局变量或函数不需要从Java代码调用,强烈建议那些被声明为静态的。
在脚本中,您可以控制所需的水平的浮点精度。这是有用的,如果完整的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指令)。
(4)使用 脚本核
通过 ScriptC_hello mScript = new ScriptC_hello(rs); 去创建一个实例。 再用 mScript.forEach_invert(aIn, aOut); 去使用这个脚本。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSrcImageView = (ImageView) findViewById(R.id.src); mDstImageView = (ImageView) findViewById(R.id.dst); mInBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test); mOutBitmap = Bitmap.createBitmap(mInBitmap.getWidth(),mInBitmap.getHeight(), mInBitmap.getConfig()); mSrcImageView.setImageBitmap(mInBitmap); RenderScript rs = RenderScript.create(this); mScript = new ScriptC_hello(rs); aIn = Allocation.createFromBitmap(rs, mInBitmap); aOut = Allocation.createFromBitmap(rs, mInBitmap); mScript.forEach_invert(aIn, aOut); aOut.copyTo(mOutBitmap); mDstImageView.setImageBitmap(mOutBitmap); rs.destroy();
脚本文件当中的内核(实际是一个方法),在映射java类当中命名为forEach_kernelname()
,非内核的可调用方法同样可以使用invoke_functionname()
来进行调用 。
(5)从Allocation
对象当中复制出数据
在java代码当中,为了从一个Allocation
当中访问数据,必须将数据复制到一个java缓冲区当中,此时可以通过Allocation
的copy方法实现,这些函数将同步与异步启动必要的内核和函数。
(6)关闭RenderScript Context
RenderScript
上下文可以通过destroy()被摧毁或通过允许RenderScript Context
对象被垃圾收集,这将导致进一步使用RenderScriptContext
的任何对象在这种情况下抛出异常。