เริ่มต้นใช้งาน WebGPU

หากต้องการใช้ Jetpack WebGPU โปรเจ็กต์ของคุณต้องมีคุณสมบัติตรงตามข้อกำหนดขั้นต่ำต่อไปนี้

  • ระดับ API ขั้นต่ำ: ต้องใช้ Android API 24 (Nougat) ขึ้นไป
  • ฮาร์ดแวร์: เราขอแนะนำให้ใช้แบ็กเอนด์เป็นอุปกรณ์ที่รองรับ Vulkan 1.1 ขึ้นไป
  • โหมดความเข้ากันได้และการรองรับ OpenGL ES: คุณใช้ WebGPU กับโหมดความเข้ากันได้ได้โดยตั้งค่าตัวเลือก featureLevel ที่ได้มาตรฐานเป็น compatibility ขณะขอ GPUAdapter
// 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 แล้วคลิกโปรเจ็กต์ใหม่
  2. เลือกเทมเพลต: เลือกเทมเพลต Empty Activity ใน Android Studio แล้วคลิกถัดไป

    กล่องโต้ตอบโปรเจ็กต์ใหม่ของ Android Studio แสดงรายการกิจกรรมในตัว
    ที่ Studio จะสร้างให้คุณ
    รูปที่ 1 การสร้างโปรเจ็กต์ใหม่ใน Android Studio
  3. กำหนดค่าโปรเจ็กต์

    • ชื่อ: ตั้งชื่อโปรเจ็กต์ (เช่น "JetpackWebGPUSample")
    • ชื่อแพ็กเกจ: ตรวจสอบว่าชื่อแพ็กเกจตรงกับเนมสเปซที่คุณเลือก (เช่น com.example.webgpuapp)
    • ภาษา: เลือก Kotlin
    • SDK ขั้นต่ำ: เลือก API 24: Android 7.0 (Nougat) ขึ้นไปตามที่แนะนำสำหรับไลบรารีนี้
    • ภาษาการกำหนดค่าบิลด์: ขอแนะนำให้ใช้ Kotlin DSL (build.gradle.kts) สำหรับการจัดการ Dependency ที่ทันสมัย
    กล่องโต้ตอบกิจกรรมว่างของ Android Studio ซึ่งมีช่องสำหรับ
    ป้อนข้อมูลกิจกรรมว่างใหม่ เช่น ชื่อ ชื่อแพ็กเกจ ตำแหน่งบันทึก
    และ SDK ขั้นต่ำ
    รูปที่ 2เริ่มต้นด้วยกิจกรรมที่ว่างเปล่า
  4. เสร็จสิ้น: คลิกเสร็จสิ้น แล้วรอให้ Android Studio ซิงค์ไฟล์โปรเจ็กต์

เพิ่มไลบรารี WebGPU Jetpack

  • เพิ่มที่เก็บ google ลงใน settings.gradle ตามที่อธิบายไว้ใน ใช้ไลบรารี Jetpack ในแอป
  • เพิ่มทรัพยากร Dependency สำหรับอาร์ติแฟกต์ที่ต้องการในไฟล์ build.gradle ของแอปหรือโมดูล

ไลบรารี androidx.webgpu มีไฟล์ไลบรารี .so ของ NDK ของ WebGPU รวมถึงอินเทอร์เฟซของโค้ดที่มีการจัดการ

คุณอัปเดตเวอร์ชันของไลบรารีได้โดยการอัปเดต build.gradle และ ซิงค์โปรเจ็กต์กับไฟล์ Gradle โดยใช้ปุ่ม "ซิงค์โปรเจ็กต์" ใน Android Studio

สถาปัตยกรรมระดับสูง

การแสดงผล WebGPU ภายในแอปพลิเคชัน Android จะทำงานบนเธรดการแสดงผลเฉพาะเพื่อรักษาการตอบสนองของ UI

  • เลเยอร์ UI: สร้าง UI ด้วย Jetpack Compose ผสานรวมพื้นผิวการวาดภาพ WebGPU เข้ากับลำดับชั้น Compose โดยใช้ AndroidExternalSurface
  • ตรรกะการแสดงผล: คลาสเฉพาะ (เช่น WebGpuRenderer) มีหน้าที่รับผิดชอบในการจัดการออบเจ็กต์ WebGPU ทั้งหมดและประสานงานลูปการแสดงผล
  • เลเยอร์ Shader: โค้ด Shader ของ WGSL ที่จัดเก็บไว้ในค่าคงที่ res หรือสตริง
แผนภาพสถาปัตยกรรมระดับสูงที่แสดงการโต้ตอบระหว่าง
    UI Thread, Rendering Thread เฉพาะ และฮาร์ดแวร์ GPU ในแอปพลิเคชัน WebGPU
    Android
รูปที่ 3สถาปัตยกรรมระดับสูงของ WebGPU ใน Android

ทีละขั้นตอน: แอปตัวอย่าง

ส่วนนี้จะอธิบายขั้นตอนที่จำเป็นในการแสดงผลสามเหลี่ยมสีบนหน้าจอ ซึ่งแสดงให้เห็นเวิร์กโฟลว์หลักของ 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()
                }
            }
        }
    }
}

ตั้งค่าโปรแกรมแสดงผล

สร้างWebGpuRendererชั้นเรียนใน WebGpuRenderer.kt คลาสนี้จะจัดการ การสื่อสารกับ 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 ซึ่งระบบบิลด์จะลิงก์และจัดการโดยอัตโนมัติ เมธอด Helper createWebGpu จะจัดการการโหลด libwebgpu_c_bundled.so ที่รวมไว้

การตั้งค่าไปป์ไลน์

ตอนนี้เรามีอุปกรณ์แล้ว เราต้องบอก GPU วิธีวาดสามเหลี่ยม เราทำเช่นนี้โดยการสร้าง "ไปป์ไลน์" ที่มีโค้ด Shader (เขียนใน 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 Composable
  • WebGpuSurface.kt: กำหนดคอมโพเนนต์ UI โดยใช้ [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ขอบเขตวงจรของ SurfaceView, เริ่มต้นโปรแกรมแสดงผลเมื่อ Surface พร้อม และล้างข้อมูลเมื่อ Surface ถูกทำลาย
  • WebGpuRenderer.kt: ห่อหุ้มตรรกะทั้งหมดที่เฉพาะเจาะจง WebGPU (การสร้างอุปกรณ์ การตั้งค่าไปป์ไลน์) โดยจะแยกออกจาก UI และรับเฉพาะ[Surface](/reference/android/view/Surface.html) และมิติข้อมูลที่จำเป็นต่อการวาด

การจัดการวงจรและทรัพยากร

การจัดการวงจรของกิจกรรมจะได้รับการจัดการโดยขอบเขตของ Kotlin Coroutine ที่ [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)) จัดให้ภายใน Jetpack Compose

  • การสร้างพื้นผิว: เริ่มต้นการกำหนดค่า Device และ Surface ที่จุดเริ่มต้นของบล็อก Lambda onSurface โค้ดนี้จะทำงานทันทีเมื่อ 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) เลย์เอาต์กลุ่มการเชื่อมโยง และโมดูล Shader เพียงครั้งเดียวในระหว่างการตั้งค่าแอปพลิเคชันเพื่อเพิ่มการนำกลับมาใช้ใหม่ให้ได้มากที่สุด
  • เพิ่มประสิทธิภาพการใช้บัฟเฟอร์: อัปเดตเนื้อหาของ GPUBuffers ที่มีอยู่ผ่าน GPUQueue.writeBuffer แทนที่จะสร้างบัฟเฟอร์ใหม่ในแต่ละเฟรม
  • ลดการเปลี่ยนแปลงสถานะ: จัดกลุ่มการเรียกใช้การวาดที่ใช้ไปป์ไลน์เดียวกันและเชื่อมโยงกลุ่มเพื่อลดค่าใช้จ่ายของไดรเวอร์และปรับปรุงประสิทธิภาพการแสดงผล