Damit Sie Jetpack WebGPU verwenden können, muss Ihr Projekt die folgenden Mindestanforderungen erfüllen:
- Mindest-API-Level: Android-API 24 (Nougat) oder höher ist erforderlich.
- Hardware: Geräte, die Vulkan 1.1 oder höher unterstützen, werden für das Backend bevorzugt.
- Kompatibilitätsmodus und OpenGL ES-Unterstützung: Die Verwendung von WebGPU mit dem Kompatibilitätsmodus ist möglich, indem Sie die standardisierte Option
featureLevelaufcompatibilitysetzen, wenn SieGPUAdapteranfordern.
// Example of requesting an adapter with "compatibility" mode enabled:
val adapter = instance.requestAdapter(
GPURequestAdapterOptions(featureLevel = FeatureLevel.Compatibility))
Installation und Einrichtung
Voraussetzungen:
Android Studio: Laden Sie die aktuelle Version von Android Studio von der offiziellen Website herunter und folgen Sie der Installationsanleitung für Android Studio.
Ein neues Projekt erstellen
Nachdem Sie Android Studio installiert haben, führen Sie die folgenden Schritte aus, um Ihr WebGPU-Projekt einzurichten:
- Neues Projekt starten: Öffnen Sie Android Studio und klicken Sie auf Neues Projekt.
Vorlage auswählen: Wählen Sie in Android Studio die Vorlage Empty Activity aus und klicken Sie auf Next.
Abbildung 1.Neues Projekt in Android Studio erstellen Projekt konfigurieren:
- Name: Geben Sie Ihrem Projekt einen Namen, z.B. „JetpackWebGPUSample“).
- Paketname: Prüfen Sie, ob der Paketname mit dem ausgewählten Namespace übereinstimmt (z.B. com.example.webgpuapp).
- Sprache: Wählen Sie Kotlin aus.
- Minimum SDK: Wählen Sie API 24: Android 7.0 (Nougat) oder höher aus, wie für diese Bibliothek empfohlen.
- Build Configuration Language (Sprache für die Build-Konfiguration): Für die moderne Abhängigkeitsverwaltung wird Kotlin DSL (build.gradle.kts) empfohlen.
Abbildung 2.Mit einer leeren Aktivität beginnen Fertigstellen: Klicken Sie auf Fertigstellen und warten Sie, bis Android Studio Ihre Projektdateien synchronisiert hat.
WebGPU-Jetpack-Bibliothek hinzufügen
- Fügen Sie das
google-Repository zusettings.gradlehinzu, wie unter Jetpack-Bibliothek in Ihrer App verwenden beschrieben. - Fügen Sie die Abhängigkeiten für die benötigten Artefakte in die Datei „build.gradle“ für Ihre App oder Ihr Modul ein:
- Hinweis: Die aktuelle Bibliotheksversion finden Sie unter webgpu | Jetpack | Android Developers.
Die Bibliothek androidx.webgpu enthält die .so-Bibliotheksdateien des WebGPU NDK sowie die verwalteten Code-Schnittstellen.
Sie können die Bibliotheksversion aktualisieren, indem Sie Ihre build.gradle-Datei aktualisieren und Ihr Projekt mit Gradle-Dateien synchronisieren. Verwenden Sie dazu die Schaltfläche Projekt synchronisieren in Android Studio.
Gesamtarchitektur
Das WebGPU-Rendering in einer Android-Anwendung wird in einem dedizierten Rendering-Thread ausgeführt, um die Reaktionsfähigkeit der Benutzeroberfläche zu gewährleisten.
- UI-Ebene: Die Benutzeroberfläche wird mit Jetpack Compose erstellt. Eine WebGPU-Zeichenoberfläche wird mit
AndroidExternalSurfacein die Compose-Hierarchie eingebunden. - Rendering-Logik: Eine spezielle Klasse (z.B. WebGpuRenderer) ist für die Verwaltung aller WebGPU-Objekte und die Koordination des Rendering-Loops verantwortlich.
- Shader-Ebene: WGSL-Shader-Code, der in Ressourcen- oder String-Konstanten gespeichert ist.
Schritt-für-Schritt-Anleitung: Beispiel-App
In diesem Abschnitt werden die wichtigsten Schritte beschrieben, die zum Rendern eines farbigen Dreiecks auf dem Bildschirm erforderlich sind. So wird der WebGPU-Kernworkflow veranschaulicht.
Die Hauptaktivität
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WebGpuSurface()
}
}
}
Composable für die äußere Oberfläche
Erstellen Sie eine neue Datei mit dem Namen „WebgpuSurface.kt“. Dieses Composable umschließt das AndroidExternalSurface, um eine Brücke zwischen Compose und Ihrem Renderer zu schaffen.
@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()
}
}
}
}
}
Renderer einrichten
Erstellen Sie einen WebGpuRenderer-Kurs in WebGpuRenderer.kt. Diese Klasse übernimmt die Kommunikation mit der GPU.
Definieren Sie zuerst die Klassenstruktur und die Variablen:
class WebGpuRenderer() {
private lateinit var webGpu: WebGpu
private lateinit var renderPipeline: GPURenderPipeline
}
Initialisierung:Implementieren Sie als Nächstes die init-Funktion, um die WebGPU-Instanz zu erstellen und die Oberfläche zu konfigurieren. Diese Funktion wird vom AndroidExternalSurface-Bereich innerhalb des zusammensetzbaren Elements für die externe Oberfläche aufgerufen, das wir zuvor erstellt haben.
Hinweis:Die init-Funktion verwendet createWebGpu, eine Hilfsmethode (Teil von androidx.webgpu.helper), um die Einrichtung zu vereinfachen. Mit diesem Dienstprogramm wird die WebGPU-Instanz erstellt, ein Adapter ausgewählt und ein Gerät angefordert.
// 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,
)
)
}
Die androidx.webgpu-Bibliothek enthält JNI- und .so-Dateien, die automatisch vom Build-System verknüpft und verwaltet werden. Die Hilfsmethode
createWebGpu lädt das gebündelte libwebgpu_c_bundled.so.
Pipeline einrichten
Nachdem wir ein Gerät haben, müssen wir der GPU mitteilen, wie das Dreieck gezeichnet werden soll. Dazu erstellen wir eine „Pipeline“, die unseren Shader-Code (in WGSL geschrieben) enthält.
Fügen Sie diese private Hilfsfunktion Ihrer WebGpuRenderer-Klasse hinzu, um die Shader zu kompilieren und die Rendering-Pipeline zu erstellen.
// 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)
)
)
}
Rahmen zeichnen
Nachdem die Pipeline fertig ist, können wir jetzt die Render-Funktion implementieren. Mit dieser Funktion wird die nächste verfügbare Textur vom Bildschirm abgerufen, Zeichenbefehle werden aufgezeichnet und an die GPU gesendet.
Fügen Sie diese Methode in Ihre WebGpuRenderer-Klasse ein:
// 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()
}
Ressourcenbereinigung
Implementieren Sie die Bereinigungsfunktion, die von WebGpuSurface aufgerufen wird, wenn die Oberfläche zerstört wird.
// Inside WebGpuRenderer class
fun cleanup() {
if (::webGpu.isInitialized) {
webGpu.close()
}
}
Gerenderte Ausgabe
Beispiel für die Struktur einer App
Es empfiehlt sich, die Rendering-Implementierung von der UI-Logik zu entkoppeln, wie in der Struktur der Beispiel-App:
app/src/main/
├── java/com/example/app/
│ ├── MainActivity.kt // Entry point
│ ├── WebGpuSurface.kt // Composable Surface
│ └── WebGpuRenderer.kt // Pure WebGPU logic
- MainActivity.kt: Der Einstiegspunkt der Anwendung. Damit wird der Inhalt auf die
WebGpuSurface-Composable festgelegt. - WebGpuSurface.kt: Definiert die UI-Komponente mit
[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)). Sie verwaltet den Lebenszyklusbereich vonSurface, initialisiert den Renderer, wenn die Oberfläche bereit ist, und bereinigt ihn, wenn er zerstört wird. - WebGpuRenderer.kt: Kapselt die gesamte WebGPU-spezifische Logik (Geräteerstellung, Pipeline-Einrichtung). Sie ist von der Benutzeroberfläche entkoppelt und empfängt nur die
[Surface](/reference/android/view/Surface.html)und Dimensionen, die zum Zeichnen erforderlich sind.
Lebenszyklus- und Ressourcenverwaltung
Die Lebenszyklusverwaltung wird vom Kotlin-Coroutine-Bereich übernommen, der von [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)) in Jetpack Compose bereitgestellt wird.
- Oberflächenerstellung: Initialisieren Sie die
Device- undSurface-Konfiguration am Anfang desonSurface-Lambda-Blocks. Dieser Code wird sofort ausgeführt, wennSurfaceverfügbar ist. - Zerstörung der Oberfläche: Wenn der Nutzer die Seite verlässt oder der
Surfacevom System zerstört wird, wird der Lambda-Block abgebrochen. Einfinally-Block wird ausgeführt undrenderer.cleanup()wird aufgerufen, um Speicherlecks zu vermeiden. - Größenanpassung: Wenn sich die Abmessungen der Oberfläche ändern, kann
AndroidExternalSurfaceden Block neu starten oder je nach Konfiguration Updates direkt verarbeiten. Der Renderer schreibt also immer in einen gültigen Puffer.
Fehlerbehebung und Validierung
WebGPU bietet Mechanismen zum Validieren von Eingabestrukturen und zum Erfassen von Laufzeitfehlern.
- Logcat:Validierungsfehler werden im Android-Logcat ausgegeben.
- Fehlerbereiche:Sie können bestimmte Fehler erfassen, indem Sie GPU-Befehle in
[device.pushErrorScope()](/reference/kotlin/androidx/webgpu/GPUDevice#pushErrorScope(kotlin.Int))- und device.popErrorScope()-Blöcke einschließen.
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")
}
}
Tipps zur Leistungssteigerung
Beachten Sie beim Programmieren in WebGPU Folgendes, um Leistungsengpässe zu vermeiden:
- Objekterstellung pro Frame vermeiden: Instanziieren Sie Pipelines (
GPURenderPipeline), Bindungsgruppenlayouts und Shadermodule einmal während der Einrichtung der Anwendung, um die Wiederverwendung zu maximieren. - Pufferoptimierung: Aktualisieren Sie den Inhalt vorhandener
GPUBuffersüberGPUQueue.writeBuffer, anstatt für jeden Frame neue Puffer zu erstellen. - Statusänderungen minimieren: Gruppieren Sie Zeichenaufrufe, die dieselbe Pipeline und dieselben Bindungsgruppen verwenden, um den Treiber-Overhead zu minimieren und die Rendering-Effizienz zu verbessern.