WebGPU 使用入门

如需使用 Jetpack WebGPU,您的项目必须满足以下最低要求:

  • Minimum API Level:需要 Android API 24 (Nougat) 或更高级别。
  • 硬件:后端最好使用支持 Vulkan 1.1 及更高版本的设备。
  • 兼容模式和 OpenGL ES 支持:通过在请求 GPUAdapter 时将标准化的 featureLevel 选项设置为 compatibility,即可在兼容模式下使用 WebGPU。
// 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“新建项目”对话框,其中显示了 Studio 将代表您创建的内置 activity 列表。
    图 1. 在 Android Studio 中创建新项目
  3. 配置项目

    • 名称:为项目命名(例如,“JetpackWebGPUSample”)。
    • Package Name:验证软件包名称是否与您选择的命名空间一致(例如 com.example.webgpuapp)。
    • 语言:选择 Kotlin
    • Minimum SDK:选择 API 24: Android 7.0 (Nougat) 或更高版本,这是此库的建议设置。
    • Build Configuration Language:建议使用 Kotlin DSL (build.gradle.kts) 进行现代依赖项管理。
    Android Studio“Empty Activity”对话框,其中包含用于填充新空白 activity 的字段,例如“名称”“软件包名称”“保存位置”和“最低 SDK”。
    图 2. 从空白 activity 开始
  4. 完成:点击 Finish,然后等待 Android Studio 同步您的项目文件。

添加 WebGPU Jetpack 库

androidx.webgpu 库包含 WebGPU NDK .so 库文件以及受管理的接口。

您可以通过更新 build.gradle 并使用 Android Studio 中的“同步项目”按钮将项目与 Gradle 文件同步来更新库版本。

高级架构

Android 应用中的 WebGPU 渲染在专用渲染线程上运行,以保持界面响应速度。

  • 界面层:界面使用 Jetpack Compose 构建。WebGPU 绘制界面使用 AndroidExternalSurface 集成到 Compose 层次结构中。
  • 渲染逻辑:一种专用类(例如,WebGpuRenderer) 负责管理所有 WebGPU 对象并协调渲染循环。
  • 着色器层:存储在 res 或字符串常量中的 WGSL 着色器代码。
高级别架构图,显示了 WebGPU Android 应用中界面线程、专用渲染线程和 GPU 硬件之间的互动。
图 3.Android 上的 WebGPU 高级架构

分步说明:示例应用

本部分将逐步介绍在屏幕上渲染彩色三角形所需的基本步骤,演示核心 WebGPU 工作流。

主要 activity

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

外部界面可组合项

创建一个名为 WebgpuSurface.kt 的新文件。此可组合项封装了 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()
                }
            }
        }
    }
}

设置渲染器

WebGpuRenderer.kt 中创建 WebGpuRenderer 类。此类将负责与 GPU 进行通信的繁重工作。

首先,定义类结构和变量:

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

初始化:接下来,实现 init 函数以创建 WebGPU 实例并配置界面。此函数由我们之前创建的外部界面可组合函数内的 AndroidExternalSurface 作用域调用。

注意:init 函数使用 createWebGpuandroidx.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 在 surface 被销毁时调用。

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

呈现的输出

一张 Android 手机屏幕的屏幕截图,显示了 WebGPU 应用的输出:一个实心红色三角形,居中显示在深蓝色背景上。
图 4.示例 WebGPU 应用的渲染输出,显示一个红色三角形

示例应用结构

最好将渲染实现与界面逻辑分离,如示例应用所用的结构:

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)) 定义界面组件。它管理 Surface 生命周期范围,在 surface 准备就绪时初始化渲染器,并在其销毁时进行清理。
  • WebGpuRenderer.kt:封装了所有特定于 WebGPU 的逻辑(设备创建、流水线设置)。它与界面分离,仅接收绘制所需的 [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 销毁:当用户离开或 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 的内容,而不是每帧都创建新的缓冲区。
  • 尽量减少状态变化:将共享同一流水线的绘制调用和绑定组分组,以尽量减少驱动程序开销并提高渲染效率。