Per utilizzare Jetpack WebGPU, il tuo progetto deve soddisfare i seguenti requisiti minimi:
- Livello API minimo: è richiesto Android API 24 (Nougat) o versioni successive.
- Hardware: per il backend sono preferibili i dispositivi che supportano Vulkan 1.1 o versioni successive.
- Modalità di compatibilità e supporto di OpenGL ES: l'utilizzo di WebGPU con la modalità di compatibilità è possibile impostando l'opzione standardizzata
featureLevelsucompatibilitydurante la richiesta diGPUAdapter.
// Example of requesting an adapter with "compatibility" mode enabled:
val adapter = instance.requestAdapter(
GPURequestAdapterOptions(featureLevel = FeatureLevel.Compatibility))
Installazione e configurazione
Prerequisiti:
Android Studio: scarica l'ultima versione di Android Studio dal sito web ufficiale e segui le istruzioni riportate nella Guida all'installazione di Android Studio.
Creare un nuovo progetto
Una volta installato Android Studio, segui questi passaggi per configurare il tuo progetto WebGPU:
- Avvia un nuovo progetto: apri Android Studio e fai clic su Nuovo progetto.
Seleziona un modello: scegli il modello Attività vuota in Android Studio e fai clic su Avanti.
Figura 1.Creazione di un nuovo progetto in Android Studio Configura il progetto:
- Nome: assegna un nome al progetto (ad es. "JetpackWebGPUSample").
- Nome pacchetto: verifica che il nome del pacchetto corrisponda allo spazio dei nomi scelto (ad es. com.example.webgpuapp).
- Lingua: seleziona Kotlin.
- SDK minimo: seleziona API 24: Android 7.0 (Nougat) o versioni successive, come consigliato per questa libreria.
- Linguaggio di configurazione della build: ti consigliamo di utilizzare Kotlin DSL (build.gradle.kts) per la gestione moderna delle dipendenze.
Figura 2.Inizia con un'attività vuota Fine: fai clic su Fine e attendi che Android Studio sincronizzi i file del progetto.
Aggiungere la libreria Jetpack WebGPU
- Aggiungi il repository
googleasettings.gradlecome descritto in Utilizzare una libreria Jetpack nell'app - Aggiungi le dipendenze per gli artefatti necessari nel file build.gradle per la tua app o il tuo modulo:
- Nota: controlla webgpu | Jetpack | Android Developers per l'ultima versione della libreria
La libreria
androidx.webgpu
contiene i file della libreria .so dell'NDK WebGPU e le interfacce
del codice gestito.
Puoi aggiornare la versione della libreria aggiornando build.gradle e sincronizzando il progetto con i file Gradle utilizzando il pulsante "Sincronizza progetto" in Android Studio.
Architettura di alto livello
Il rendering WebGPU all'interno di un'applicazione Android viene eseguito su un thread di rendering dedicato per mantenere la reattività della UI.
- Livello UI: la UI è creata con Jetpack Compose. Una superficie di disegno WebGPU
è integrata nella gerarchia di Compose utilizzando
AndroidExternalSurface. - Logica di rendering: una classe specializzata (ad es. WebGpuRenderer) è responsabile della gestione di tutti gli oggetti WebGPU e del coordinamento del ciclo di rendering.
- Livello shader: codice shader WGSL archiviato in costanti res o stringa.
Procedura dettagliata: app di esempio
Questa sezione illustra i passaggi essenziali necessari per visualizzare un triangolo colorato sullo schermo, dimostrando il flusso di lavoro principale di WebGPU.
L'attività principale
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WebGpuSurface()
}
}
}
Il componente componibile della superficie esterna
Crea un nuovo file denominato WebgpuSurface.kt. Questo composable racchiude
AndroidExternalSurface per fornire un ponte tra Compose e il tuo renderer.
@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()
}
}
}
}
}
Configurare il renderer
Crea un corso WebGpuRenderer in WebGpuRenderer.kt. Questa classe gestirà il
lavoro pesante di comunicazione con la GPU.
Innanzitutto, definisci la struttura della classe e le variabili:
class WebGpuRenderer() {
private lateinit var webGpu: WebGpu
private lateinit var renderPipeline: GPURenderPipeline
}
Inizializzazione:poi, implementa la funzione init per creare l'istanza WebGPU e configurare la superficie. Questa funzione viene chiamata dall'ambito AndroidExternalSurface all'interno del composable della superficie esterna che abbiamo creato in precedenza.
Nota:la funzione init utilizza createWebGpu, un metodo helper (parte di androidx.webgpu.helper) per semplificare la configurazione. Questa utilità crea l'istanza WebGPU,
seleziona un adattatore e richiede un dispositivo.
// 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,
)
)
}
La libreria androidx.webgpu include file JNI e .so, che vengono
collegati e gestiti automaticamente dal sistema di build. Il metodo helper
createWebGpu si occupa del caricamento di libwebgpu_c_bundled.so in bundle.
Configurazione della pipeline
Ora che abbiamo un dispositivo, dobbiamo dire alla GPU come disegnare il triangolo. A questo scopo, creiamo una "pipeline" che contiene il codice dello shader (scritto in WGSL).
Aggiungi questa funzione helper privata alla classe WebGpuRenderer per compilare gli shader e creare la pipeline di rendering.
// 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)
)
)
}
Disegnare una cornice
Ora che la pipeline è pronta, possiamo implementare la funzione di rendering. Questa funzione acquisisce la successiva texture disponibile dallo schermo, registra i comandi di disegno e li invia alla GPU.
Aggiungi questo metodo alla classe 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()
}
Pulizia delle risorse
Implementa la funzione di pulizia, chiamata da WebGpuSurface quando la
superficie viene eliminata.
// Inside WebGpuRenderer class
fun cleanup() {
if (::webGpu.isInitialized) {
webGpu.close()
}
}
Output visualizzato
Struttura dell'app di esempio
È buona norma separare l'implementazione del rendering dalla logica dell'interfaccia utente, come nella struttura utilizzata dall'app di esempio:
app/src/main/
├── java/com/example/app/
│ ├── MainActivity.kt // Entry point
│ ├── WebGpuSurface.kt // Composable Surface
│ └── WebGpuRenderer.kt // Pure WebGPU logic
- MainActivity.kt: il punto di accesso dell'applicazione. Imposta i contenuti su
WebGpuSurfaceComponibile. - WebGpuSurface.kt: definisce il componente UI utilizzando
[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)). Gestisce l'ambito del ciclo di vitaSurface, inizializzando il renderer quando la superficie è pronta ed eseguendo la pulizia quando viene distrutta. - WebGpuRenderer.kt: incapsula tutta la logica specifica di WebGPU (creazione di dispositivi, configurazione della pipeline). È disaccoppiato dalla UI e riceve solo le
[Surface](/reference/android/view/Surface.html)e le dimensioni necessarie per il disegno.
Gestione del ciclo di vita e delle risorse
La gestione del ciclo di vita viene gestita dall'ambito delle coroutine Kotlin fornito da
[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)) all'interno di Jetpack Compose.
- Creazione della superficie: inizializza la configurazione di
DeviceeSurfaceall'inizio del blocco lambdaonSurface. Questo codice viene eseguito immediatamente quandoSurfacediventa disponibile. - Distruzione della superficie: quando l'utente esce o il
Surfaceviene distrutto dal sistema, il blocco lambda viene annullato. Viene eseguito un bloccofinally, che chiamarenderer.cleanup()per evitare perdite di memoria. - Ridimensionamento: se le dimensioni della superficie cambiano,
AndroidExternalSurfacepotrebbe riavviare il blocco o gestire direttamente gli aggiornamenti a seconda della configurazione, in modo che il renderer scriva sempre in un buffer valido.
Debug e convalida
WebGPU dispone di meccanismi progettati per convalidare le strutture di input e rilevare gli errori di runtime.
- Logcat:gli errori di convalida vengono stampati in Android Logcat.
- Ambiti di errore: puoi acquisire errori specifici racchiudendo i comandi della GPU all'interno dei blocchi
[device.pushErrorScope()](/reference/kotlin/androidx/webgpu/GPUDevice#pushErrorScope(kotlin.Int))e 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")
}
}
Suggerimenti per il rendimento
Quando programmi in WebGPU, considera quanto segue per evitare colli di bottiglia delle prestazioni:
- Evita la creazione di oggetti per frame: crea istanze di pipeline
(
GPURenderPipeline), associa i layout dei gruppi e i moduli shader una sola volta durante la configurazione dell'applicazione per massimizzare il riutilizzo. - Ottimizza l'utilizzo del buffer: aggiorna i contenuti di
GPUBuffersesistenti tramiteGPUQueue.writeBufferanziché creare nuovi buffer per ogni frame. - Ridurre al minimo le modifiche dello stato: raggruppa le chiamate di disegno che condividono la stessa pipeline e associa i gruppi per ridurre al minimo l'overhead del driver e migliorare l'efficienza del rendering.