WebGPU のスタートガイド

Jetpack WebGPU を使用するには、プロジェクトが次の最小要件を満たしている必要があります。

  • Minimum API Level: Android API 24(Nougat)以上が必要です。
  • ハードウェア: バックエンドには Vulkan 1.1+ をサポートするデバイスが推奨されます。
  • 互換モードと OpenGL ES のサポート: 標準化された featureLevel オプションを compatibility に設定し、GPUAdapter をリクエストすることで、互換モードで 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 の [New Project] ダイアログ。Studio がユーザーに代わって作成するアクティビティの組み込みリストが表示されています。
    図 1.Android Studio で新しいプロジェクトを作成する
  3. プロジェクトを構成する:

    • 名前: プロジェクトに名前を付けます(例: 「JetpackWebGPUSample」)。
    • パッケージ名: パッケージ名が選択した名前空間(com.example.webgpuapp など)と一致していることを確認します。
    • 言語: [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. 完了: [完了] をクリックし、Android Studio がプロジェクト ファイルを同期するまで待ちます。

WebGPU Jetpack ライブラリを追加

androidx.webgpu ライブラリには、WebGPU NDK の .so ライブラリ ファイルとマネージド コード インターフェースが含まれています。

ライブラリのバージョンを更新するには、build.gradle を更新し、Android Studio の [Sync Project] ボタンを使用してプロジェクトを Gradle ファイルと同期します。

上位レベルのアーキテクチャ

Android アプリケーション内の WebGPU レンダリングは、UI の応答性を維持するために専用のレンダリング スレッドで実行されます。

  • UI レイヤ: 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 という名前の新しいファイルを作成します。このコンポーザブルは 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.ktWebGpuRenderer クラスを作成します。このクラスは、GPU との通信の重い処理を処理します。

まず、クラス構造と変数を定義します。

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

初期化: 次に、init 関数を実装して WebGPU インスタンスを作成し、サーフェスを構成します。この関数は、先ほど作成した外部サーフェス コンポーザブル内の AndroidExternalSurface スコープによって呼び出されます。

注: init 関数は、セットアップを効率化するために、ヘルパー メソッド(androidx.webgpu.helper の一部)である createWebGpu を使用します。このユーティリティは、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()
    }
  }

レンダリングされた出力

WebGPU アプリケーションの出力が表示された Android スマートフォンの画面のスクリーンショット。暗い青色の背景の中央に赤い三角形が描画されている。
図 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 ラムダ ブロックの先頭で DeviceSurface の構成を初期化します。このコードは、Surface が利用可能になるとすぐに実行されます。
  • Surface の破棄: ユーザーが移動するか、システムによって Surface が破棄されると、ラムダブロックがキャンセルされます。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)、バインド グループ レイアウト、シェーダー モジュールを 1 回インスタンス化して、再利用を最大化します。
  • バッファの使用を最適化: 各フレームで新しいバッファを作成するのではなく、GPUQueue.writeBuffer を介して既存の GPUBuffers の内容を更新します。
  • 状態変化を最小限に抑える: 同じパイプラインを共有するドローコールをグループ化し、グループをバインドして、ドライバのオーバーヘッドを最小限に抑え、レンダリング効率を向上させます。