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 プロジェクトをセットアップします。
- 新しいプロジェクトを開始する: Android Studio を開き、[New Project] をクリックします。
テンプレートを選択する: Android Studio で [Empty Activity] テンプレートを選択し、[Next] をクリックします。
図 1.Android Studio で新しいプロジェクトを作成する プロジェクトを構成する:
- 名前: プロジェクトに名前を付けます(例: 「JetpackWebGPUSample」)。
- パッケージ名: パッケージ名が選択した名前空間(com.example.webgpuapp など)と一致していることを確認します。
- 言語: [Kotlin] を選択します。
- Minimum SDK: このライブラリで推奨されている API 24: Android 7.0(Nougat)以上を選択します。
- ビルド構成言語: 最新の依存関係管理には、Kotlin DSL(build.gradle.kts)を使用することをおすすめします。
図 2.空のアクティビティから始める 完了: [完了] をクリックし、Android Studio がプロジェクト ファイルを同期するまで待ちます。
WebGPU Jetpack ライブラリを追加
- アプリで Jetpack ライブラリを使用するの説明に従って、
googleリポジトリをsettings.gradleに追加します。 - アプリまたはモジュールの build.gradle ファイルに、必要なアーティファクトの依存関係を追加します。
- 注: 最新のライブラリ バージョンについては、webgpu | Jetpack | Android デベロッパーをご覧ください。
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 のコア ワークフローを示します。
メイン アクティビティ
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 関数は、セットアップを効率化するために、ヘルパー メソッド(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()
}
}
レンダリングされた出力
サンプルアプリの構造
サンプルアプリで使用されている構造のように、レンダリング実装を 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ラムダ ブロックの先頭でDeviceとSurfaceの構成を初期化します。このコードは、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の内容を更新します。 - 状態変化を最小限に抑える: 同じパイプラインを共有するドローコールをグループ化し、グループをバインドして、ドライバのオーバーヘッドを最小限に抑え、レンダリング効率を向上させます。