您好,登錄后才能下訂單哦!
剛開始接觸Android圖形上的一些東東,為了學習這里翻譯一篇官網博客上的文章,增加了一些內容,并實現了一下Android RenderScript的一個例子,開發的環境是Android Studio,API 23. RenderScript跟圖形關系也不很大,有一點關系其實就是在GPGPU的概念上。
原文鏈接:http://android-developers.blogspot.tw/2011/03/renderscript.html
RenderScript的設計目標
RS有3個主要的設計目標,以下按照重要性從大到小介紹.
可移植性:應用程序需要能夠運行在不同的設備上,這些有可能是采用的完全不同的硬件。ARM架構現在就有多種不同的硬件--有或者沒有VFP,有或者沒有NEON,不同數量的寄存器。除了ARM,還有X86架構,多種GPU架構,甚至更多的DSP架構。
性能:第2個目標是在滿足可移植的條件下盡可能的提升性能。對于RS而言,我們需要在性能上比現有的解決方案更好。
易用性:第3個目標是盡可能簡化開發。盡可能使用自動化過程來避免耦合的代碼和繁重的工作。
為了實現這3個目標,我們在設計上做了一些權衡。這些權衡首先將RS從現有的Android架構方法中進行了剝離,比如Dalvik或NDK,這些是用來解決不同問題的不同工具。
核心設計的選擇
第一個需要做出的抉擇是采用什么語言?有很多語言可以選擇,其中Shader語言,C或者C++都是可以考慮的。最后我們放棄了Shader,因為Shader需要操作的數據結構跟圖形應用綁定太緊,并且缺少指針和遞歸限制了易用性。C++從一方面來講很不錯但是它的問題是可移植性欠缺。高級的C++特性很難運行在沒有CPU的硬件上。最后我們選擇了C99,因為它提供了提供了跟其他選擇一樣的性能,并且很容易被開發者理解,而且能夠在眾多設備上運行。
另一個權衡是RS的流程。具體說就是如何將源碼轉換成機器碼。我們考慮了多個方案并且在開發RS過程中實現了2種不同的方案。早先版本中(2.3)在設備上編譯C代碼生成機器碼,這樣做有一些好處,例如應用程序可以快速地編譯,但是也卻也帶來了易用性上的問題。必須要先編譯你的App,安裝運行,然后才能發現你的語法錯誤,這是件非常痛苦的事情。而且低端的CPU會限制對代碼的分析和優化。
所以我們轉而考慮LLVM,采用一個修改過的clang版本將腳本的編譯和分析放在開發端。我們在這個階段中進行了高層次的優化,生成LLVM字節碼。從LLVM中間字節碼生成機器碼,依然是在設備上(附帶額外的特定設備的優化)
我們最后一個比較大的權衡是線程的啟動。主要權衡的是性能和可移植性。現有的并行計算方案允許開發者在特定設備上進行調優,但是可能卻會影響其他設備的性能。如果有足夠的時間和資源開發者肯定能夠對所有硬件設備都進行調優。然而測試和調優有的時候卻無法進行,你無法在未發布的硬件上或者你沒有的硬件上進行調優。另一個更可移植的方法是把調優放在運行時,犧牲一點最高性能,而提供足夠優秀的平均性能。考慮到我們的第一目標是可移植,所以我們選擇將調優放在了運行時。
將線程啟動的管理放到運行時帶來的另一個影響是決定在哪里運行你的腳本。例如,有些硬件支持指針和遞歸,有的卻不支持。我們選擇不允許這些事情,而提供給開發者一個最小的通用標準API,我們選擇在運行時對腳本進行分析。這允許開發者能夠最大限度地發揮支持這些特性的硬件,因為總是會認為會有一個全特性的CPU可以依賴。所以,開發者可以聚焦在寫出更好的App,硬件開發商也由于競爭,而制作出更多特性更高性能的硬件。一個新的硬件特性出現,應用程序也不用去修改已有的代碼。
易用性是RS設計中的主要驅動力。大部分現有的并行計算和圖形平臺需要在App中開發復雜的邏輯代碼來實現高性能,這樣的代碼很容易出bug,寫起來也很痛苦。開發端的靜態代碼分析有助于解決這個問題。每個RS腳本生成一個對應的java類。命名和成員都是從RS腳本中提取出來極大簡化了RS腳本的使用。
例子: RS應用
一個簡單的RS應用程序是什么樣子的?在這個非常簡單的例子中,我們將獲取一個Bitmap對象,通過運行一段RS腳本,將其轉化為一個單色調的Bitmap。在介紹RS腳本之前,先看下應用程序的代碼,這段代碼來自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應用需要的第一個對象是context,context是核心對象用來創建和管理所有其他的RS對象。通過RenderScript.Create創建一個context對象。在RS應用運行期間,context必須一直存在。
下面從Bitmap中創建了兩個Allocation。RS有自己的存儲分配器,因為存儲空間很可能被多個處理器共享或者存在于不同的存儲空間中。當一個Allocation創建時,它所有可能的用途需要被列舉出來,這樣系統才能夠選擇正確的存儲來滿足其用途。
createFromBitmap創建一個RS Allocation,并將Bitmap內容復制到該Allocation。Allocation是RS應用中內存使用的單元。createTyped生成了另一個Allocation與前面生成的有相同的結構。Allocation結構的定義可以通過getType來查詢。RS類型定義了Allocation的結構。這個例子中,RS類型包含了height,width和bitmap的格式。
下面加載了RS腳本,腳本名為mono.rs, 注意ScriptC_mono是根據mono.rs自動生成的java類。
下面3行使用自動生成的java類ScriptC_mono設置腳本的屬性。
現在所有都準備好了,函數invoke_filter則是實際計算的部分。它觸發腳本中filter()C函數,這里也可以傳入參數。由于函數調用是異步的,返回值在這里是不允許的。
最后一行復制計算結果到另一張Bitmap中,RS機制內部有內置的同步機制,來確保腳本運行完畢才執行復制。
例子: 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行簡單告訴編譯器使用哪一個版本的RS API。第2行控制自動生成的java代碼。
3個全局變量列舉了在腳本中使用到的3個變量,gMonoMult被設為靜態。非靜態,const, globals都是允許的,但僅生成一個get反射方法(???),用來在同步時持有靜態變量。
root()是特別的方法,相當于C里面的main函數。當RS被喚醒時,root()將被調用。也可以傳遞參數進去。在這里參數分別是傳入的像素和傳出的像素。這里也可以傳遞用戶指針地址和長度進去。我們這里的例子中忽略了指針參數。
root()函數中的代碼分別解包RGBA_8888的像素格式到一個4float的vector中。第2行使用了內置的數學點積函數,通過將輸入的像素乘上單色常亮獲得灰度像素。注意點積的返回值是單個float,這里簡單使用float3來接收返回值,點積運算的結果會分別設置到float3的x,y,z中。最后我們使用另一個內置函數來封裝float3到一個32位像素中。該例子也說明rsPackColorTo8888入參可以試RGB(float3)或者RGBA(float4).如果Alpha沒有提供,沒有alpha值是1.0.
filter()函數會在java代碼中調用,它會在allocation的每個元素上并行啟動計算。第1個參數是需要啟動哪個RS腳本--該腳本的root()函數將在每個元素上執行。第2個和第3個參數分別表示輸入的allocation和輸出的allocation。最后兩個參數是指向用戶數據的指針和數據長度。
如果設備有多個處理器,forEach函數將會在對個線程執行。將來forEach可以提供一個轉移點,可以在多個處理器之間進行轉換。我們的例子中有理由相信filter()函數將在CPU上執行,而root()將可能在GPU或DSP上執行。
我希望這個例子可以粗略探究到RS的設計和RS如何簡單運用。
補充的例子代碼和說明
RS腳本放在哪里?Eclipse和Android Studio好像不太一樣,以Android Studio為例,放app\src\main\rs中。
運行結果
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。