一、入口
https://developer.android.google.cn -> API指南 -> 庫 -> 功能 ->
v8 支持庫 -> RenderScript 開發(fā)者指南
本文章是對上面內(nèi)容的翻譯
二、RenderScript介紹
RenderScript是一個框架棕叫,這個框架是為了可以在性能好的android機器上執(zhí)行密集計算的任務(wù)。RenderScript是以并行的數(shù)據(jù)計算為設(shè)計原則殿怜,盡管對串行的工作也有益系枪。RenderScript在機器的可用處理器上并行運行,例如多核的cpu和GPU固额。這個允許你更多的關(guān)注算法而不是更多的關(guān)注安排工作的執(zhí)行順序眠蚂。
RenderScript特別擅長執(zhí)行圖像處理,計算攝影以及計算機視覺斗躏。
使用RenderScript之前逝慧,要明白下面幾個理念:
- 使用c99-derived language來完成性能更好的運算代碼。下面的章節(jié)Writing a RenderSctipt Kernel描述了怎么使用它來寫一個運算內(nèi)核啄糙。
- 控制API用于組織RenderScript資源和控制內(nèi)核執(zhí)行器的生命周期笛臣。運行在三種不同的語言下:Java,android NDK中的C++和c99派生出的內(nèi)核語言。下面的章節(jié)分別描述了第一種在Java中使用RenderScript(Using RenderScript from Java Code)和第三種單一來源的RenderScript(Single-Source renderScript)隧饼。
三沈堡、Writing a RenderScript Kernel
渲染腳本的內(nèi)核通常會在<project_root>/src/文件加下的.rs文件中,每個.rs被稱為一個腳本燕雁。每個腳本包含它自己的一組內(nèi)核诞丽,函數(shù),和變量拐格。一個腳本包含:
pragma聲明(#pragme version(1)):腳本中使用渲染腳本內(nèi)核使用的語言版本率拒,而且只能是1
pragma聲明(#pragma rs java_package_name(com.example.app)):定義從這個腳本中映射出對應(yīng)Java類的包名。要切記禁荒,.rs文件必須是應(yīng)用包里的代碼的一部分猬膨,而不是位于依賴庫工程中。
0或更多個執(zhí)行函數(shù):執(zhí)行函數(shù)是單線程的渲染腳本函數(shù),可以在Java代碼中通過任意參數(shù)調(diào)用這個函數(shù)勃痴。執(zhí)行函數(shù)通常用于在較大的進程管線內(nèi)的初始化安裝和串行計算谒所。
0或更多個腳本全局:每個腳本全局等同于c中的一個全局變量∨嫔辏可以在Java中訪問腳本全局劣领,腳本全局經(jīng)常作為參數(shù)傳給渲染腳本內(nèi)核。
-
0或更多個計算內(nèi)核:計算內(nèi)核是單個函數(shù)或函數(shù)集合铁材,計算內(nèi)核可以穿過數(shù)據(jù)集合在渲染腳本運行時并行執(zhí)行尖淘,有兩種計算內(nèi)核:映射內(nèi)核(也被成為foreach 內(nèi)核)和減少內(nèi)核。
<br />映射內(nèi)核是一個并行的函數(shù)著觉,它運行于相同維度的Allocation的集合中村生。默認的,這些維度中的每一個坐標上的元素執(zhí)行這個函數(shù)一次饼丘。它通常(不一定)用于將輸入Allocation的集合轉(zhuǎn)換為輸出Allocation的集合趁桃。- 下面是一個簡單映射內(nèi)核的例子:
uchar4 RS_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函數(shù)相同肄鸽。在函數(shù)原型中使用的RS_KERNEL指明這個函數(shù)是一個渲染腳本映射內(nèi)核而不是一個可執(zhí)行函數(shù)卫病。基于啟動內(nèi)核時的輸入Allocation典徘,in參數(shù)被自動填充蟀苛,x和y參數(shù)會在下面討論。內(nèi)核的返回值會被寫入到輸出Allocation的對應(yīng)位置逮诲。內(nèi)核運行在整個的輸入Allocation上屹逛,在Allocation的每一個元素上執(zhí)行這個內(nèi)核函數(shù)一次。
<br /> 映射內(nèi)核可能有一個或更多的輸入Allocation汛骂,單個或兩個輸出Allocation罕模。渲染腳本運行的時候會檢查并確保所有的輸入Allocation和輸出Allocation有相同的維度,而且每個元素的類型也要與內(nèi)核的原型(uchar4 in)匹配帘瞭;如果不匹配淑掌,會拋出異常。注意:在Android6.0(API level 23)之前蝶念,映射內(nèi)核只具有不高于一個的輸入Allocation抛腕。
<br />如果你需要更多的輸入 Allocation 和輸出 Allocation ,這些對象要和名字為rs_allocation的腳本全局綁定媒殉,并且在內(nèi)核或者可執(zhí)行函數(shù)中担敌,只能通過rsGetElementAt_type()或者rsSetElementAt_type()來訪問。注意:**為了方便廷蓉,RS_KERNEL是渲染腳本自動產(chǎn)生的宏定義全封。
#define RS_KERNEL attribute((kernel))
減少內(nèi)核是一組函數(shù),它運行在相同維度的輸入Allocation上。默認的刹悴,累加器函數(shù)會在Allocation中的每一個坐標的元素上執(zhí)行一次行楞。這個內(nèi)核一般用來減少輸入Allocation的集合將之變成一個。
-
下面是一個簡單地減少內(nèi)核的例子土匀,功能是將輸入的元素加起來子房。
#pragma rs reduce(addint) accumulator(addintAccum)static void addintAccum(int *accum, int val) { *accum += val; }
- 下面是一個簡單映射內(nèi)核的例子:
減少內(nèi)核包含一個或更多用戶寫的函數(shù)。#pragma rs reduce通過指定它的名字(例如就轧,addint)证杭,組成內(nèi)核的函數(shù)的名字和角色(例如,一個accumulator函數(shù)addintAccum)來定義內(nèi)核妒御。函數(shù)必須是靜態(tài)的解愤。減少內(nèi)核需要累加器函數(shù);它或許還有別的函數(shù)携丁,取決于你想讓他干什么。
<br />減少內(nèi)核的累加器函數(shù)必須返回void兰怠,必須具有最少兩個參數(shù)梦鉴。第一個參數(shù)(例如 accum)是累加數(shù)據(jù)項的指針,第二個參數(shù)(例如 val)會基于內(nèi)核啟動時傳進來的輸入Allocation參數(shù)自動填充揭保。累加數(shù)據(jù)項是在渲染腳本運行時產(chǎn)生的肥橙;默認值為0。默認的秸侣,這個內(nèi)核運行于整個輸入Allocation上存筏,分配內(nèi)存中的每個元素執(zhí)行累加器函數(shù)一次。默認的味榛,累加數(shù)據(jù)項的最終的值被視為減少內(nèi)核的結(jié)果椭坚,返回到Java中。渲染腳本運行時會檢查并確保輸入Allocation中的元素和累加器函數(shù)的原型匹配搏色,如果不匹配善茎,渲染腳本會拋出異常。
<br />減少內(nèi)核擁有一個以上的輸入Allocation频轿,但是沒有輸出Allocation垂涯。
想了解減少內(nèi)核更多信息,查看下面的部分Reduction Kernels in Depth航邢。
減少內(nèi)核的支持版本是Android7.0或以上耕赘。
映射內(nèi)核的函數(shù)或者減少內(nèi)核的累加器函數(shù)會獲取當前執(zhí)行到哪個坐標(Allocaiton的坐標),通過傳入int/uint32_t類型的參數(shù)x,y,和z膳殷,這些參數(shù)是可選的操骡。
<br />映射內(nèi)核的函數(shù)或者減少內(nèi)核的累加器函數(shù)也可以采用可選的rs_kernel_context類型的上下文作為參數(shù)。一些運行時API需要這個參數(shù)來查詢當前執(zhí)行器的屬性--例如,rsGetDimX当娱。(上下文參數(shù)在Android6.0(API level 23)或以上才可用)吃既。
- 可選的init()函數(shù)。init()函數(shù)是一種特殊的可執(zhí)行函數(shù)跨细,渲染腳本會在腳本第一次初始化的時候調(diào)用它鹦倚。這樣可以在腳本創(chuàng)建的時候自動進行一些運算。
- 0或更多個靜態(tài)腳本全局和靜態(tài)函數(shù)冀惭。靜態(tài)的腳本全局和普通的腳本全局一樣震叙,但是它不能從java中訪問。靜態(tài)的函數(shù)是一個標準的c函數(shù)散休,它可以被腳本中的任何內(nèi)核和可執(zhí)行函數(shù)調(diào)用媒楼,但是不暴露給Java的API。如果腳本全局或者函數(shù)不需要再java代碼中調(diào)用戚丸,強烈建議將其聲明為靜態(tài)的划址。
設(shè)置浮點指針的精度
在腳本中你可以控制浮點類型的精度,如果不需要使用IEEE 754-2008標準(default)限府,這點就很有用了夺颤。下面的pragma可以被設(shè)置為不同的浮點指針精度。
- #pragma rs_fp_full(不指定時的默認值):對于需要浮點精度的應(yīng)用程序胁勺,如IEEE 754-2008標準所述
- #pragma rs_fp_relaxed:對于不需要嚴格的IEEE 754-2008標準的應(yīng)用程序世澜,可以使用更低的精度,這種模式可以使用刷新到零或者四舍五入到零。
-
#pragma rs_fp_imprecise:對于不需要嚴格精度的應(yīng)用署穗。這種模式使得rs_fp_relaxed模式下的所有東西遵循下面的規(guī)則:
- 結(jié)果是-0.0的可以用+0.0代替
- INF(無窮大)和NAN(無效數(shù)字)沒有被定義
許多應(yīng)用使用 rs_fp-relaxed 也不會有什么副作用寥裂。對于僅需要放松進度就可以優(yōu)化的架構(gòu)非常有用(就像SIMD CPU instructions)
四、獲取RenderScript APIs
開發(fā)android應(yīng)用時案疲,你可以有兩種方法獲取到RenderScript API:
1封恰、當你開發(fā)的app最低版本是Android3.0(API level 11)時,你可以直接使用android.renderscript
2褐啡、在support library里面俭驮,有android.support.v8.renderscript,最低支持到Android2.3(API level 9)春贸。
下面是你要考慮的點:
- 如果你使用Support Library APIs混萝,無論你想使用是RenderScript的什么功能,都會兼容到android2.3版本萍恕。這樣可以允許你的應(yīng)用運行在更多的設(shè)備上逸嘀。
- 部分RenderScript特性將會無效
- 如果你使用 Support Library APIs,與使用 native APIs 相比允粤,你的apk可能會更大
使用RenderScript Support Library APIS
你必須配置你的開發(fā)環(huán)境才可以獲取到支持庫崭倘。下面這些是必須的:
- Android SDK Tools version >= 22.2
- Android SDK Build-tools version >= 18.1.0
你可以通過Android SDK Manager來檢查和更新這些工具的版本
<br />如何使用支持庫
- 確認上面工具的版本滿足
- 更新android構(gòu)建指引的設(shè)置
打開你的應(yīng)用的module的build.gradle
-
將下面的RenderScript的設(shè)置添加進去
android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { minSdkVersion 9 targetSdkVersion 19 renderscriptTargetApi 18 renderscriptSupportModeEnabled true } }
上面的設(shè)置控制了構(gòu)建過程的下列行為
- renderscriptTargetApi-會產(chǎn)生指定的字節(jié)碼版本翼岁。在保證功能的前提下,我們建議將這個值設(shè)置為需要支持庫(需要將renderscriptSupportModeEnabled 設(shè)置為 true)的最低版本司光。這個設(shè)置的有效值是大于Android3.0(API level 11)的整型值琅坡,小于最新發(fā)布的API的正式版。如果app中manifest指定的最低sdk版本與這個值不同(我認為意思是小于這個值)残家,build.gradle中設(shè)置的值會被忽略榆俺,并且被設(shè)置為manifest中的最低sdk版本。
- renderscriptSupportModeEnabled-如果應(yīng)用所運行的設(shè)備不支持目標版本坞淮,回饋到兼容版本產(chǎn)生特定的字節(jié)碼茴晋。
- buildToolsVersion-構(gòu)建工具的版本。這個版本需要>=18.1.0回窘。如果這個選項沒有指定诺擅,會使用安裝的構(gòu)建工具的最高版本。你應(yīng)該指定這個選項來確保在不同的開發(fā)機器上能有一致的配置啡直。
- 如果你的app要使用RenderScript烁涌,導入下面的類:
import android.support.v8.renderscript.*;
五、在Java中使用(Using RenderScript from Java Code
在Java使用RenderScript需要依賴android.renderscript或者android.support.v8.renderscript包里面的API酒觅。下面是基本的使用模式:
- Initialize a RenderScript context:這個RenderScript對象通過RenderScript.create(Context)方法創(chuàng)建撮执,確保RenderScript對象可以被使用而且會提供一個對象來控制后續(xù)的RenderScript的對象的生命周期。你應(yīng)該將context的創(chuàng)建視為一個潛在的長時間運行的操作阐滩,因為他會在不同的硬件上創(chuàng)建資源二打;如果可能的話县忌,初始化操作不應(yīng)該在app的關(guān)鍵路徑(我猜是不是說要延遲初始化)掂榔。通常,一個應(yīng)用在一個階段只會有一個RenderScript Context實例症杏。
- Create at least one Allocation to be passed to a script:Allocation是RenderScript用到的對象装获,它用來給定量的數(shù)據(jù)提供存儲空間。腳本中的內(nèi)核將Allocation對象作為它的輸出和輸入厉颤⊙ㄔィ可以內(nèi)核中通過rsGetElementAt_type()和rsSetElementAt_type()來訪問這個對象。Allocation允許將數(shù)組從Java代碼傳遞到RenderScript代碼逼友,反之亦然精肃。Allocation對象通常使用createTyped()或者createFromBitmap()方法來創(chuàng)建。
-
Create whatever scripts are necessary:當使用RenderScript的時候帜乞,有兩種類型的腳本:
-
ScriptC:這是一個用戶定義的腳本司抱,在上面Writing a RenderScript Kernel有描述過如何寫一個腳本。為了在Java代碼中方便的獲取這個腳本黎烈,每個腳本都有一個通過RenderScript編譯器映射出來的Java類习柠;這個類的名字是ScriptC_filename匀谣。例如,如果上面的內(nèi)核位于invert资溃。rs和RenderScript context**也已經(jīng)在mRenderScript中創(chuàng)建武翎,初始化腳本的代碼是:
Script_invert invert = new Script_invert(mRenderScript) - ScriptIntrinsic:這個腳本創(chuàng)建于RenderScript內(nèi)核,為了一般的操作溶锭,就像Gaussian blur(高斯模糊)宝恶,convolution(卷積)和 image blending(圖像混合)。想了解更多的信息暖途,看一下ScriptIntrinsic的子類卑惜。
-
ScriptC:這是一個用戶定義的腳本司抱,在上面Writing a RenderScript Kernel有描述過如何寫一個腳本。為了在Java代碼中方便的獲取這個腳本黎烈,每個腳本都有一個通過RenderScript編譯器映射出來的Java類习柠;這個類的名字是ScriptC_filename匀谣。例如,如果上面的內(nèi)核位于invert资溃。rs和RenderScript context**也已經(jīng)在mRenderScript中創(chuàng)建武翎,初始化腳本的代碼是:
- Populate Allocations with data:除了通過createFromBitmap()方法創(chuàng)建的對象,Allocation被創(chuàng)建時也可以通過空數(shù)據(jù)來填充驻售。通過Allocation的copy方法來填充露久,這個copy方法是同步的。
- Set any necessary script globals:在Script_filename類中欺栗,你可以使用set_globalname方法來設(shè)置毫痕。你的腳本中有一個腳本全局名字是threshold,可以通過set_threshold(int)方法來設(shè)置迟几;通過set_lookup(Allocation)方法設(shè)置一個 as_allocation 類型的腳本全局變量lookup消请。這些set方法是異步的**。
-
Launch the appropriate kernels and invokable functions :
通過ScriptC_filename類中的方法forEach_mappingKernelName()或reduce_reductionKernelName()來啟動給定內(nèi)核类腮。這個啟動時異步的臊泰。根據(jù)內(nèi)核中設(shè)定的參數(shù),這個方法采用一個或更多的Allocation蚜枢,所有的Allocation都必須設(shè)定相同的維度缸逃。默認的,內(nèi)核在這些維度的每個坐標上執(zhí)行厂抽;想在這些坐標的子集上運行內(nèi)核需频,需要傳遞合適的參數(shù)Script.LaunchOptions作為forEach和reduce方法的最后一個參數(shù)。使用ScriptC_filename類中方法invoke_functionName**來啟動時筷凤,這些啟動是異步的昭殉。 - Retrieve data from Allocation objects and javaFuturetype objects。為了在Java代碼中從Allocation獲得數(shù)據(jù)藐守,你要使用Allocation中的'copy'方法將數(shù)據(jù)copy到j(luò)ava中挪丢。想要從減少內(nèi)核中獲取結(jié)果,必須使用javaFutureType.get()方法。上面說的'copy'和get()方法是同步的。
- Tear down the RenderScript context塘砸。你可以通過下面的方法銷毀RenderScript context,destroy()或者允許RenderScript context對象進行垃圾收集巢块。之后礁阁,你使用任何屬于這個context的對象都會拋出異常。
異步執(zhí)行模式 Asynchronous execution model
方法forEach,invoke,reduce,and set都是異步的族奢,每個方法都有可能在完成目標操作之前返回(返回是不是在說結(jié)束)姥闭。但是,單個操作是串行的越走,依照他們啟動的順序棚品。
<br />Allocation類提供'copy'方法copy數(shù)據(jù)給Alloction對象,或者從Allocation對象中copy出數(shù)據(jù)廊敌。'copy'方法是同步的铜跑,是串行的(相對于上述的異步操作方法操作相同Allocation)。
<br />映射的javaFutureType類提供一個get()方法從reduction中獲取結(jié)果骡澈,get()方法是同步的锅纺,是串行的(相對于異步的reduction)。
六肋殴、單一來源的渲染腳本(Single-Source RenderScript)
Android 7.0 (API 24) 介紹了一種新的編程特性-Single-Source RenderScript,新的特性中囤锉,在script定義的時候,kernels從script中產(chǎn)生而不是從Java中產(chǎn)生』ご福現(xiàn)在這種方式局限于映射內(nèi)核中官地,在這個部分中簡稱為"kernels"。這種新的特征也支持在script中創(chuàng)建rs_allocation類型的Allocation±优常現(xiàn)在可以在單獨的腳本中實現(xiàn)整個算法驱入,即使需要啟動多個kernels。優(yōu)點是雙重的:1.更多的可讀代碼氯析,因為他用一種語言來實現(xiàn)算法亏较;2.潛在的,存在實現(xiàn)更快的代碼的可能魄鸦,因為在多個kernels啟動時宴杀,Java和RenderScript之間的轉(zhuǎn)換變少了癣朗。
<br />在Single-Source RenderScript中拾因,你寫一個Writing a RenderScript Kernel部分中描述的kernels。寫一個叫做 rsForEach() 可執(zhí)行的功能來啟動kernels旷余。這個方法采用一個內(nèi)核函數(shù)作為其第一個參數(shù)绢记,接下來是輸入Allocation和輸出Allocation。里一個相似的方法 reForEachWithOptions() 采用一個額外的參數(shù) rs_script_call_it正卧,這個參數(shù)指定一個輸入Allocation和輸出Allocation的元素子集作為內(nèi)核函數(shù)執(zhí)行的空間蠢熄。
<br />按照 Using RenderScript from Java Code 部分中的步驟,可以調(diào)用Java中特定執(zhí)行函數(shù)來開啟RenderScript 運算炉旷。在 launch the appropriate kernels 這一步中签孔,調(diào)用一個可執(zhí)行函數(shù) invoke_function_name()** 來開啟整個運算叉讥,包括啟動kernels。
<br />從一個內(nèi)核啟動到另一個內(nèi)核啟動饥追,經(jīng)常需要使用 Allocation 保存和及時傳遞結(jié)果图仓。你可以通過 rsCreateAllocation() 來創(chuàng)建他們。這個API的一種方便使用的方式是reCreateAllocation_<T><W>(...),T是元素的數(shù)據(jù)類型但绕,W是這個元素的矢量寬度救崔。這個函數(shù)采用X,Y和Z這三個維度作為參數(shù),對于1D和2D的allocation,Y或Z維度的尺寸會被忽略捏顺。例如六孵,reCreateAllocation_uchar4(16384)創(chuàng)建了一個含有16384個一維元素的分配空間,每一個元素都是uchar4類型的幅骄。
<br />分配的空間由系統(tǒng)自動管理劫窒,你不必特地的去釋放他們。但是拆座,你可以調(diào)用 rsClearObject(rs_allocation* alloc) 來表明你不再需要這塊內(nèi)存烛亦,這樣系統(tǒng)能盡快的將這塊資源釋放掉。
<br />Writing a RenderScript Kernel部分有一個轉(zhuǎn)換圖片簡單例子懂拾。下面使用 Single-Source RenderScript 拓展這個例子煤禽,對這個圖片做更多的操作,它包含另外一個內(nèi)核岖赋,greyscale,用來將一個彩色的圖片轉(zhuǎn)換為黑白的檬果。process()方法將兩個內(nèi)核連續(xù)的應(yīng)用于輸入的圖片(輸入的圖片Allocation),產(chǎn)生一個輸出圖片唐断。輸入 Allocation 和輸出 Allocation 作為 rs_allocation 的參數(shù)傳入选脊。
// File:singleSource.rs
#pragma version(1)
#pragma rs java_package_name(com.android.rssample)
static const float4 weight = {0.299f, 0.587f, 0.114f, 0.0f};
uchar4 RS_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;
}
uchar4 RS_KERNEL greyscale(uchar4 in) {
const float4 inF = rsUnpackColor8888(in);
const float4 outF = (float4){ dot(inF, weight) };
return rsPackColorTo8888(outF);
}
void process(rs_allocation inputImage, rs_allocation outputImage) {
const uint32_t imageWidth = rsAllocationGetDimX(inputImage);
const uint32_t imageHeight = rsAllocationGetDimY(inputImage);
rs_allocation tmp = rsCreateAllocation_uchar4(imageWidth, imageHeight);
rsForEach(invert, inputImage, tmp);
rsForEach(greyscale, tmp, outputImage);
}
你可以像下面一樣在Java中調(diào)用process()函數(shù):
// File SingleSource.java
RenderScript RS = RenderScript.create(context);
ScriptC_singlesource script = new ScriptC_singlesource(RS);
Allocation inputAllocation = Allocation.createFromBitmapResource(
RS, getResources(), R.drawable.image);
Allocation outputAllocation = Allocation.createTyped(
RS, inputAllocation.getType(),
Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT);
script.invoke_process(inputAllocation, outputAllocation);
這個例子展示了完全通過RenderScript語言來實現(xiàn)一個涉及到兩個內(nèi)核的算法。沒有 Single-Source RenderScript 脸甘,你必須在Java中啟動兩個內(nèi)核恳啥,分別啟動兩個內(nèi)核會使整個算法變得難以理解。Single-Source RenderScript 特性不單使代碼更容易看懂丹诀,它還消除了內(nèi)核啟動期間 Java 和 script 之間的過渡钝的。一些迭代算法會啟動內(nèi)核上百次,使得這種轉(zhuǎn)換的開銷相當?shù)拇蟆?/p>
七铆遭、Reduction Kernels in Depth
...
將會在下面的章節(jié)中講述一個使用 RenderScript 進行高斯模糊的例子硝桩,敬請期待