【android開發】Google RenderScript文檔【一】

這篇文章純粹是翻譯,先翻譯完再說(主要是RenderScript看起來很牛逼的樣子)。先粗略翻譯一遍(可能會有很多問題),然後一步步學習吧。
那就開始吧。


【 概 述】
RenderScript是一款用於執行高計算量任務的框架,它在Android平臺上具有良好的性能。當然,其設計初衷是用於處理大量的並行數據計算,但也不妨使用在其他場景中。其運行時(Runtime)利用設備的多CPU/GPU進行並行計算,讓你可以去專注表達算法本身,而不是去在意協調任務的調用。RenderScript對於圖像處理、運算攝影(computational photography)、視訊處理(計算機視覺: computer vision)有着優異的表現。
在繼續往下介紹RenderScript之前,我們需要有兩點概念需要理解:
● 高性能計算內核使用的是C99-derived語言編寫。一個計算內核是一個 or 一組函數,你可以通過RenderScript的runtime來執行它們,進行一堆數據的並行處理。
● Java API用於管理RenderScript資源的生命週期和控制內核的執行。

一、實現RenderScript內核(Writing a RenderScript Kernel)

RenderScript內核文件保存爲 .rs 類型,並且位於項目的src根目錄下( /src/ ),每個 .rs 文件被稱作爲一個腳本,每個腳本包含了他自身的一組內核、函數、變量。一個腳本可以包含的內容如下:
● 一個編譯指示申明(#pragma version(1)),一個用來申明當前RenderScript內核使用的語言版本的申明。目前只有 1 爲有效的取值。
● 一個編譯指示申明(#pragma rs java_package_name(com.example.app)),一個用來申明此腳本對應的Java類的包名。[注意:你的 .rs 文件必須是你應用包中的一部分,而不是放置在項目的庫文件夾中]
● 0個或多個可被調用的函數(invokable functions)。一個可被調用的函數是一個單線程的RenderScript函數,你可以在Java中使用任意參數來調用他。[這些函數通常在初始化時 或者 在一個大型處理管道中處理一系列的計算時很有用]
● 0個或多個全局腳本(script globals)。一個全局腳本類似於C語言中的全局變量。你可以在Java代碼中訪問這些全局腳本。[全局腳本通常使用於向RenderScript內核傳遞參數]
● 0個或多個計算內核(compute kernels)。有兩種類型的計算內核:映射內核(也叫做遍歷內核 mapping kernel)、減少內核(reduction kernel)。

1)映射內核(mapping kernel)
一個映射內核是一個並行函數,它用於處理一組相同dimension的Allocation。默認情況下,他會爲dimension中的每個coordinate執行一次。他主要(但不是唯一)用於對輸入的一組Allocation數據進行轉換,同一時間輸出Allocaion的一個元素。下面是一個映射內核的例子:

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語言的函數一樣滴。RS_KERNEL 主要使用來做函數的原型指定,他表示這個函數是一個”映射內核(mapping kernel)”而不是一個”可調用函數(invokable function)”。輸入參數“in”是基於在內核啓動時傳入的Allocation參數而自動填充的,參數x 和y在後文中將會提到。內核的返回值將會自動被寫入到輸出參數Allocation中的適當位置。默認情況下,內核將會貫穿執行整個輸入的Allocation(Allocation中的每個元素都將通過內核的方法執行一遍)。
一個映射內核可能有一個或多個輸入Allocation,有一個或者兩個Allocation輸出。RenderScript的運行時回去檢測並確保所有的輸入和輸出Allocation都有相同的規格(dimension),並且輸入的元素類型和輸出的Allocation與內核的原型匹配,如果其中一種檢測失敗,RenderScript將會拋出異常。【提示:在Android6.0(API23)之前,一個映射內核也許沒有超過一個輸入的Allocation】
如果你需要多個輸入或者多個輸出Allocation(超過內核所擁有的),那些對象應該綁定到全局腳本 rs_allocation 上,同時從內核中訪問,或者通過可被調用函數(invokable function)的rsGetElementAt_type() / rsSetElementAt_type()。【提示:爲了你的方便,RS_KERNEL是由RenderScript自動宏觀定義出來的】

#define RS_KERNEL __attribute__((kernel))

2)減少內核(reduction kernel)
reduction kernel也是處理一堆相同dimension的Allocation的function家庭中的一員。默認情況下,他的累計函數(accumulator function)將會爲每個dimension中的coordinate執行一遍。他主要(但不是唯一)用於“降維”一個輸入的Allocation集合成一個單獨的值。下面是一個reduction kernel將輸入元素累加起來的例子。

#pragma rs reduce(addint) accumulator(addintAccum)

static void addintAccum(int *accum, int val) {
    *accum += val;
}

一個reduction kernel是由一個或者多個用戶寫的function組成。 #pragma rs reduce是用來通過指定其名字(此例中爲addint)、function(用於組成內核,此例中,addintAccum是一個accumulator function)的名字和角色來定義內核。所有這樣的function都應該是static的。一個reduction kernel通常需要一個accumulator function;他也有可能擁有其他function,這取決於你想讓這個kernel做什麼。
一個reduction kernel的accumulator function的返回值必須是void,並且需要至少含有兩個參數。第一個參數(此例中是 accum)是一個指針,指向一個accumulator data item;第二個參數(此例中是 val)基於kernel在啓動時傳遞過來的Allocation參數而自動自動填充的。accumulator data item由RenderScript運行時創建;默認情況下,初始化爲0。默認情況下,此內核或執行完整個Allocation(Allocation中的每個元素都將會通過accumulator function執行一次)。默認情況下,accumulator data item 的最終值會被視爲此reduction的結果並返回給java。RenderScript的運行時回去檢測並確保所有的輸入和輸出Allocation都有相同的規格(dimension),並且輸入的元素類型和輸出的Allocation與內核的原型匹配,如果其中一種檢測失敗,RenderScript將會拋出異常。
一個reduction kernel擁有一個或多個輸入Allocation,但是沒有輸出Allocation。
後文的【Reduction Kernels in Depth】將會更詳細的介紹到。
【reduction kernel的支持版本在android7.0(API24)以後】
mapping kernel function 或者 reduction kernel accumulator function在當前任務執行中可能會使用特殊參數x,y和z(類型必須是int或者uint32_t)訪問coordinate。這些參數都是可選的。
mapping kernel function 或者 reduction kernel accumulator function可能也會獲取可選的特殊參數context(rs_kernel_context類型),對於runtime API家族來說,此context是非常需要的,它用來去查詢確認當前執行的任務中的屬性——舉個例子:rsGetDimX.( context參數適用於android6.0 ( API 23)及以上 )
● 一個可選的 init() 函數。一個init()函數是一個特殊類型的可被調用函數(invokable function),他是在腳本第一次被實例化時會被RenderScript執行的函數。這個函數允許在腳本創建時進行一些計算(初始化函數)。
● 一個或者多個靜態全局script和函數。靜態script腳本除了不能被java代碼訪問之外,其他和一般的全局script一樣。靜態函數是一個標準的C語言函數,他可以被所有內核調用,但是沒有暴露給java API。如果一個全局script或者function不用被java API調用,那麼強烈建議他申明爲static的。

【設置浮點精度】
你可以控制script中要求的浮點數精確等級。特別是當我們沒必要完全按照IEEE 754-2008(默認使用的標準)時,特別有用。下面將介紹如何設置不同浮點精度等級。
* #pragma rs_fp_full ( 如果沒有特別指定,使用的就是此值 ):這個適用於使用IEEE 754-2008標準框架下的標準浮點精度的APP。
* #pragma rs_fp_relaxed:這個參數適用於沒那麼嚴格的基於 IEEE 754-2008標準框架的APP,他允許更低的精度。This mode enables flush-to-zero for denorms and round-towards-zero.
* #pragma rs_fp_imprecise:這個參數使用於那些不必要強制要求精度的APP。除了允許rs_fp_relaxed的特性外,還支持下面的特性:
1)將 -0.0結果使用+0.0來代替
2)將INF和NAN視爲未定義。
在大多數的應用程序中,使用 rs_fp_relaxed 是沒啥毛病的。由於很多架構都是隻對 relax精度做附加優化(例如:SIMD CPU 架構),所以使用 rs_fp_relaxed很有好處的。

訪問RenderScript API


當你使用RenderScript來開發Android應用程序時,你可以有兩種方式來訪問其API(兼容包和RenderScript包);
1、使用 android.renderscrip - 此包主要支持android3.0(API 11)及以上。
2、使用 android.support.v8.renderscript - 這是一個兼容包,支持android 2.3(API 9)及以上。這個包需要單獨導入。
【使用RenderScript支持包API】
爲了使用RenderScript的支持包,你需要確定你的開發環境可以訪問他們,下面有幾點要求:
** android的SDK tools版本需要在22.2或者更高
** android的SDK build-tools版本需要在18.1.0或者更高
(如果沒達到版本要求,請使用SDK MANAGER更新),另外,使用支持包可能會讓安裝包稍大

使用支持庫來訪問API需要有下面幾個步驟:
for android studio:
1)確保你的SDK版本和SDK build tool滿足要求
2)爲編譯工具添加對RenderScript的支持設置。
A、在應用程序的module中打開build.gradle文件
B、添加下面的代碼。

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.3"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 16

        renderscriptTargetApi 18
        renderscriptSupportModeEnabled true

    }
}

for Eclipse:
1)確保你的SDK版本和SDK build tool滿足要求
2)爲編譯工具添加對RenderScript的支持設置。
A、打開project.properties文件。
B、添加下面的代碼到文件中

renderscript.target=18
renderscript.support.mode=true
sdk.buildtools=18.1.0

通過java代碼使用RenderScript


不管是使用原生包還是支持包,大部分的應用程序遵循下面一些相同的使用模式:
1、初始化一個RenderScript實例 :創建一個RenderScript上下文(實例),通過其create( Context )方法創建。實例化RenderScript比較消耗資源,也很耗時,因此,一般情況下,我們只實例化一個對象。

2、創建至少一個Allocation對象,用於傳遞給Script。 一個Allocation對象是一個存儲固定數量數據的RenderScript對象。內核使用Allocation對象來作爲輸入或者輸出。Allocation對象可以在綁定爲全局腳本(script global)時,在內核裏通過rsGetElementAt_type() 或者 rsSetElementAt_type()來進行訪問。Allocation對象允許從java代碼中傳遞集合到RenderScript代碼中,反之亦然。Allocation對象常通過createTyped( RenderScript, Type ) 或者 createFromBitmap( RenderScript, Bitmap )來創建。

3、創建Script(任意多個,根據需求而定)也很必要。當你使用RenderScript時,有兩種類型的Script可用。分別是:
** ScriptC:這是一種用戶定義的Script(上文:寫一個RenderScript內核有介紹)。每一個Script都對應着一個java類(由RenderScript編譯器映射),主要是爲了更方便的在java代碼中訪問Script;這樣的java類,一般是以ScriptC_filename作爲名字,例如:有個映射內核,文件名爲:invert.rs 並且假設已經創建了一個RenderScript實例對象,變量名爲mRenderScript,接下來的初始化Script的代碼將會是:ScriptC_invert invert = new ScriptC_invert(mRenderScript)。
** ScriptIntrinsic:這些是爲了通用操作而內置在RenderScript中的一些Script。例如:高斯模糊,卷積,圖像調配等。(想了解更多,請查詢 android.renderscript.ScriptIntrinisic類的子類)

4、給Allocation填充數據: 除了使用createFromBitmap(),Allocation在第一次創建時,其包含的數據都爲空。使用“copy”類型的方法即可爲Allocation添加數據。注意:“copy”類型的方法爲線程同步的。

5、創建任意多個全局腳本(Script global): 你可以在相同的 ScriptC_filename類中使用set_globalname方法來設置global。例如:我可以使用java代碼 set_threshold(int) 來設置一個名爲threshold的int型變量;再例如:我可以通過java代碼set_lookup(Allocation)來設置一個名爲lookup的rs_allocation類型的變量。這些方法也是線程異步的。

6、啓動相應的內核,並執行相應的函數: forEach_mappingKernelName() 或 reduce_reductionKernelName()類型的方法可以啓動一個在同一個類(ScriptC_filename)中的指定的內核。這些方法是線程異步的。取決於傳遞給內核的參數,方法可以擁有一個或多個Allocation,並且他們有相同的規格(dimension)。默認情況下,一個內核會執行完這些dimension中所有的coordinate;如果你要執行的內容越過一些coordinate的子集,你可以傳遞一個可選參數 Script.lauchOptions 作爲 forEach 或 reduce 的最後一個參數。
在相同的Script_filename類中,可以使用invoke_functionName方法來啓動一個可被調用函數(invokable function)。這些方法都是線程同步的。

7、從Allocation對象和JavaFutureType對象中檢索數據: 如果你想通過java代碼去訪問Allocation中的數據,你只能通過Allocation的“copy”類型方法將那部分數據拷貝出來。如果你想獲取Reduction內核的結果,你只能使用javaFutureType.get()方法。“copy”類型和get()方法都是線程同步的。

8、銷燬RenderScript 實例: 你可以通過RenderScript實例的destroy()方法來銷燬RenderScript實例對象,當然,也可以允許其被垃圾回收機制回收。如果此實例再次被使用,將會拋出異常。

【異步執行模型】
forEach、invoke、reduce和set方法都是異步的——即:每個方法都有可能在完成要求動作前返回給java。然而,每個獨立的動作在他們啓動後就是按照一定順序序列化了的。

Allocation類提供了“copy”類型的方法去將Allocation中的數據拷貝出來。“copy”類型的方法是異步的,並且是序列化了的(遵循上述説到的異步Action方式)。

javaFutureType類提供了get()方法去獲取reduction的結果。get()方法是異步的並且也是序列化的。

發佈了38 篇原創文章 · 獲贊 38 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章