溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Hello! RenderScript

發(fā)布時(shí)間:2020-07-16 14:33:36 來(lái)源:網(wǎng)絡(luò) 閱讀:1484 作者:xubonjucs 欄目:移動(dòng)開(kāi)發(fā)

剛開(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的概念上。


原文鏈接: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)是在滿(mǎn)足可移植的條件下盡可能的提升性能。對(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)衡是線(xià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í)。


將線(xiàn)程啟動(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可以依賴(lài)。所以,開(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類(lèi)。命名和成員都是從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)滿(mǎn)足其用途。


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)查詢(xún)。RS類(lèi)型定義了Allocation的結(jié)構(gòu)。這個(gè)例子中,RS類(lèi)型包含了height,width和bitmap的格式。


下面加載了RS腳本,腳本名為mono.rs, 注意ScriptC_mono是根據(jù)mono.rs自動(dòng)生成的java類(lèi)。

下面3行使用自動(dòng)生成的java類(lèi)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ù)分別是傳入的像素和傳出的像素。這里也可以傳遞用戶(hù)指針地址和長(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ù)是指向用戶(hù)數(shù)據(jù)的指針和數(shù)據(jù)長(zhǎng)度。


如果設(shè)備有多個(gè)處理器,forEach函數(shù)將會(huì)在對(duì)個(gè)線(xiàn)程執(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é)果

Hello! RenderScript


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI