RenderScript:Android平臺上高性能圖形計(jì)算框架
RenderScript介紹
RenderScript是一種高性能的計(jì)算框架,用于在設(shè)備上執(zhí)行復(fù)雜的數(shù)學(xué)計(jì)算,尤其是在圖像處理方面。最初是為了解決Android平臺上圖形和計(jì)算密集型任務(wù)而設(shè)計(jì)的,可以在CPU或GPU上并行執(zhí)行,以利用多核處理器和圖形硬件的加速能力。
RenderScript的主要特點(diǎn):
- 并行計(jì)算:RenderScript利用設(shè)備的多核處理器進(jìn)行并行計(jì)算,從而顯著提高計(jì)算密集型任務(wù)的性能。
- 硬件加速:RenderScript能夠利用GPU進(jìn)行硬件加速計(jì)算,適用于圖形和圖像處理任務(wù)。
- 易于使用:RenderScript使用C99樣式的語法,Android SDK提供了方便的Java/Kotlin綁定,使得可以在Android應(yīng)用中輕松集成RenderScript代碼。
- 性能優(yōu)化:RenderScript編譯器會自動(dòng)優(yōu)化代碼,以充分利用目標(biāo)設(shè)備的硬件特性。
RenderScript使用場景:
- 圖像處理:如模糊、銳化、顏色轉(zhuǎn)換等。
- 計(jì)算機(jī)視覺:如特征檢測、邊緣識別等。
- 物理模擬:如碰撞檢測、粒子系統(tǒng)等。
隨著Android平臺的不斷發(fā)展,一些新的API和框架(如Android的NDK,Vulkan和RenderEffect(Android12))也提供了類似的性能優(yōu)化能力。
Google在Android開發(fā)者博客中提到,其實(shí)早已不建議將RenderScript用于對性能需求至關(guān)重要的任務(wù),Android開發(fā)者應(yīng)該轉(zhuǎn)向可在GPU硬件層級上高效運(yùn)作、且具有出色的跨平臺體驗(yàn)的Vulkan API 。
RenderScript腳本
RenderScript .rs 腳本使用一種類似于C99的語法,允許開發(fā)者定義內(nèi)核函數(shù),這些函數(shù)可以在Android設(shè)備上高效地并行執(zhí)行。.rs 腳本的基本語法說明:
1.腳本頭
在腳本的開頭,通常會看到一些預(yù)處理指令,用于定義腳本的版本和Java包名:
#pragma version(1)
#pragma rs java_package_name(com.reathin.renderscript)
- #pragma version(1):指定RenderScript的版本。
- #pragma rs java_package_name(...):指定生成的Java類的包名。
2.數(shù)據(jù)類型
RenderScript使用了一些特殊的數(shù)據(jù)類型,如rs_allocation,是用于訪問內(nèi)存分配的引用類型。還有uchar4、float4等類型,用于表示包含四個(gè)無符號字符或浮點(diǎn)數(shù)的向量。
3.內(nèi)核函數(shù)
內(nèi)核函數(shù)是RenderScript腳本中的主要部分,定義了要在GPU或CPU上執(zhí)行的并行計(jì)算。
void blur(const uchar4 *v_in, uchar4 *v_out, const void *usrData, uint32_t x, uint32_t y) {
// 內(nèi)核函數(shù)的實(shí)現(xiàn)
}
- void blur(...):定義了一個(gè)名為blur的內(nèi)核函數(shù)。
- const uchar4 *v_in 和 uchar4 *v_out:是輸入和輸出參數(shù)的指針。
- const void *usrData:是傳遞給內(nèi)核的任意用戶數(shù)據(jù)。
- uint32_t x, uint32_t y:是內(nèi)核的當(dāng)前執(zhí)行位置(例如,像素坐標(biāo))。
4.根函數(shù)
根函數(shù)是RenderScript腳本的入口點(diǎn)。當(dāng)RenderScript運(yùn)行時(shí)加載腳本時(shí),會調(diào)用根函數(shù)。
void root() {
// 初始化代碼或調(diào)用其他內(nèi)核函數(shù)
}
5.訪問全局變量
在.rs腳本中,可以定義全局變量,并在內(nèi)核函數(shù)中訪問它們。
float mGlobalData;
void blur(...) {
// 使用 mGlobalData
}
6.調(diào)用其他內(nèi)核
可以在根函數(shù)或其他內(nèi)核函數(shù)中調(diào)用其他內(nèi)核。
void blur(...) {
// 另一個(gè)內(nèi)核的實(shí)現(xiàn)
}
void root() {
// 調(diào)用另一個(gè)內(nèi)核
blur(...);
}
7.分配和訪問內(nèi)存
RenderScript使用Allocation對象來管理內(nèi)存。在Java代碼中,會創(chuàng)建Allocation對象,并將其傳遞給RenderScript內(nèi)核。在.rs腳本中,可以使用rsGetAllocationAddress函數(shù)來獲取指向這些分配的內(nèi)存的指針。
const uchar4 *in = rsGetAllocationAddress(inAllocation);
uchar4 *out = rsGetAllocationAddress(outAllocation);
8.內(nèi)置函數(shù)和API
RenderScript提供了一些內(nèi)置函數(shù)和API,用于執(zhí)行常見的操作,如數(shù)學(xué)運(yùn)算、內(nèi)存操作、類型轉(zhuǎn)換等。
9.編譯和鏈接
.rs 腳本文件在構(gòu)建過程中會被編譯成字節(jié)碼,并與應(yīng)用一起打包。當(dāng)應(yīng)用運(yùn)行時(shí),RenderScript運(yùn)行時(shí)會加載這些字節(jié)碼,并在適當(dāng)?shù)挠布蠄?zhí)行它們。
10.注意事項(xiàng)
- 確保你的RenderScript腳本遵循Android NDK的C/C++編碼規(guī)范。
- 由于RenderScript是并行執(zhí)行的,要避免在內(nèi)核函數(shù)中使用可能導(dǎo)致競態(tài)條件的全局變量或狀態(tài)。
- 對于涉及復(fù)雜計(jì)算或大量數(shù)據(jù)的任務(wù),RenderScript可以顯著提高性能,也要小心管理內(nèi)存和避免不必要的復(fù)制操作。
使用RenderScript實(shí)現(xiàn)模糊效果
在Android中實(shí)現(xiàn)模糊效果通常使用RenderScript或者自定義的OpenGL著色器。RenderScript和OpenGL都是相對底層的API,需要一定的圖形編程知識。對于更簡單的模糊效果,可以考慮使用第三方庫,如Glide或Picasso,提供了更高級的API來實(shí)現(xiàn)圖像模糊和其他效果。
下面使用RenderScript實(shí)現(xiàn)模糊效果。
添加RenderScript支持:
android {
//高版本Studio
buildFeatures {
renderScript true
}
defaultConfig {
renderscriptTargetApi 21
renderscriptSupportModeEnabled true
}
}
使用RenderScript實(shí)現(xiàn)圖片高斯模糊效果
public static Bitmap scriptBlur(Context context, Bitmap origin, int outWidth, int outHeight, float radius) {
if (origin == null || origin.isRecycled()) {
return null;
}
mStartTime = System.currentTimeMillis();
RenderScript renderScript = RenderScript.create(context.getApplicationContext(), RenderScript.ContextType.NORMAL, RenderScript.CREATE_FLAG_NONE);
Allocation blurInput = Allocation.createFromBitmap(renderScript, origin);
Allocation blurOutput = Allocation.createTyped(renderScript, blurInput.getType());
ScriptIntrinsicBlur blur = null;
try {
blur = ScriptIntrinsicBlur.create(renderScript, blurInput.getElement());
} catch (RSIllegalArgumentException e) {
if (e.getMessage().contains("Unsuported element type")) {
blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
}
}
if (blur == null) {
//腳本模糊失敗
return null;
}
blur.setRadius(range(radius, 0, 20));
blur.setInput(blurInput);
blur.forEach(blurOutput);
Bitmap result = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888);
blurOutput.copyTo(result);
//釋放
renderScript.destroy();
blurInput.destroy();
blurOutput.destroy();
origin.recycle();
long time = (System.currentTimeMillis() - mStartTime);
Log.i("BlurUtils", "模糊用時(shí):[" + time + "ms]");
return result;
}
調(diào)用高斯模糊方法
val bitmap = BitmapFactory.decodeResource(resources, R.mipmap.image_beauty)
val bitmap1 = BlurUtils.scriptBlur(this, bitmap, bitmap.getWidth(), bitmap.getHeight(), 10f)
val bitmap2 = BlurUtils.scriptBlur(this, bitmap, bitmap.getWidth(), bitmap.getHeight(), 15f)
val bitmap3 = BlurUtils.scriptBlur(this, bitmap, bitmap.getWidth(), bitmap.getHeight(), 20f)
val bitmap4 = BlurUtils.scriptBlur(this, bitmap, bitmap.getWidth(), bitmap.getHeight(), 25f)
imageView1.setImageBitmap(bitmap)
imageView2.setImageBitmap(bitmap2)
imageView3.setImageBitmap(bitmap3)
imageView4.setImageBitmap(bitmap4)
運(yùn)行效果:
在Android中,RenderScript .rs 腳本是一種用C99語法編寫的文件,定義了可以在設(shè)備上并行執(zhí)行的計(jì)算任務(wù)。通常用于圖形和計(jì)算密集型任務(wù),例如圖像處理、物理模擬等。.rs 腳本編譯后會生成二進(jìn)制代碼,代碼可以在Android設(shè)備的CPU或GPU上執(zhí)行。
Android Studio創(chuàng)建rs腳本目錄:
(1) 編寫.rs腳本
#pragma version(1)
#pragma rs java_package_name(com.reathin.renderscript)
// 輸入圖像
rs_allocation inImage;
// 輸出圖像
rs_allocation outImage;
// 模糊半徑
float blurRadius;
// 定義模糊函數(shù)
void blur(const uchar4 *v_in, uchar4 *v_out, const void *usrData, uint32_t x, uint32_t y) {
// 計(jì)算模糊后的顏色值
float4 sum = 0.0f;
int count = 0;
for (float dx = -blurRadius; dx <= blurRadius; dx++) {
for (float dy = -blurRadius; dy <= blurRadius; dy++) {
int newX = x + (int)dx;
int newY = y + (int)dy;
if (newX >= 0 && newX < rsAllocationGetDimX(inImage) && newY >= 0 && newY < rsAllocationGetDimY(inImage)) {
sum += rsUnpackColor8888(*v_in + rsAllocationGetElementPtr(inImage, newX, newY));
count++;
}
}
}
*v_out = rsPackColor8888(sum / count);
}
// 根函數(shù),RenderScript執(zhí)行時(shí)的入口點(diǎn)
void root() {
// 獲取輸入和輸出圖像的指針
const uchar4 *in = rsGetAllocationAddress(inImage);
uchar4 *out = rsGetAllocationAddress(outImage);
// 執(zhí)行模糊操作
blur(in, out, NULL, 0, 0);
}
將 .rs 文件放在項(xiàng)目的 src/main/rs 目錄下。在Java或Kotlin代碼中加載這個(gè)腳本并設(shè)置輸入和輸出 Allocation 對象,最后調(diào)用RenderScript的內(nèi)核來執(zhí)行模糊操作。
(2) 調(diào)用RenderScript內(nèi)核進(jìn)行調(diào)用
import android.content.Context;
import android.graphics.Bitmap;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptC;
public Bitmap applyBlur(Context context, Bitmap inputBitmap, float blurRadius) {
// 創(chuàng)建RenderScript實(shí)例
RenderScript rs = RenderScript.create(context);
// 創(chuàng)建輸入和輸出Allocation
Allocation inputAllocation = Allocation.createFromBitmap(rs, inputBitmap);
Allocation outputAllocation = Allocation.createTyped(rs, inputAllocation.getType());
// 加載RenderScript腳本
ScriptC_Blur blurScript = new ScriptC_Blur(rs);
blurScript.set_inImage(inputAllocation);
blurScript.set_outImage(outputAllocation);
blurScript.set_blurRadius(blurRadius);
// 執(zhí)行模糊操作
blurScript.invoke_root();
// 創(chuàng)建輸出位圖并復(fù)制數(shù)據(jù)
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap.getWidth(), inputBitmap.getHeight(), inputBitmap.getConfig());
outputAllocation.copyTo(outputBitmap);
// 銷毀資源和清理
inputAllocation.destroy();
outputAllocation.destroy();
rs.destroy();
return outputBitmap;
}
ScriptC_Blur是根據(jù).rs腳本文件自動(dòng)生成的類。需要用實(shí)際的類名替換ScriptC_Blur應(yīng)該與.rs文件名相同(去掉.rs擴(kuò)展名,并將下劃線_替換為駝峰命名法的大寫字母)。
完整示例代碼: https://github.com/Reathin/Sample-Android