Apr 1

RenderScript初探

Lrdcq , 2016/04/01 14:14 , 程序 , 閱讀(6006) , Via 本站原創
RenderScript是安卓3.1引入,安卓4.0全面推开,且通过兼容包最低可兼容到安卓2.2的一种脚本语言工具。它虽然叫script,但它是一门以c99位基础的类c语言,看起来会觉得有点像GLSL(opengl着色器语言,事实上功能上也很像),并且它确实和安卓的jvm和其它脚本解释器无关,而是通过编译为llvm运行在本地代码上。
它的名字虽然叫render(渲染)script,但它的实际功能缺并不仅限于渲染或者绘制相关的东西,而是一个解决数据密集型计算的通用解决方案。就现在的api来看,renderscript的api的功能包括:基本数学,向量,矩阵,四元 / 多线程,gpu调用,时间 / 输入输出对象;曾经还有图形绘制的api,不过在安卓api23已经被弃用了,当然现在也就不推荐使用了。可以看到,api主要实现的功能是数学,图形学运算(类似于GLSL)与高效集成计算构架(类似于CUDA,openCL)。目标当然一目了然。

1.Show Code
一段最简单的renderscript如下:
#pragma version(1)

#pragma rs java_package_name(com.example)



#pragma rs_fp_relaxed



float rate = 0.0f;



uchar4 __attribute__((kernel)) change(uchar4 in)
{

    float4 f4 = rsUnpackColor8888(in);

    float3 result = f4.rgb;



    float out = (result.r+result.g+result.b)/3.0;
    
    out += (1-out)*0.4;



    result.r=result.r*(1.0-rate) + out *rate;
    
    result.g=result.g*(1.0-rate) + out *rate;
    
    result.b=result.b*(1.0-rate) + out *rate;
    

    return rsPackColorTo8888(result);

 }

如果有图形学开发经验写过shader脚本的同学,肯定一样就能看出来这段代码在做什么——是不是看起来像一段片断着色器的代码呢?这段代码前3行定义了程序的基本属性。然后定义了一个全局变量rate,显然不用说,这个变量一定是在什么地方可以配置或者变化的。然后,下面编写了一个函数change,入参和出参虽然是uchar4,不过第一行实现和最后一行实现告诉我们了,我们是在操作一个color颜色——那这段代码显然,就是输入一个颜色,通过一段操作,最后输出一个颜色——这就是片断着色器所做的事儿。然后具体的颜色处理逻辑,是将这个颜色处理为一个比较泛白的灰度色,并且通过rate(0~1)控制从彩色到灰度色的过度。

2.使用
那么写了这么一段renderscript脚本,我们如何使用它呢。这时候我们回到java层。在Android Studio里,我们首先要对项目进行一些配置,把刚才写的这个rs文件,假设是helloworld.rs文件,放置到src/rs/文件夹中,只有放在这儿Android Studio才会认为它是一个renderscript脚本。其次配置gradle,要在安卓的defaultConfig中添加renderscriptTargetApi;如果使用了支持包,还需要将renderscriptSupportModeEnabled设为true。

然后rebuild一下项目,什么也没发生?当然,其实发生的事情是,Android Studio自动为你的helloworld.rs生成了对应的java类进行操作,叫ScriptC_helloworld。那么在java中初始化这个类肯定是首要需要做的了。
RenderScript rs = RenderScript.create(this);

ScriptC_helloworld script = new ScriptC_helloworld(rs);


我们通过当前的上下文初始化出,renderscript的上下文,然后通过这个上下文实例化我们的这段script。这样我们这段脚本就在java里操作了。但到底如何操作呢?看看生成的这个类的方法就知道了:
void set_rate(float v);
float get_rate();
void forEach_change(Allocation ain, Allocation aout);
void forEach_change(Allocation ain, Allocation aout, Script.LaunchOptions sc);

诶,可以获取或者设置我们在脚本中rate变量,并且看起来可以调用代码中的change函数诶。那么这个函数的入参Allocation又是什么鬼呢?

哦,原来可以把一个位图给转换进去。那我们的操作过程就明晰了。我们手上的是一张位图,Bitmap,通过Allocation给传入我们的renderscript中进行处理,然后同样使用一个Allocation把数据给传出来就可以了。所以如果我们的业务是要把一个view变成灰色并冻结,我们写出的使用代码看起来是这样的:

RenderScript rs = RenderScript.create(this);

ScriptC_hellohome script = new ScriptC_hellohome(rs);



Bitmap inputBitmap = getBitmapFromView(main);

Allocation inputAllocation = Allocation.createFromBitmap(rs, inputBitmap);

Bitmap outputBimap = Bitmap.createBitmap(
  inputBitmap.getWidth(), inputBitmap.getHeight(), inputBitmap.getConfig()
);

Allocation outputAllocation = Allocation.createFromBitmap(rs, outputBimap);



script.set_rate(0.5F);

script.forEach_change(inputAllocation, outputAllocation);


outputAllocation.copyTo(outputBimap);


surfaceView.setImageBitmap(outputBimap);

当然,这里直接设置了rate为0.5即50%,事实上在实际效果时这是跟随动画变化的。

3.拓展功能
也就是说,我们可以很方便的用renderscript处理复杂的图片效果了。但是有些常见的复杂效果,比如模糊化,那renderscript代码可难写了,想想就烦。怎么办呢?其实从4.2开始,安卓就自带了好一些常用的图像处理脚本,包括这些:

嗯,估计这里边最常用的是ScriptIntrinsicBlur了,用这个做高斯模糊,简直轻松加愉快嘛。同时,还有一个叫ScriptGroup的类,可以把自带的script和自己的script之类的打包在一起,就像流水线一样工作,这样处理图片效果,当然没问题咯。

4.性能
说到图片处理,密集型运算,大家首先考虑的当然是性能/开发难度,还有手机上关系的能耗,三个问题与关系了。

首先从原理上看,renderscript和java层的代码,和ndk的代码还是有一些不同的。

- java代码在开发者手中变为jvm字节码,在手机上变为本地代码运行。
- ndk代码直接在开发者手中交叉编译为本地代码,在到手机上运行。
- renderscript在开发者手中变为llvm字节码,在手机上变为本地代码再运行。

llvm字节码是什么呢,是现在在c/cpp类语言中广泛使用的中间层vm,苹果与微软的东西都在广泛使用llvm,所以性能肯定是杠杠的。因此可以认为,renderscript本身的运行性能远高于jvm但稍弱于ndk开发的代码。当然,从开发难度于复杂度上来讲,是反过来。

然后针对图像运算的性能来对比呢?仔细罗列方案,我们有这么一些方法:
- java canvas绘制 CPU
- java opengl es GPU
- renderscript 普通处理方法 CPU
- renderscript opencl(伪)托管 GPU
- ndk 绘画sdk CPU
- ndk opengl es GPU
首先考虑的一点,是否需要使用GPU,毕竟在手机上随随便便开gpu是一件很费电发热不讨好的事儿。但gpu进行图像运算的优势也是非常屌的。在demo中对图像进行黑白化在进行模糊运算,这一个过程需要80-100ms左右,对于界面动画要求,就算是30帧的fps,也是完全没法满足的。也许去掉模糊化会达到要求,但是我们使用renderscript很大一个需求不就是为了实现模糊么。相同的事情,对整个界面(1080p)的图像进行高斯模糊,canvas实现的伪高斯模糊(fastblur)需要接近200ms,而gpuimage(一个非常非常火的第三方界面组件)中实现的blur远低于30ms。另外还要考虑到多线程运算图像多级缓冲等延迟显示方案,可以实现更高的效率。

因此,简单的说,如果需要实现动画,gpu进行运算是性能最佳的解决方案,在java层调用opengl进行运算就可以了,否则,renderscript性能约等于ndk在cpu进行计算的性能,且开发效率更高。

5.结论
RenderScript出来这么旧了,并没有广泛使用起来,确实还是有一定问题的。RenderScript适用于大数据计算与静态图像处理,主要运行在cpu中,性能与ndk相近,但是开发难度远比ndk低。

结论:如果有什么,对运算强度要求极高,对实时性要求没那么高的计算,想使用ndk进行开发,又觉得ndk开发太麻烦了的话,就使用RenderScript吧。
logo