[譯]原文鏈接
RenderScript是運(yùn)行在Android系統(tǒng)上的一個(gè)高性能密集計(jì)算框架。RenderScript主要面向并行數(shù)據(jù)的計(jì)算,當(dāng)然也可以使串行數(shù)據(jù)計(jì)算的負(fù)載受益。RenderScript運(yùn)行時(shí)會(huì)在一臺(tái)設(shè)備所有可用處理器上并行工作绩鸣,比如多核的CPU,GPU或者DSP,這使得你可以專注于算法虏冻,而不是任務(wù)調(diào)度或者負(fù)載平衡槽棍。RenderScript在應(yīng)用程序執(zhí)行圖像處理墓怀,計(jì)算攝影或者計(jì)算機(jī)視覺(jué)等方面都非常有用汽纠。
為了開(kāi)始RenderScript的學(xué)習(xí),這有兩個(gè)很重要的概念是你需要了解的:
- 高性能的計(jì)算內(nèi)核是通過(guò)衍生自C99的語(yǔ)言編寫(xiě)的傀履。
- 有專用的Java API用于管理RenderScript的資源生命周期以及控制內(nèi)核的執(zhí)行虱朵。
編寫(xiě)一個(gè)RenderScript內(nèi)核
一個(gè)RenderScript內(nèi)核通常是駐留在<project_root>/src/目錄下的一個(gè).rs文件。每個(gè).rs文件被稱之為一個(gè)腳本钓账。每個(gè)腳本文件包含了它自己的一套內(nèi)核碴犬,函數(shù)和變量。一個(gè)腳本文件可以包含以下內(nèi)容:
編譯聲明(#pragma version(1))梆暮,聲明腳本文件所使用的內(nèi)核語(yǔ)言版本服协,目前1是唯一的有效值。
編譯聲明(#pragma rs java_package_name(com.example.app))啦粹,聲明腳本文件關(guān)聯(lián)Java類的包名偿荷。請(qǐng)注意,你的.rs文件必須是應(yīng)用程序包的一部分卖陵,而不是一個(gè)library項(xiàng)目遭顶。
一些可調(diào)用函數(shù)±崮瑁可調(diào)用函數(shù)是一個(gè)單線程的RenderScript函數(shù)棒旗,你可以通過(guò)參數(shù)從java代碼進(jìn)行調(diào)用。這些往往對(duì)于初始設(shè)置或者更大處理管道的串行計(jì)算非常有用撩荣。
一些全局變量铣揉。全局腳本變量其實(shí)等同于C語(yǔ)言變量。你可以通過(guò)java代碼訪問(wèn)這些全局腳本變量餐曹,這些變量往往是作為參數(shù)傳遞到RenderScript內(nèi)核逛拱。
-
一些計(jì)算內(nèi)核。一個(gè)內(nèi)核是通過(guò)并行函數(shù)去處理每一個(gè)Allocation中的Element台猴。
一個(gè)簡(jiǎn)單的內(nèi)核可能看起來(lái)就像下面這樣: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; }
在很多方面朽合,這等同于一個(gè)標(biāo)準(zhǔn)的C函數(shù)。第一個(gè)顯著的特點(diǎn)是_attribute_((kernel))適用于函數(shù)原型饱狂。它表示該函數(shù)是一個(gè)RenderScript內(nèi)核而不是一個(gè)可調(diào)用的功能曹步。下一個(gè)特點(diǎn)是in參數(shù)和它的類型。在RenderScript內(nèi)核里休讳,這是一個(gè)特別的參數(shù)讲婚,它會(huì)根據(jù)輸入的Allocation自動(dòng)填充,傳遞給內(nèi)核啟動(dòng)俊柔。內(nèi)核會(huì)執(zhí)行每一個(gè)Allocation中的Element筹麸,因此默認(rèn)情況下活合,內(nèi)核會(huì)在整個(gè)Allocation中運(yùn)行。第三個(gè)顯著的特點(diǎn)是內(nèi)核的返回類型物赶。從內(nèi)核返回的值會(huì)自動(dòng)寫(xiě)入到輸出Allocation的適當(dāng)位置白指。RenderScript會(huì)在運(yùn)行時(shí)進(jìn)行檢查,確保Allocation中輸入輸出的Element類型是和內(nèi)核中的原型相匹配的块差。如果它們不匹配侵续,將會(huì)拋出異常。
一個(gè)內(nèi)核可能有一個(gè)輸入的Allocation憨闰,一個(gè)輸出的Allocation,或者兩個(gè)都有需五。一個(gè)內(nèi)核可能不會(huì)有超過(guò)一個(gè)的輸入或輸出Allocation鹉动。如果需要超過(guò)一個(gè)的輸入和輸出,那這些對(duì)象需要被綁定到rs_allocation的全局腳本宏邮,通過(guò)rsGetElementAt_type()或者rsSetElementAt_type()進(jìn)行內(nèi)核訪問(wèn)或函數(shù)調(diào)用泽示。
一個(gè)內(nèi)核可以通過(guò)使用x,y蜜氨,z參數(shù)來(lái)訪問(wèn)當(dāng)前執(zhí)行的坐標(biāo)械筛。這些參數(shù)是可選的,但是坐標(biāo)參數(shù)的類型必須是uint32_t飒炎。
- 一個(gè)可選的init()函數(shù)埋哟。init()函數(shù)是一個(gè)特殊的功能函數(shù),它在腳本實(shí)例化完成后運(yùn)行郎汪。這使得一些計(jì)算可以在腳本創(chuàng)建后自動(dòng)運(yùn)行赤赊。
- 一些靜態(tài)全局變量和函數(shù)。一個(gè)靜態(tài)全局變量相當(dāng)于全局變量只不過(guò)不能通過(guò)java代碼進(jìn)行設(shè)置煞赢。一個(gè)靜態(tài)函數(shù)相當(dāng)于一個(gè)標(biāo)準(zhǔn)的C函數(shù)抛计,能夠被內(nèi)核或其他腳本中的功能函數(shù)進(jìn)行調(diào)用,但是不暴露給Java API照筑。如果一個(gè)腳本變量或者函數(shù)不需要被Java代碼調(diào)用吹截,那強(qiáng)烈建議將其聲明為靜態(tài)的(static)。
設(shè)置浮點(diǎn)數(shù)精度
你可以通過(guò)腳本控制浮點(diǎn)數(shù)的精度級(jí)別凝危。如果不需要完整的IEEE 754-2008標(biāo)準(zhǔn)(默認(rèn)情況下使用)波俄,這非常有用。下面的這些聲明可以為浮點(diǎn)數(shù)精度設(shè)置不同的級(jí)別:
- #pragma rs_fp_full(沒(méi)有指定會(huì)默認(rèn)聲明):針對(duì)需要浮點(diǎn)數(shù)精度為IEEE 754-2008標(biāo)準(zhǔn)的應(yīng)用媒抠。
- #pragma rs_fp_relaxed :針對(duì)不需要嚴(yán)格遵循IEEE 754-2008標(biāo)準(zhǔn)弟断,并且可以接受精度要求不高的應(yīng)用。This mode enables flush-to-zero for denorms and round-towards-zero.
- #pragma rs_fp_imprecise : 針對(duì)沒(méi)有嚴(yán)格精度要求的應(yīng)用趴生。這個(gè)模式允許在rs_fp_relaxed模式中的以下內(nèi)容:
- -0.0的操作結(jié)果可以通過(guò)返回+0.0代替阀趴。
- INF和NAN的操作是不確定的昏翰。
大多數(shù)應(yīng)用可以使用rs_fp_relaxed,沒(méi)有任何的副作用刘急。這可能在某些結(jié)構(gòu)上非常有利棚菊,由于只需要適配寬松的進(jìn)度而帶來(lái)一些優(yōu)化(例如SIMD的CPU指令)。
訪問(wèn)RenderScript API接口
當(dāng)開(kāi)發(fā)一款應(yīng)用使用到RenderScript叔汁,你可以通過(guò)兩種方式來(lái)訪問(wèn)它的API:
- android.renderscript - 這個(gè)包里的API允許運(yùn)行在Android 3.0(API 11)以及更高的設(shè)備上统求。
- android.support.v8.renderscript - 這是Support Library的一個(gè)兼容包,使得API可以運(yùn)行在Android 2.2(API 8)以及更高的設(shè)備上据块。
我們強(qiáng)烈建議使用Support Library APIs來(lái)訪問(wèn)RenderScript的接口码邻,因?yàn)樗峁┝烁鼜V泛的設(shè)備兼容性。如果需要為特定版本的Android開(kāi)發(fā)應(yīng)用另假,可以使用android.renderscript像屋。
使用RenderScript Support Library APIs
為了使用Support Library RenderScript的API,你必須確保你的開(kāi)發(fā)環(huán)境能夠訪問(wèn)到它們边篮。下面的Android SDK工具是使用這些API所需要的:
- Android SDK Tools revision 22.2 or higher
- Android SDK Build-tools revision 18.1.0 or higher
你可以在Android SDK Manager里面檢查并更新這些工具己莺。
使用Support Library RenderScript的API:
- 確保已經(jīng)安裝了所需要的Android SDK版本以及構(gòu)建工具。
- 更新Android構(gòu)建過(guò)程的配置戈轿,加入RenderScript的配置:
打開(kāi)你的應(yīng)用模塊的build.gradle文件
-
添加RenderScript的配置
android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { minSdkVersion 8 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
上述構(gòu)建過(guò)程中的配置控制著不同的行為:
- renderscriptTargetApi - 指定生成的字節(jié)碼版本凌受。我們建議你設(shè)置這個(gè)值為能夠提供你正在使用的所有功能的最低API級(jí)別,同時(shí)設(shè)置renderscriptSupportModeEnabled為true思杯。這個(gè)設(shè)置的有效值為11到最近發(fā)布的API級(jí)別值胜蛉。如果你的應(yīng)用程序配置文件里面指定的最小SDK版本設(shè)置為不同的值,那這個(gè)值將會(huì)被忽略智蝠,構(gòu)建文件中的目標(biāo)版本值用來(lái)設(shè)置最低SDK版本腾么。
- renderscriptSupportModeEnabled - 如果運(yùn)行的設(shè)備不支持該目標(biāo)版本,那么可以指定生成的字節(jié)碼回落到一個(gè)兼容的版本杈湾。
- buildToolsVersion - Android 構(gòu)建工具所使用的版本解虱。這個(gè)值應(yīng)該被設(shè)置為18.1.0或者更高。如果這個(gè)可選的配置沒(méi)有被指定漆撞,那么將會(huì)默認(rèn)使用安裝的最高版本的構(gòu)建工具殴泰。你應(yīng)該總是設(shè)置這個(gè)值,以確保不同開(kāi)發(fā)設(shè)備不同配置之間的一致性浮驳。
-
在你使用RenderScript的類文件里面悍汛,導(dǎo)入Support Library的類:
import android.support.v8.renderscript.*;
通過(guò)Java代碼使用RenderScript
通過(guò)Java代碼來(lái)使用RenderScript依賴于位于android.renderscript或android.support.v8.renderscript包中的API類。大多數(shù)應(yīng)用都遵循基本的使用模式:
初始化一個(gè)RenderScript上下文至会。RenderScript上下文离咐,通過(guò)create(Context)方法進(jìn)行創(chuàng)建,確保RenderScript能過(guò)被使用,同時(shí)提供一個(gè)對(duì)象來(lái)控制所有后續(xù)的RenderScript對(duì)象的生存期宵蛀。你應(yīng)該考慮創(chuàng)建一個(gè)長(zhǎng)期運(yùn)行的context昆著,因?yàn)樗梢詣?chuàng)建不同的硬件資源。通常术陶,一個(gè)應(yīng)用程序只能持有一個(gè)RenderScript單例凑懂。
創(chuàng)建至少一個(gè)Allocation傳遞到腳本中。一個(gè)Allocation是一個(gè)RenderScript對(duì)象梧宫,提供固定數(shù)據(jù)的存儲(chǔ)接谨。腳本中的內(nèi)核,采取Allocation對(duì)象作為它們的輸入和輸出塘匣,同時(shí)Allocation對(duì)象可以在綁定全局腳本變量的時(shí)候脓豪,通過(guò)調(diào)用rsGetElementAt_type()和rsSetElementAt_type()方法在內(nèi)核中進(jìn)行訪問(wèn)。Allocation對(duì)象運(yùn)行數(shù)組從Java代碼中傳遞到RenderScript代碼馆铁,反之亦然跑揉。Allocation對(duì)象創(chuàng)建最典型的方式是使用createTyped(RenderScript, Type)或者createFromBitmap(RenderScript, Bitmap)。
-
創(chuàng)建腳本是必要的埠巨。在使用RenderScript時(shí),提供兩種類型的腳本:
-
ScriptC:這些是寫(xiě)在上述RenderScript內(nèi)核中的用戶自定義腳本现拒。為了使Java代碼能夠更容易訪問(wèn)到腳本代碼辣垒,每個(gè)腳本都會(huì)通過(guò)RenderScript編譯器反射為一個(gè)具體的Java類。這些類的名字為ScriptC_filename印蔬。例如勋桶,你的內(nèi)核位于invert.rs,并且一個(gè)RenderScript上下文已經(jīng)位于mRS,Java代碼實(shí)例化腳本為:
ScriptC_invert invert = new ScriptC_invert(mRenderScript);
ScriptIntrinsic:這些是內(nèi)置在RenderScript內(nèi)核的共同操作侥猬,比如高斯模糊例驹,圖像混合等等。想了解更多信息退唠,請(qǐng)參閱ScriptIntrinsic的子類鹃锈。
-
用數(shù)據(jù)填充Allocation對(duì)象。除了通過(guò)android.renderscript創(chuàng)建Allocation對(duì)象瞧预,一個(gè)Allocation對(duì)象會(huì)在第一次創(chuàng)建的時(shí)候被填充空數(shù)據(jù)屎债。為了填充一個(gè)Allocation對(duì)象,使用Allocation方法中的copy方法垢油。
設(shè)置所有需要的全局變量盆驹。全局變量可以使用同一ScriptC_filename類里面,名為set_globalname的方法進(jìn)行設(shè)置滩愁。例如躯喇,為了設(shè)置一個(gè)名為elements的int值,使用java方法set_elements(int)硝枉。RenderScript對(duì)象也可以在內(nèi)核里面進(jìn)行設(shè)置廉丽;例如倦微,名為lookup的rs_allocation變量,可以通過(guò)方法set_lookup(Allocation)進(jìn)行設(shè)置雅倒。
啟動(dòng)相應(yīng)的內(nèi)核璃诀。啟動(dòng)指定內(nèi)核的方法,體現(xiàn)在相同的具有名為forEach_kernelname()方法的ScriptC_filename類蔑匣。這些啟動(dòng)是異步的劣欢,并會(huì)通過(guò)啟動(dòng)的順序進(jìn)行序列化。根據(jù)傳遞給內(nèi)核的不同參數(shù)裁良,這些方法將會(huì)采取一個(gè)或兩個(gè)Allocation對(duì)象凿将。默認(rèn)情況下,一個(gè)內(nèi)核會(huì)執(zhí)行整個(gè)輸入或者輸出Allocation;為了執(zhí)行Allocation的子集价脾,可以通過(guò)適當(dāng)?shù)?a target="_blank" rel="nofollow">Script.LaunchOptions作為最后一個(gè)參數(shù)傳遞給forEach方法牧抵。調(diào)用的函數(shù)可以通過(guò)反射ScriptC_filename內(nèi)中的invoke_functionname方法進(jìn)行調(diào)用。
復(fù)制Allocation對(duì)象的數(shù)據(jù)侨把。為了從Java代碼訪問(wèn)到Allocation對(duì)象的數(shù)據(jù)犀变,必須使用Allocation的copy方法將數(shù)據(jù)拷貝到Java的緩沖區(qū)中。
回收RenderScript上下文秋柄。RenderScript的上下文可以通過(guò)destroy()方法進(jìn)行回收获枝,或者通過(guò)垃圾回收機(jī)制進(jìn)行銷毀。這會(huì)導(dǎo)致任何想要繼續(xù)使用這個(gè)對(duì)象的地方拋出異常骇笔。
譯者注:以前沒(méi)了解過(guò)RenderScript省店,所以翻譯起來(lái)也分外吃力,很多地方可能存在一些理解上的誤差笨触,在學(xué)習(xí)過(guò)程中會(huì)繼續(xù)校正懦傍,如果大家在閱讀過(guò)程中遇到問(wèn)題,也請(qǐng)直接查看原文芦劣。附上幾個(gè)例子粗俱,以加深對(duì)RenderScript的理解:
使用RenderScript實(shí)現(xiàn)圖片高斯模糊效果
-
build.gradle添加RenderScript配置
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" renderscriptTargetApi 19 renderscriptSupportModeEnabled true } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
-
自定義BlurBitmap類,實(shí)現(xiàn)高斯模糊方法
public class BlurBitmap { private static final float BITMAP_SCALE = 1.0f; private static final float BLUR_RADIUS = 25; public static Bitmap blur(Context context, Bitmap image) { int width = Math.round(image.getWidth() * BITMAP_SCALE); int height = Math.round(image.getHeight() * BITMAP_SCALE); Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false); Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap); RenderScript rs = RenderScript.create(context); ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap); Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap); theIntrinsic.setRadius(BLUR_RADIUS); theIntrinsic.setInput(tmpIn); theIntrinsic.forEach(tmpOut); tmpOut.copyTo(outputBitmap); return outputBitmap; } }