如果工作負載適合 GPU 運算,請將 RenderScript 指令碼遷移至 OpenGL ES (GLES),讓以 Kotlin、Java 或 NDK 編寫的應用程式充分利用 GPU 硬體。概略說明內容,協助您使用 OpenGL ES 3.1 運算著色器取代 RenderScript 指令碼。
GLES 初始化
請勿建立 RenderScript 結構定義物件,而是按照下列步驟,使用 EGL 建立 GLES 畫面外的內容:
取得預設螢幕
使用預設顯示畫面初始化 EGL,指定 GLES 版本。
選擇介面類型為
EGL_PBUFFER_BIT
的 EGL 設定。使用顯示和設定建立 EGL 結構定義。
使用
eglCreatePBufferSurface
建立螢幕外介面。如果結構定義僅用於運算,狀態可能會是很小 (1x1) 的介面。建立轉譯執行緒,並在轉譯執行緒中呼叫螢幕、表面和 EGL 結構定義
eglMakeCurrent
,將 GL 結構定義繫結至執行緒。
範例應用程式示範如何在 GLSLImageProcessor.kt
中初始化 GLES 結構定義。詳情請參閱「EGLSurfaces 和 OpenGL ES」。
GLES 偵錯輸出內容
在 OpenGL 取得實用錯誤時,會使用擴充功能啟用偵錯記錄功能,藉此設定偵錯輸出回呼。從 SDK (glDebugMessageCallbackKHR
) 執行這項操作的方法尚未實作,並擲回例外狀況。範例應用程式包含來自 NDK 程式碼回呼的包裝函式。
GLES 配置
RenderScript 分配可以遷移至不可變儲存空間紋理或著色器儲存空間緩衝區物件。如果是唯讀映像檔,您可以使用範例物件進行篩選。
GLES 資源會在 GLES 中分配。為了避免與其他 Android 元件互動時產生記憶體複製負擔,KHR 映像檔擴充功能允許分享 2D 陣列的圖片資料。搭載 Android 8.0 以上版本的 Android 裝置需要這項擴充功能。graphics-core
Android Jetpack 程式庫支援在代管程式碼中建立這些映像檔,以及將其對應至分配的 HardwareBuffer
:
val outputBuffers = Array(numberOfOutputImages) {
HardwareBuffer.create(
width, height, HardwareBuffer.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
)
}
val outputEGLImages = Array(numberOfOutputImages) { i ->
androidx.opengl.EGLExt.eglCreateImageFromHardwareBuffer(
display,
outputBuffers[i]
)!!
}
遺憾的是,這不會建立不可變更的儲存紋理,讓運算著色器直接寫入緩衝區。這個範例使用 glCopyTexSubImage2D
將運算著色器使用的儲存空間紋理複製到 KHR Image
,如果 OpenGL 驅動程式支援 EGL Image Storage 擴充功能,該擴充功能即可用來建立共用不可變更的儲存紋理,以避免複製。
轉換至 GLSL 運算著色器
您的 RenderScript 指令碼會轉換為 GLSL 運算著色器。
編寫 GLSL 運算著色器
在 OpenGL ES 中,電腦著色器是以 OpenGL 著色語言 (GLSL) 編寫。
調整指令碼全域變數
您可以根據指令碼全域的特性,針對未在著色器中修改的全域使用制服或統一緩衝區物件:
- 統一緩衝區:建議用於頻繁變更且大於推送常數上限的指令碼全域變數。
對於著色器內變更的全域變數,您可以使用不可變更的儲存紋理或著色器儲存空間緩衝區物件。
執行運算作業
運算著色器不屬於圖形管線,屬於一般用途,是專為計算高度平行處理的工作而設計。如此一來,您可以進一步控管這些專案的執行方式,但也代表您必須進一步瞭解工作的平行處理方式。
建立及初始化運算程式
建立和初始化運算程式與使用其他 GLES 著色器時有許多共通點。
建立程式及其相關聯的運算著色器。
附加著色器來源、編譯著色器 (並檢查編譯結果)。
附加著色器、連結程式並使用程式。
製作、初始化及繫結任何制服。
開始運算作業
Compute 著色器會在一系列工作群組 (在著色器原始碼內定義) 中運作的抽象 1D、2D 或 3D 空間,代表著色器的最小叫用大小及幾何圖形。下列著色器適用於 2D 圖片,並在平面上定義工作群組:
private const val WORKGROUP_SIZE_X = 8
private const val WORKGROUP_SIZE_Y = 8
private const val ROTATION_MATRIX_SHADER =
"""#version 310 es
layout (local_size_x = $WORKGROUP_SIZE_X, local_size_y = $WORKGROUP_SIZE_Y, local_size_z = 1) in;
工作群組可以共用由 GL_MAX_COMPUTE_SHARED_MEMORY_SIZE
定義的記憶體。此記憶體至少為 32 KB,且可使用 memoryBarrierShared()
提供一致的記憶體存取權。
定義工作群組大小
即使問題空間在 1 的工作群組大小上也能順利運作,但對平行處理運算著色器而言,設定適當的工作群組大小仍相當重要。舉例來說,如果大小太小,GPU 驅動程式可能無法平行處理運算作業。在理想情況下,這些大小應依據 GPU 調整,不過合理的預設值在目前裝置上的足夠效果 (例如著色器程式碼片段中 8x8 的工作群組大小)。
有 GL_MAX_COMPUTE_WORK_GROUP_COUNT
,但可以相當大量;根據規格,在所有三個軸上,這個值至少須為 65535。
調度著色器
執行運算的最後一個步驟,就是使用其中一個分派函式 (例如 glDispatchCompute
) 分派著色器。分派函式負責設定每個軸的工作群組數量:
GLES31.glDispatchCompute(
roundUp(inputImage.width, WORKGROUP_SIZE_X),
roundUp(inputImage.height, WORKGROUP_SIZE_Y),
1 // Z workgroup size. 1 == only one z level, which indicates a 2D kernel
)
如要傳回此值,請先使用記憶體長條讓運算作業完成:
GLES31.glMemoryBarrier(GLES31.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)
如要將多個核心鏈結在一起 (例如使用 ScriptGroup
遷移程式碼),請建立並分派多個程式,並同步處理這些程式對輸出內容的存取權與記憶體障礙。
範例應用程式示範了兩項運算工作:
- HUE 輪替:具有單一運算著色器的運算工作。如需程式碼範例,請參閱
GLSLImageProcessor::rotateHue
。 - 模糊處理:較為複雜的運算工作,依序執行兩個運算著色器。如需程式碼範例,請參閱
GLSLImageProcessor::blur
。
如要進一步瞭解記憶體障礙,請參閱「確保瀏覽權限」和「共用變數」。