Introduction
WebGPU représente l'avenir du rendu graphique et du calcul parallèle dans les navigateurs. Contrairement à WebGL, il offre un contrôle plus fin sur le GPU, une meilleure gestion des ressources et un support natif des compute shaders. Ce tutoriel avancé vous guide à travers l'implémentation complète d'une application WebGPU, du setup jusqu'aux optimisations de pipeline. Vous apprendrez à gérer les buffers, les bind groups et les shaders WGSL pour des performances maximales. Chaque étape inclut du code fonctionnel prêt à l'emploi.
Prérequis
- Chrome 113+ ou Edge 113+ avec WebGPU activé
- Connaissances solides en JavaScript/TypeScript et GLSL/WGSL
- Compréhension des concepts GPU (buffers, shaders, pipelines)
- Node.js pour le tooling et bundling
Initialisation du contexte WebGPU
async function initWebGPU(): Promise<GPUDevice> {
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) throw new Error('WebGPU non supporté');
const device = await adapter.requestDevice();
return device;
}Cette fonction initialise l'adaptateur et le device GPU. Vérifiez toujours la disponibilité de WebGPU avant d'exécuter du code critique.
Création du buffer de vertices
function createVertexBuffer(device: GPUDevice, data: Float32Array): GPUBuffer {
const buffer = device.createBuffer({
size: data.byteLength,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true,
});
new Float32Array(buffer.getMappedRange()).set(data);
buffer.unmap();
return buffer;
}Crée un buffer vertex optimisé avec mappedAtCreation pour un upload initial efficace des données.
Shader WGSL compute simple
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
let i = id.x;
if (i < arrayLength(&data)) { data[i] *= 2.0; }
}Shader compute basique qui double chaque valeur du buffer. Utilisez workgroup_size adapté à votre GPU.
Configuration du compute pipeline
function createComputePipeline(device: GPUDevice, shaderCode: string): GPUComputePipeline {
const shaderModule = device.createShaderModule({ code: shaderCode });
return device.createComputePipeline({
layout: 'auto',
compute: { module: shaderModule, entryPoint: 'main' },
});
}Pipeline compute avec layout auto pour simplifier le bind group. Toujours réutiliser les modules shader compilés.
Exécution du compute pass
function dispatchCompute(device: GPUDevice, pipeline: GPUComputePipeline, bindGroup: GPUBindGroup, count: number) {
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(Math.ceil(count / 64));
pass.end();
device.queue.submit([encoder.finish()]);
}Dispatch le compute shader avec le bon nombre de workgroups. Soumettez toujours les commandes à la queue GPU.
Bonnes pratiques
- Utilisez toujours des bind groups réutilisables et minimisez les changements de pipeline
- Préférez les buffers persistants plutôt que de recréer à chaque frame
- Testez les performances avec GPU timing queries
- Gérez explicitement la synchronisation avec device.queue.onSubmittedWorkDone()
- Validez les limites de votre GPU (maxBufferSize, maxComputeWorkgroupSize)
Erreurs courantes à éviter
- Oublier d'appeler unmap() sur les buffers mappés, causant des fuites mémoire
- Taille de workgroup non multiple de 32 ou 64, entraînant des performances médiocres
- Ne pas vérifier les erreurs GPU via device.lost
- Ignorer les limites de storage buffers sur certains navigateurs
Pour aller plus loin
Approfondissez avec nos ressources sur les techniques avancées de rendu et compute. Découvrez nos formations Learni dédiées au WebGPU et au graphisme haute performance.