剛開(kāi)始接觸Android圖形上的一些東東,為了學(xué)習(xí)這里翻譯一篇官網(wǎng)博客上的文章,增加了一些內(nèi)容,并實(shí)現(xiàn)了一下Android RenderScript的一個(gè)例子,開(kāi)發(fā)的環(huán)境是Android Studio,API 23. RenderScript跟圖形關(guān)系也不很大,有一點(diǎn)關(guān)系其實(shí)就是在GPGPU的概念上。
成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、灞橋網(wǎng)絡(luò)推廣、小程序定制開(kāi)發(fā)、灞橋網(wǎng)絡(luò)營(yíng)銷、灞橋企業(yè)策劃、灞橋品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供灞橋建站搭建服務(wù),24小時(shí)服務(wù)熱線:18982081108,官方網(wǎng)址:www.cdcxhl.com
原文鏈接:http://android-developers.blogspot.tw/2011/03/renderscript.html
RenderScript的設(shè)計(jì)目標(biāo)
RS有3個(gè)主要的設(shè)計(jì)目標(biāo),以下按照重要性從大到小介紹.
可移植性:應(yīng)用程序需要能夠運(yùn)行在不同的設(shè)備上,這些有可能是采用的完全不同的硬件。ARM架構(gòu)現(xiàn)在就有多種不同的硬件--有或者沒(méi)有VFP,有或者沒(méi)有NEON,不同數(shù)量的寄存器。除了ARM,還有X86架構(gòu),多種GPU架構(gòu),甚至更多的DSP架構(gòu)。
性能:第2個(gè)目標(biāo)是在滿足可移植的條件下盡可能的提升性能。對(duì)于RS而言,我們需要在性能上比現(xiàn)有的解決方案更好。
易用性:第3個(gè)目標(biāo)是盡可能簡(jiǎn)化開(kāi)發(fā)。盡可能使用自動(dòng)化過(guò)程來(lái)避免耦合的代碼和繁重的工作。
為了實(shí)現(xiàn)這3個(gè)目標(biāo),我們?cè)谠O(shè)計(jì)上做了一些權(quán)衡。這些權(quán)衡首先將RS從現(xiàn)有的Android架構(gòu)方法中進(jìn)行了剝離,比如Dalvik或NDK,這些是用來(lái)解決不同問(wèn)題的不同工具。
核心設(shè)計(jì)的選擇
第一個(gè)需要做出的抉擇是采用什么語(yǔ)言?有很多語(yǔ)言可以選擇,其中Shader語(yǔ)言,C或者C++都是可以考慮的。最后我們放棄了Shader,因?yàn)镾hader需要操作的數(shù)據(jù)結(jié)構(gòu)跟圖形應(yīng)用綁定太緊,并且缺少指針和遞歸限制了易用性。C++從一方面來(lái)講很不錯(cuò)但是它的問(wèn)題是可移植性欠缺。高級(jí)的C++特性很難運(yùn)行在沒(méi)有CPU的硬件上。最后我們選擇了C99,因?yàn)樗峁┝颂峁┝烁渌x擇一樣的性能,并且很容易被開(kāi)發(fā)者理解,而且能夠在眾多設(shè)備上運(yùn)行。
另一個(gè)權(quán)衡是RS的流程。具體說(shuō)就是如何將源碼轉(zhuǎn)換成機(jī)器碼。我們考慮了多個(gè)方案并且在開(kāi)發(fā)RS過(guò)程中實(shí)現(xiàn)了2種不同的方案。早先版本中(2.3)在設(shè)備上編譯C代碼生成機(jī)器碼,這樣做有一些好處,例如應(yīng)用程序可以快速地編譯,但是也卻也帶來(lái)了易用性上的問(wèn)題。必須要先編譯你的App,安裝運(yùn)行,然后才能發(fā)現(xiàn)你的語(yǔ)法錯(cuò)誤,這是件非常痛苦的事情。而且低端的CPU會(huì)限制對(duì)代碼的分析和優(yōu)化。
所以我們轉(zhuǎn)而考慮LLVM,采用一個(gè)修改過(guò)的clang版本將腳本的編譯和分析放在開(kāi)發(fā)端。我們?cè)谶@個(gè)階段中進(jìn)行了高層次的優(yōu)化,生成LLVM字節(jié)碼。從LLVM中間字節(jié)碼生成機(jī)器碼,依然是在設(shè)備上(附帶額外的特定設(shè)備的優(yōu)化)
我們最后一個(gè)比較大的權(quán)衡是線程的啟動(dòng)。主要權(quán)衡的是性能和可移植性?,F(xiàn)有的并行計(jì)算方案允許開(kāi)發(fā)者在特定設(shè)備上進(jìn)行調(diào)優(yōu),但是可能卻會(huì)影響其他設(shè)備的性能。如果有足夠的時(shí)間和資源開(kāi)發(fā)者肯定能夠?qū)λ杏布O(shè)備都進(jìn)行調(diào)優(yōu)。然而測(cè)試和調(diào)優(yōu)有的時(shí)候卻無(wú)法進(jìn)行,你無(wú)法在未發(fā)布的硬件上或者你沒(méi)有的硬件上進(jìn)行調(diào)優(yōu)。另一個(gè)更可移植的方法是把調(diào)優(yōu)放在運(yùn)行時(shí),犧牲一點(diǎn)最高性能,而提供足夠優(yōu)秀的平均性能??紤]到我們的第一目標(biāo)是可移植,所以我們選擇將調(diào)優(yōu)放在了運(yùn)行時(shí)。
將線程啟動(dòng)的管理放到運(yùn)行時(shí)帶來(lái)的另一個(gè)影響是決定在哪里運(yùn)行你的腳本。例如,有些硬件支持指針和遞歸,有的卻不支持。我們選擇不允許這些事情,而提供給開(kāi)發(fā)者一個(gè)最小的通用標(biāo)準(zhǔn)API,我們選擇在運(yùn)行時(shí)對(duì)腳本進(jìn)行分析。這允許開(kāi)發(fā)者能夠最大限度地發(fā)揮支持這些特性的硬件,因?yàn)榭偸菚?huì)認(rèn)為會(huì)有一個(gè)全特性的CPU可以依賴。所以,開(kāi)發(fā)者可以聚焦在寫(xiě)出更好的App,硬件開(kāi)發(fā)商也由于競(jìng)爭(zhēng),而制作出更多特性更高性能的硬件。一個(gè)新的硬件特性出現(xiàn),應(yīng)用程序也不用去修改已有的代碼。
易用性是RS設(shè)計(jì)中的主要驅(qū)動(dòng)力。大部分現(xiàn)有的并行計(jì)算和圖形平臺(tái)需要在App中開(kāi)發(fā)復(fù)雜的邏輯代碼來(lái)實(shí)現(xiàn)高性能,這樣的代碼很容易出bug,寫(xiě)起來(lái)也很痛苦。開(kāi)發(fā)端的靜態(tài)代碼分析有助于解決這個(gè)問(wèn)題。每個(gè)RS腳本生成一個(gè)對(duì)應(yīng)的java類。命名和成員都是從RS腳本中提取出來(lái)極大簡(jiǎn)化了RS腳本的使用。
例子: RS應(yīng)用
一個(gè)簡(jiǎn)單的RS應(yīng)用程序是什么樣子的?在這個(gè)非常簡(jiǎn)單的例子中,我們將獲取一個(gè)Bitmap對(duì)象,通過(guò)運(yùn)行一段RS腳本,將其轉(zhuǎn)化為一個(gè)單色調(diào)的Bitmap。在介紹RS腳本之前,先看下應(yīng)用程序的代碼,這段代碼來(lái)自HelloCompute SDK樣例。
(代碼與原文有些改變)
public class MainActivity extends AppCompatActivity { private Bitmap inbmp; private Bitmap outbmp; private ImageView inImage; private ImageView outImage; private RenderScript rs; private Allocation inAlloc; private Allocation outAlloc; private ScriptC_mono script; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); inImage = (ImageView)findViewById(R.id.inimg); outImage = (ImageView)findViewById(R.id.outimg); Resources res = getResources(); inbmp = BitmapFactory.decodeResource(res, R.drawable.a); outbmp = inbmp.copy(Bitmap.Config.ARGB_8888, false); createScript(); outImage.setImageBitmap(outbmp); } private void createScript() { rs = RenderScript.create(this); inAlloc = Allocation.createFromBitmap(rs, inbmp, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); outAlloc = Allocation.createTyped(rs, inAlloc.getType()); script = new ScriptC_mono(rs); script.set_gIn(inAlloc); script.set_gOut(outAlloc); script.set_gScript(script); script.invoke_filter(); outAlloc.copyTo(outbmp); } }
RS應(yīng)用需要的第一個(gè)對(duì)象是context,context是核心對(duì)象用來(lái)創(chuàng)建和管理所有其他的RS對(duì)象。通過(guò)RenderScript.Create創(chuàng)建一個(gè)context對(duì)象。在RS應(yīng)用運(yùn)行期間,context必須一直存在。
下面從Bitmap中創(chuàng)建了兩個(gè)Allocation。RS有自己的存儲(chǔ)分配器,因?yàn)榇鎯?chǔ)空間很可能被多個(gè)處理器共享或者存在于不同的存儲(chǔ)空間中。當(dāng)一個(gè)Allocation創(chuàng)建時(shí),它所有可能的用途需要被列舉出來(lái),這樣系統(tǒng)才能夠選擇正確的存儲(chǔ)來(lái)滿足其用途。
createFromBitmap創(chuàng)建一個(gè)RS Allocation,并將Bitmap內(nèi)容復(fù)制到該Allocation。Allocation是RS應(yīng)用中內(nèi)存使用的單元。createTyped生成了另一個(gè)Allocation與前面生成的有相同的結(jié)構(gòu)。Allocation結(jié)構(gòu)的定義可以通過(guò)getType來(lái)查詢。RS類型定義了Allocation的結(jié)構(gòu)。這個(gè)例子中,RS類型包含了height,width和bitmap的格式。
下面加載了RS腳本,腳本名為mono.rs, 注意ScriptC_mono是根據(jù)mono.rs自動(dòng)生成的java類。
下面3行使用自動(dòng)生成的java類ScriptC_mono設(shè)置腳本的屬性。
現(xiàn)在所有都準(zhǔn)備好了,函數(shù)invoke_filter則是實(shí)際計(jì)算的部分。它觸發(fā)腳本中filter()C函數(shù),這里也可以傳入?yún)?shù)。由于函數(shù)調(diào)用是異步的,返回值在這里是不允許的。
最后一行復(fù)制計(jì)算結(jié)果到另一張Bitmap中,RS機(jī)制內(nèi)部有內(nèi)置的同步機(jī)制,來(lái)確保腳本運(yùn)行完畢才執(zhí)行復(fù)制。
例子: The Script
下面是mono.rs腳本
#pragma version(1) #pragma rs java_package_name(com.example.xubo.hellocompute) rs_allocation gIn; rs_allocation gOut; rs_script gScript; const static float3 gMonoMult = {0.299f, 0.587f, 0.114f}; void root(const uchar4 *v_in, uchar4 *v_out, const void *usrData, uint32_t x, uint32_t y) { float4 f4 = rsUnpackColor8888(*v_in); float3 mono = dot(f4.rgb, gMonoMult); *v_out = rsPackColorTo8888(mono); } void filter() { rsForEach(gScript, gIn, gOut, 0, 0); }
第1行簡(jiǎn)單告訴編譯器使用哪一個(gè)版本的RS API。第2行控制自動(dòng)生成的java代碼。
3個(gè)全局變量列舉了在腳本中使用到的3個(gè)變量,gMonoMult被設(shè)為靜態(tài)。非靜態(tài),const, globals都是允許的,但僅生成一個(gè)get反射方法(???),用來(lái)在同步時(shí)持有靜態(tài)變量。
root()是特別的方法,相當(dāng)于C里面的main函數(shù)。當(dāng)RS被喚醒時(shí),root()將被調(diào)用。也可以傳遞參數(shù)進(jìn)去。在這里參數(shù)分別是傳入的像素和傳出的像素。這里也可以傳遞用戶指針地址和長(zhǎng)度進(jìn)去。我們這里的例子中忽略了指針參數(shù)。
root()函數(shù)中的代碼分別解包RGBA_8888的像素格式到一個(gè)4float的vector中。第2行使用了內(nèi)置的數(shù)學(xué)點(diǎn)積函數(shù),通過(guò)將輸入的像素乘上單色常亮獲得灰度像素。注意點(diǎn)積的返回值是單個(gè)float,這里簡(jiǎn)單使用float3來(lái)接收返回值,點(diǎn)積運(yùn)算的結(jié)果會(huì)分別設(shè)置到float3的x,y,z中。最后我們使用另一個(gè)內(nèi)置函數(shù)來(lái)封裝float3到一個(gè)32位像素中。該例子也說(shuō)明rsPackColorTo8888入?yún)⒖梢栽嘡GB(float3)或者RGBA(float4).如果Alpha沒(méi)有提供,沒(méi)有alpha值是1.0.
filter()函數(shù)會(huì)在java代碼中調(diào)用,它會(huì)在allocation的每個(gè)元素上并行啟動(dòng)計(jì)算。第1個(gè)參數(shù)是需要啟動(dòng)哪個(gè)RS腳本--該腳本的root()函數(shù)將在每個(gè)元素上執(zhí)行。第2個(gè)和第3個(gè)參數(shù)分別表示輸入的allocation和輸出的allocation。最后兩個(gè)參數(shù)是指向用戶數(shù)據(jù)的指針和數(shù)據(jù)長(zhǎng)度。
如果設(shè)備有多個(gè)處理器,forEach函數(shù)將會(huì)在對(duì)個(gè)線程執(zhí)行。將來(lái)forEach可以提供一個(gè)轉(zhuǎn)移點(diǎn),可以在多個(gè)處理器之間進(jìn)行轉(zhuǎn)換。我們的例子中有理由相信filter()函數(shù)將在CPU上執(zhí)行,而root()將可能在GPU或DSP上執(zhí)行。
我希望這個(gè)例子可以粗略探究到RS的設(shè)計(jì)和RS如何簡(jiǎn)單運(yùn)用。
補(bǔ)充的例子代碼和說(shuō)明
RS腳本放在哪里?Eclipse和Android Studio好像不太一樣,以Android Studio為例,放app\src\main\rs中。
運(yùn)行結(jié)果