開始使用 WebGPU

如要使用 Jetpack WebGPU,專案必須符合下列最低需求:

  • 最低 API 級別:必須為 Android API 24 (Nougat) 以上版本。
  • 硬體:後端最好使用支援 Vulkan 1.1 以上版本的裝置。
  • 相容性模式和 OpenGL ES 支援:如要使用 WebGPU 搭配相容性模式,請在要求 GPUAdapter 時,將標準化 featureLevel 選項設為 compatibility
// Example of requesting an adapter with "compatibility" mode enabled:
val adapter = instance.requestAdapter(
  GPURequestAdapterOptions(featureLevel = FeatureLevel.Compatibility))

安裝與設定

需求條件:

Android Studio:從官方網站下載最新版 Android Studio,然後按照 Android Studio 安裝指南中的說明操作。

建立新專案

安裝 Android Studio 後,請按照下列步驟設定 WebGPU 專案:

  1. 建立新專案:開啟 Android Studio,然後點選「New Project」
  2. 選取範本:在 Android Studio 中選擇「Empty Activity」範本,然後按一下「Next」

    Android Studio 的「New Project」對話方塊,顯示 Studio 會代您建立的內建活動清單。
    圖 1.在 Android Studio 中建立新專案
  3. 設定專案

    • 名稱:為專案命名 (例如 「JetpackWebGPUSample」)。
    • 套件名稱:確認套件名稱與您選擇的命名空間相符 (例如 com.example.webgpuapp)。
    • 「Language」(程式語言):選取「Kotlin」
    • 「Minimum SDK」:選取「API 24: Android 7.0 (Nougat)」以上版本,這是建議用於這個程式庫的版本。
    • 建構設定語言:建議使用 Kotlin DSL (build.gradle.kts) 管理新式依附元件。
    Android Studio 的「Empty Activity」對話方塊,內含可填入新空白活動的欄位,例如「Name」、「Package Name」、「Save Location」和「Minimum SDK」。
    圖 2.從空白活動開始
  4. 完成:按一下「Finish」,然後等待 Android Studio 同步處理專案檔案。

新增 WebGPU Jetpack 程式庫

androidx.webgpu 程式庫包含 WebGPU NDK .so 程式庫檔案,以及受管理程式碼介面。

如要更新程式庫版本,請更新 build.gradle,然後在 Android Studio 中使用「Sync Project」按鈕,將專案與 Gradle 檔案同步。

高階架構

Android 應用程式中的 WebGPU 算繪作業會在專屬的算繪執行緒上執行,以維持 UI 的回應性。

  • 使用者介面層:使用 Jetpack Compose 建構使用者介面。WebGPU 繪圖介面會使用 AndroidExternalSurface 整合至 Compose 階層。
  • 算繪邏輯:專門類別 (例如 WebGpuRenderer) 負責管理所有 WebGPU 物件,並協調算繪迴圈。
  • 著色器層:儲存在 res 或字串常數中的 WGSL 著色器程式碼。
高階架構圖:顯示 WebGPU Android 應用程式中,UI 執行緒、專屬算繪執行緒和 GPU 硬體之間的互動。
圖 3. Android 上的 WebGPU 高階架構

逐步操作說明:範例應用程式

本節將逐步說明在畫面上算繪彩色三角形的必要步驟,示範 WebGPU 的核心工作流程。

主要活動

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            WebGpuSurface()
        }
    }
}

外部介面可組合項

建立名為 WebgpuSurface.kt 的新檔案。這個 Composable 會包裝 AndroidExternalSurface,在 Compose 和算繪器之間提供橋接功能。

@Composable
fun WebGpuSurface(modifier: Modifier = Modifier) {
    // Create and remember a WebGpuRenderer instance.
    val renderer = remember { WebGpuRenderer() }
    AndroidExternalSurface(
        modifier = modifier.fillMaxSize(),
    ) {
        // This block is called when the surface is created or resized.
        onSurface { surface, width, height ->
            // Run the rendering logic on a background thread.
            withContext(Dispatchers.Default) {
                try {
                    // Initialize the renderer with the surface
                    renderer.init(surface, width, height)
                    // Render a frame.
                    renderer.render() 
                } finally {
                    // Clean up resources when the surface is destroyed.
                    renderer.cleanup()
                }
            }
        }
    }
}

設定 Renderer

WebGpuRenderer.kt 中建立 WebGpuRenderer 類別。這個類別會處理與 GPU 通訊的繁重工作。

首先,定義類別結構和變數:

class WebGpuRenderer() {
    private lateinit var webGpu: WebGpu
    private lateinit var renderPipeline: GPURenderPipeline
}

初始化:接著,實作 init 函式,建立 WebGPU 執行個體並設定介面。這個函式是由我們稍早建立的外部介面可組合函式內的 AndroidExternalSurface 範圍呼叫。

注意:init 函式會使用 createWebGpu,這是輔助方法 (androidx.webgpu.helper 的一部分),可簡化設定程序。這個公用程式會建立 WebGPU 執行個體、選取介面卡,並要求裝置。

// Inside WebGpuRenderer class
suspend fun init(surface: Surface, width: Int, height: Int) {
    // 1. Create Instance & Device
    webGpu = createWebGpu(surface)
    val device = webGpu.device

    // 2. Setup Pipeline (compile shaders)
    initPipeline(device)

    // 3. Configure the Surface
    webGpu.webgpuSurface.configure(
      GPUSurfaceConfiguration(
        device,
        width,
        height,
        TextureFormat.RGBA8Unorm,
      )
    )
  }

androidx.webgpu 程式庫包含 JNI 和 .so 檔案,這些檔案會由建構系統自動連結及管理。輔助方法 createWebGpu 負責載入已組合的 libwebgpu_c_bundled.so

管道設定

現在我們有了裝置,需要告訴 GPU 如何繪製三角形。 我們建立的「管道」包含著色器程式碼 (以 WGSL 編寫),即可達成這個目標。

將這個私有輔助函式新增至 WebGpuRenderer 類別,編譯著色器並建立算繪管道。

// Inside WebGpuRenderer class
private fun initPipeline(device: GPUDevice) {
    val shaderCode = """
        @vertex fn vs_main(@builtin(vertex_index) vertexIndex : u32) ->
        @builtin(position) vec4f {
            const pos = array(vec2f(0.0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));
            return vec4f(pos[vertexIndex], 0, 1);
        }
        @fragment fn fs_main() -> @location(0) vec4f {
            return vec4f(1, 0, 0, 1);
        }
    """

    // Create Shader Module
    val shaderModule = device.createShaderModule(
      GPUShaderModuleDescriptor(shaderSourceWGSL = GPUShaderSourceWGSL(shaderCode))
    )

    // Create Render Pipeline
    renderPipeline = device.createRenderPipeline(
      GPURenderPipelineDescriptor(
        vertex = GPUVertexState(
          shaderModule,
        ), fragment = GPUFragmentState(
          shaderModule, targets = arrayOf(GPUColorTargetState(TextureFormat.RGBA8Unorm))
        ), primitive = GPUPrimitiveState(PrimitiveTopology.TriangleList)
      )
    )
  }

繪製影格

管道準備就緒後,我們現在可以實作算繪函式。這個函式會從畫面取得下一個可用紋理、記錄繪圖指令,並將這些指令提交至 GPU。

將這個方法新增至 WebGpuRenderer 類別:

// Inside WebGpuRenderer class
fun render() {
    if (!::webGpu.isInitialized) {
      return
    }

    val gpu = webGpu

    // 1. Get the next available texture from the screen
    val surfaceTexture = gpu.webgpuSurface.getCurrentTexture()

    // 2. Create a command encoder
    val commandEncoder = gpu.device.createCommandEncoder()

    // 3. Begin a render pass (clearing the screen to blue)
    val renderPass = commandEncoder.beginRenderPass(
      GPURenderPassDescriptor(
        colorAttachments = arrayOf(
          GPURenderPassColorAttachment(
            GPUColor(0.0, 0.0, 0.5, 1.0),
            surfaceTexture.texture.createView(),
            loadOp = LoadOp.Clear,
            storeOp = StoreOp.Store,
          )
        )
      )
    )

    // 4. Draw
    renderPass.setPipeline(renderPipeline)
    renderPass.draw(3) // Draw 3 vertices
    renderPass.end()

    // 5. Submit and Present
    gpu.device.queue.submit(arrayOf(commandEncoder.finish()))
    gpu.webgpuSurface.present()
  }

清除資源

實作清除函式,當表面遭到刪除時,WebGpuSurface 會呼叫該函式。

// Inside WebGpuRenderer class
fun cleanup() {
    if (::webGpu.isInitialized) {
      webGpu.close()
    }
  }

顯示的輸出內容

Android 手機螢幕截圖,顯示 WebGPU 應用程式的輸出內容:深藍色背景中央有一個實心紅色三角形。
圖 4:範例 WebGPU 應用程式的算繪輸出內容,顯示紅色三角形

範例應用程式結構

建議您將算繪實作項目與 UI 邏輯分離,如範例應用程式所用的結構:

app/src/main/
├── java/com/example/app/
│   ├── MainActivity.kt       // Entry point
│   ├── WebGpuSurface.kt      // Composable Surface
│   └── WebGpuRenderer.kt     // Pure WebGPU logic
  • MainActivity.kt:應用程式進入點。這會將內容設為 WebGpuSurface 可組合項。
  • WebGpuSurface.kt:使用 [AndroidExternalSurface](/reference/kotlin/androidx/compose/foundation/package-summary#AndroidExternalSurface(androidx.compose.ui.Modifier,kotlin.Boolean,androidx.compose.ui.unit.IntSize,androidx.compose.foundation.AndroidExternalSurfaceZOrder,kotlin.Boolean,kotlin.Function1)) 定義 UI 元件。這個類別會管理 Surface 生命週期範圍,在表面準備就緒時初始化算繪器,並在表面遭到刪除時進行清理。
  • WebGpuRenderer.kt:封裝所有 WebGPU 專屬邏輯 (裝置建立、管道設定)。與 UI 解耦,只接收繪製所需的 [Surface](/reference/android/view/Surface.html) 和尺寸。

生命週期與資源管理

生命週期管理作業是由 Jetpack Compose 內 [AndroidExternalSurface](/reference/kotlin/androidx/compose/foundation/package-summary#AndroidExternalSurface(androidx.compose.ui.Modifier,kotlin.Boolean,androidx.compose.ui.unit.IntSize,androidx.compose.foundation.AndroidExternalSurfaceZOrder,kotlin.Boolean,kotlin.Function1)) 提供的 Kotlin 協同程式範圍處理。

  • 介面建立:在 onSurface lambda 區塊的開頭初始化 DeviceSurface 設定。Surface 可用時,這段程式碼會立即執行。
  • 介面終止:使用者離開或系統終止 Surface 時,系統會取消 lambda 區塊。系統會執行 finally 區塊,呼叫 renderer.cleanup() 以防止記憶體流失。
  • 調整大小:如果表面尺寸變更,AndroidExternalSurface 可能會重新啟動區塊或直接處理更新 (視設定而定),因此算繪器一律會寫入有效的緩衝區。

偵錯與驗證

WebGPU 具有驗證輸入結構和擷取執行階段錯誤的機制。

  • Logcat:驗證錯誤會列印到 Android Logcat。
  • 錯誤範圍:您可以將 GPU 指令封裝在 [device.pushErrorScope()](/reference/kotlin/androidx/webgpu/GPUDevice#pushErrorScope(kotlin.Int)) 和 `device.popErrorScope() 區塊中,擷取特定錯誤。
device.pushErrorScope(ErrorFilter.Validation)
// ... potentially incorrect code ...
device.popErrorScope { status, type, message ->
    if (status == PopErrorScopeStatus.Success && type != ErrorType.NoError) {
        Log.e("WebGPU", "Validation Error: $message")
    } 
}

效能提升秘訣

使用 WebGPU 程式設計時,請注意下列事項,避免效能瓶頸:

  • 避免在每個影格建立物件:在應用程式設定期間,將管道 (GPURenderPipeline)、繫結群組版面配置和著色器模組執行個體化一次,盡量重複使用。
  • 最佳化緩衝區用量:透過 GPUQueue.writeBuffer 更新現有 GPUBuffers 的內容,而非在每個影格建立新的緩衝區。
  • 減少狀態變化:將共用相同管道的繪圖呼叫分組,並繫結群組,以盡量減少驅動程式的負擔,並提升算繪效率。