Pour utiliser Jetpack WebGPU, votre projet doit répondre aux exigences minimales suivantes :
- Niveau d'API minimal : Android API 24 (Nougat) ou version ultérieure est requis.
- Matériel : les appareils compatibles avec Vulkan 1.1 ou version ultérieure sont préférables pour le backend.
- Mode Compatibilité et compatibilité avec OpenGL ES : il est possible d'utiliser WebGPU avec le mode Compatibilité en définissant l'option standardisée
featureLevelsurcompatibilitylors de la demande deGPUAdapter.
// Example of requesting an adapter with "compatibility" mode enabled:
val adapter = instance.requestAdapter(
GPURequestAdapterOptions(featureLevel = FeatureLevel.Compatibility))
Installation et configuration
Prérequis :
Android Studio : téléchargez la dernière version d'Android Studio sur le site Web officiel et suivez les instructions du Guide d'installation d'Android Studio.
Créer un projet
Une fois Android Studio installé, procédez comme suit pour configurer votre projet WebGPU :
- Démarrer un nouveau projet : ouvrez Android Studio, puis cliquez sur Nouveau projet.
Sélectionnez un modèle : choisissez le modèle Empty Activity (Activité vide) dans Android Studio, puis cliquez sur Next (Suivant).
Figure 1 : Créer un projet dans Android Studio Configurez votre projet :
- Nom : donnez un nom à votre projet (par exemple, "JetpackWebGPUSample").
- Package Name (Nom du package) : vérifiez que le nom du package correspond à l'espace de noms choisi (par exemple, com.example.webgpuapp).
- Langage : sélectionnez Kotlin.
- Minimum SDK (SDK minimal) : sélectionnez API 24 : Android 7.0 (Nougat) ou une version ultérieure, comme recommandé pour cette bibliothèque.
- Langage de configuration de compilation : il est recommandé d'utiliser Kotlin DSL (build.gradle.kts) pour la gestion moderne des dépendances.
Figure 2 : commencer avec une activité vide Terminer : cliquez sur Terminer et attendez qu'Android Studio synchronise les fichiers de votre projet.
Ajouter la bibliothèque Jetpack WebGPU
- Ajoutez le dépôt
googleàsettings.gradle, comme décrit dans Utiliser une bibliothèque Jetpack dans votre application. - Ajoutez les dépendances des artefacts dont vous avez besoin dans le fichier build.gradle de votre application ou module :
- Remarque : Consultez webgpu | Jetpack | Développeurs Android pour connaître la dernière version de la bibliothèque.
La bibliothèque androidx.webgpu contient les fichiers de bibliothèque .so du NDK WebGPU ainsi que les interfaces de code géré.
Vous pouvez mettre à jour la version de la bibliothèque en modifiant votre fichier build.gradle et en synchronisant votre projet avec les fichiers Gradle à l'aide du bouton Synchroniser le projet dans Android Studio.
Architecture de haut niveau
Le rendu WebGPU dans une application Android est exécuté sur un thread de rendu dédié pour maintenir la réactivité de l'UI.
- Couche d'interface utilisateur : l'UI est créée avec Jetpack Compose. Une surface de dessin WebGPU est intégrée à la hiérarchie Compose à l'aide de
AndroidExternalSurface. - Logique de rendu : classe spécialisée (par exemple, WebGpuRenderer) est responsable de la gestion de tous les objets WebGPU et de la coordination de la boucle de rendu.
- Couche Shader : code de nuanceur WGSL stocké dans des constantes res ou string.
Procédure détaillée : application exemple
Cette section présente les étapes essentielles requises pour afficher un triangle coloré à l'écran, ce qui illustre le workflow WebGPU de base.
Activité principale
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WebGpuSurface()
}
}
}
Composable de la surface externe
Créez un fichier nommé WebgpuSurface.kt. Ce composable encapsule AndroidExternalSurface pour fournir un pont entre Compose et votre moteur de rendu.
@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()
}
}
}
}
}
Configurer le moteur de rendu
Créez une classe WebGpuRenderer dans WebGpuRenderer.kt. Cette classe gérera la majeure partie de la communication avec le GPU.
Commencez par définir la structure de la classe et les variables :
class WebGpuRenderer() {
private lateinit var webGpu: WebGpu
private lateinit var renderPipeline: GPURenderPipeline
}
Initialisation : implémentez ensuite la fonction d'initialisation pour créer l'instance WebGPU et configurer la surface. Cette fonction est appelée par le champ d'application AndroidExternalSurface à l'intérieur du composable de surface externe que nous avons créé précédemment.
Remarque : La fonction init utilise createWebGpu, une méthode d'assistance (qui fait partie de androidx.webgpu.helper) pour simplifier la configuration. Cet utilitaire crée l'instance WebGPU, sélectionne un adaptateur et demande un appareil.
// 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 bibliothèque androidx.webgpu inclut des fichiers JNI et .so qui sont automatiquement associés et gérés par le système de compilation. La méthode d'assistance createWebGpu se charge de charger le libwebgpu_c_bundled.so fourni.
Configuration du pipeline
Maintenant que nous avons un appareil, nous devons indiquer au GPU comment dessiner notre triangle. Pour ce faire, nous créons un "pipeline" contenant notre code de nuanceur (écrit en WGSL).
Ajoutez cette fonction d'assistance privée à votre classe WebGpuRenderer pour compiler les nuanceurs et créer le pipeline de rendu.
// 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)
)
)
}
Dessiner un cadre
Maintenant que le pipeline est prêt, nous pouvons implémenter la fonction de rendu. Cette fonction acquiert la prochaine texture disponible à partir de l'écran, enregistre les commandes de dessin et les envoie au GPU.
Ajoutez cette méthode à votre 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()
}
Nettoyage des ressources
Implémentez la fonction de nettoyage, qui est appelée par WebGpuSurface lorsque la surface est détruite.
// Inside WebGpuRenderer class
fun cleanup() {
if (::webGpu.isInitialized) {
webGpu.close()
}
}
Résultat affiché
Exemple de structure d'application
Il est recommandé de dissocier l'implémentation du rendu de la logique de l'UI, comme dans la structure utilisée par l'application exemple :
app/src/main/
├── java/com/example/app/
│ ├── MainActivity.kt // Entry point
│ ├── WebGpuSurface.kt // Composable Surface
│ └── WebGpuRenderer.kt // Pure WebGPU logic
- MainActivity.kt : point d'entrée de l'application. Il définit le contenu sur le composable
WebGpuSurface. - WebGpuSurface.kt : définit le composant d'UI à l'aide de
[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)). Il gère le champ d'application du cycle de vieSurface, en initialisant le moteur de rendu lorsque la surface est prête et en effectuant le nettoyage lorsqu'elle est détruite. - WebGpuRenderer.kt : encapsule toute la logique spécifique à WebGPU (création d'appareil, configuration de pipeline). Il est dissocié de l'UI et ne reçoit que les
[Surface](/reference/android/view/Surface.html)et les dimensions dont il a besoin pour dessiner.
Gestion du cycle de vie et des ressources
La gestion du cycle de vie est gérée par le champ d'application de coroutine Kotlin fourni par [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)) dans Jetpack Compose.
- Création de la surface : initialisez la configuration
DeviceetSurfaceau début du bloc lambdaonSurface. Ce code s'exécute immédiatement lorsqueSurfacedevient disponible. - Destruction de la surface : lorsque l'utilisateur quitte l'écran ou que le système détruit le
Surface, le bloc lambda est annulé. Un blocfinallyest exécuté, appelantrenderer.cleanup()pour éviter les fuites de mémoire. - Redimensionnement : si les dimensions de la surface changent,
AndroidExternalSurfacepeut redémarrer le bloc ou gérer directement les mises à jour en fonction de la configuration. Le moteur de rendu écrit donc toujours dans un tampon valide.
Débogage et validation
WebGPU dispose de mécanismes conçus pour valider les structures d'entrée et capturer les erreurs d'exécution.
- Logcat : les erreurs de validation sont affichées dans le Logcat Android.
- Portées d'erreur : vous pouvez capturer des erreurs spécifiques en encapsulant les commandes GPU dans des blocs
[device.pushErrorScope()](/reference/kotlin/androidx/webgpu/GPUDevice#pushErrorScope(kotlin.Int))et 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")
}
}
Conseils pour l'optimisation des performances
Lorsque vous programmez dans WebGPU, tenez compte des points suivants pour éviter les goulots d'étranglement des performances :
- Évitez la création d'objets par frame : instanciez les pipelines (
GPURenderPipeline), liez les mises en page de groupe et les modules de nuanceur une seule fois lors de la configuration de l'application pour maximiser la réutilisation. - Optimiser l'utilisation des tampons : mettez à jour le contenu des
GPUBuffersexistants viaGPUQueue.writeBufferau lieu de créer de nouveaux tampons à chaque frame. - Minimisez les changements d'état : regroupez les appels de dessin qui partagent le même pipeline et liez les groupes pour minimiser la surcharge du pilote et améliorer l'efficacité du rendu.