Mulai menggunakan WebGPU

Untuk menggunakan Jetpack WebGPU, project Anda harus memenuhi persyaratan minimum berikut:

  • Level API Minimum: Android API 24 (Nougat) atau yang lebih tinggi diperlukan.
  • Hardware: Perangkat yang mendukung Vulkan 1.1+ lebih disukai untuk backend.
  • Mode Kompatibilitas dan Dukungan OpenGL ES: Penggunaan WebGPU dengan mode kompatibilitas dapat dilakukan dengan menyetel opsi featureLevel standar ke compatibility saat meminta GPUAdapter.
// Example of requesting an adapter with "compatibility" mode enabled:
val adapter = instance.requestAdapter(
  GPURequestAdapterOptions(featureLevel = FeatureLevel.Compatibility))

Penginstalan & penyiapan

Prasyarat:

Android Studio: Download Android Studio versi terbaru dari situs resmi dan ikuti petunjuk yang diberikan dalam Panduan Penginstalan Android Studio.

Membuat project baru

Setelah Android Studio diinstal, ikuti langkah-langkah berikut untuk menyiapkan project WebGPU Anda:

  1. Mulai Project Baru: Buka Android Studio dan klik New Project.
  2. Pilih template: Pilih template Empty Activity di Android Studio, lalu klik Next.

    Dialog Project Baru Android Studio, yang menampilkan daftar bawaan
    aktivitas yang akan dibuat Studio untuk Anda.
    Gambar 1.Membuat project baru di Android Studio
  3. Konfigurasi project Anda:

    • Nama: Beri nama project Anda (misalnya, "JetpackWebGPUSample").
    • Nama Paket: Verifikasi bahwa nama paket cocok dengan namespace yang Anda pilih (misalnya, com.example.webgpuapp).
    • Language: Pilih Kotlin.
    • SDK Minimum: Pilih API 24: Android 7.0 (Nougat) atau yang lebih tinggi, seperti yang direkomendasikan untuk library ini.
    • Build Configuration Language: Sebaiknya gunakan Kotlin DSL (build.gradle.kts) untuk pengelolaan dependensi modern.
    Dialog Android Studio Empty Activity yang berisi kolom untuk
    mengisi aktivitas kosong baru, seperti Name, Package Name, Save
    Location, dan Minimum SDK.
    Gambar 2.Memulai dengan aktivitas kosong
  4. Selesai: Klik Finish dan tunggu hingga Android Studio menyinkronkan file project Anda.

Menambahkan library Jetpack WebGPU

Library androidx.webgpu berisi file library .so WebGPU NDK serta antarmuka kode terkelola.

Anda dapat memperbarui versi library dengan memperbarui build.gradle dan menyinkronkan project dengan file gradle menggunakan tombol "Sync Project" di Android Studio.

Arsitektur tingkat tinggi

Rendering WebGPU dalam aplikasi Android dijalankan di thread rendering khusus untuk menjaga responsivitas UI.

  • Lapisan UI: UI dibangun dengan Jetpack Compose. Permukaan gambar WebGPU diintegrasikan ke dalam hierarki Compose menggunakan AndroidExternalSurface.
  • Logika Rendering: Class khusus (misalnya, WebGpuRenderer) bertanggung jawab untuk mengelola semua objek WebGPU dan mengoordinasikan loop rendering.
  • Lapisan Shader: Kode shader WGSL yang disimpan dalam konstanta string atau res.
Diagram arsitektur tingkat tinggi yang menunjukkan interaksi antara
    UI Thread, Rendering Thread khusus, dan hardware GPU dalam aplikasi
    Android WebGPU.
Gambar 3.Arsitektur tingkat tinggi WebGPU di Android

Langkah demi langkah: aplikasi contoh

Bagian ini menjelaskan langkah-langkah penting yang diperlukan untuk merender segitiga berwarna di layar, yang menunjukkan alur kerja WebGPU inti.

Aktivitas utama

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

Composable permukaan eksternal

Buat file baru bernama WebgpuSurface.kt. Composable ini membungkus AndroidExternalSurface untuk menyediakan jembatan antara Compose dan perender Anda.

@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()
                }
            }
        }
    }
}

Menyiapkan perender

Buat class WebGpuRenderer di WebGpuRenderer.kt. Class ini akan menangani komunikasi berat dengan GPU.

Pertama, tentukan struktur class dan variabel:

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

Inisialisasi: Selanjutnya, terapkan fungsi init untuk membuat instance WebGPU dan mengonfigurasi permukaan. Fungsi ini dipanggil oleh cakupan AndroidExternalSurface di dalam composable permukaan eksternal yang kita buat sebelumnya.

Catatan: Fungsi init menggunakan createWebGpu, metode helper (bagian dari androidx.webgpu.helper) untuk menyederhanakan penyiapan. Utilitas ini membuat instance WebGPU, memilih adaptor, dan meminta perangkat.

// 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,
      )
    )
  }

Library androidx.webgpu mencakup file JNI dan .so, yang ditautkan dan dikelola secara otomatis oleh sistem build. Metode helper createWebGpu menangani pemuatan libwebgpu_c_bundled.so yang di-bundle.

Penyiapan pipeline

Sekarang setelah memiliki perangkat, kita perlu memberi tahu GPU cara menggambar segitiga. Kita melakukannya dengan membuat "pipeline" yang berisi kode shader (ditulis dalam WGSL).

Tambahkan fungsi helper pribadi ini ke class WebGpuRenderer untuk mengompilasi shader dan membuat pipeline render.

// 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)
      )
    )
  }

Menggambar bingkai

Setelah pipeline siap, kita dapat menerapkan fungsi render. Fungsi ini mendapatkan tekstur berikutnya yang tersedia dari layar, merekam perintah gambar, dan mengirimkannya ke GPU.

Tambahkan metode ini ke class WebGpuRenderer Anda:

// 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()
  }

Pembersihan resource

Terapkan fungsi pembersihan, yang dipanggil oleh WebGpuSurface saat permukaan dihancurkan.

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

Output yang dirender

Screenshot layar ponsel Android yang menampilkan output aplikasi WebGPU: segitiga merah solid yang berada di tengah dengan latar belakang biru tua.
Gambar 4.Output yang dirender dari aplikasi WebGPU sampel yang menampilkan segitiga merah

Struktur aplikasi contoh

Sebaiknya pisahkan penerapan rendering dari logika UI, seperti pada struktur yang digunakan oleh aplikasi contoh:

app/src/main/
├── java/com/example/app/
│   ├── MainActivity.kt       // Entry point
│   ├── WebGpuSurface.kt      // Composable Surface
│   └── WebGpuRenderer.kt     // Pure WebGPU logic
  • MainActivity.kt: Titik entri aplikasi. Menetapkan konten ke Composable WebGpuSurface.
  • WebGpuSurface.kt: Menentukan komponen UI menggunakan [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)). Class ini mengelola cakupan siklus proses Surface, menginisialisasi perender saat permukaan siap dan membersihkan saat dihancurkan.
  • WebGpuRenderer.kt: Merangkum semua logika khusus WebGPU (pembuatan Perangkat, penyiapan Pipeline). Objek ini dipisahkan dari UI, hanya menerima [Surface](/reference/android/view/Surface.html) dan dimensi yang diperlukan untuk menggambar.

Pengelolaan siklus proses & resource

Pengelolaan siklus proses ditangani oleh cakupan Coroutine Kotlin yang disediakan oleh [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)) dalam Jetpack Compose.

  • Pembuatan Permukaan: Lakukan inisialisasi konfigurasi Device dan Surface di awal blok lambda onSurface. Kode ini akan langsung berjalan saat Surface tersedia.
  • Penghancuran Permukaan: Saat pengguna keluar atau Surface dihancurkan oleh sistem, blok lambda dibatalkan. Blok finally dieksekusi, memanggil renderer.cleanup() untuk mencegah kebocoran memori.
  • Mengubah ukuran: Jika dimensi permukaan berubah, AndroidExternalSurface dapat memulai ulang blok atau menangani pembaruan secara langsung, bergantung pada konfigurasi, sehingga perender selalu menulis ke buffer yang valid.

Proses debug & validasi

WebGPU memiliki mekanisme yang dirancang untuk memvalidasi struktur input dan menangkap error runtime.

  • Logcat: Error validasi dicetak ke Android Logcat.
  • Cakupan Error: Anda dapat merekam error tertentu dengan mengapsulasi perintah GPU dalam blok [device.pushErrorScope()](/reference/kotlin/androidx/webgpu/GPUDevice#pushErrorScope(kotlin.Int)) dan `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")
    } 
}

Tips performa

Saat memprogram di WebGPU, pertimbangkan hal berikut untuk menghindari hambatan performa:

  • Hindari Pembuatan Objek Per Frame: Buat instance pipeline (GPURenderPipeline), tata letak grup pengikatan, dan modul shader sekali selama penyiapan aplikasi untuk memaksimalkan penggunaan ulang.
  • Mengoptimalkan Penggunaan Buffer: Perbarui konten GPUBuffers yang ada melalui GPUQueue.writeBuffer, bukan membuat buffer baru setiap frame.
  • Meminimalkan Perubahan Status: Kelompokkan panggilan gambar yang menggunakan pipeline yang sama dan ikat kelompok untuk meminimalkan overhead driver dan meningkatkan efisiensi rendering.