GPU culling

This commit is contained in:
Jérôme Bousquié
2026-09-22 15:48:15 +02:00
parent 3dd372410f
commit 3a424afe8c
25 changed files with 2155 additions and 177 deletions
+25 -16
View File
@@ -7,7 +7,7 @@ actor: person/jerome
sources: []
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
verified: true
status: target
status: current
stale_after: 2027-01-31
---
@@ -16,13 +16,19 @@ Bonnes Pratiques & Guide d'Implémentation
Ce document sert de spécification technique et de trame d'implémentation pour l'architecture de rendu 3D pilotée par le GPU (GPU-Driven Rendering) utilisant wgpu. L'objectif est de déléguer un maximum de charges de calcul au GPU pour soulager le CPU et maximiser les performances de parallélisme.
> **État du document : CIBLE (spécification du pipeline GPU-driven, non implémenté).**
> **État du document : ACTUEL (implémenté — Phase 3 du ROADMAP, 2026-07-20, DRAFT Étape 17).**
> La répartition CPU/GPU, le compute pass (World Matrices + Frustum Culling), l'Indirect Draw Buffer
> et les buffers persistants en VRAM décrits ici correspondent à la **Phase 3 du ROADMAP** et aux
> README étapes 2-3. **Aucun de ces mécanismes n'existe encore dans le code.** Aujourd'hui le rendu est
> piloté par le CPU, **objet par objet** (une soumission par mesh, voir README.md et l'exemple `manual`).
> Considérez ce document comme la spécification de référence pour l'implémentation future du pipeline
> GPU-driven, pas comme une description de l'état actuel.
> et les buffers persistants en VRAM décrits ici sont en place : `shaders/gpu_driven.wgsl`
> (deux entry points `compute_matrices` + `cull`, un module, layout explicite à 3 groupes) et les
> buffers de slots du `Renderer` (`TransformSlot`/`MatSlot`/`BBoxSlot`/`DrawSlot`/`CullUniforms`,
> capacité fixe de 256 slots).
> **Écarts documentés** (cf. DRAFT Étape 17) : (D1) un draw indirect **par slot** plutôt qu'une
> commande unique fusionnée ; (D12) 256 slots, slot matrice padded à 256 o (plafond `uniform` WebGPU) ;
> (D5) culling par **sphère** conservative dérivée de l'AABB locale du mesh, pas par l'AABB transformée
> exacte ; (D4) single buffer, pas de double-buffering.
> **Piège connu (2026-09-22, D14)** : l'ordre des arguments de `select` en WGSL est l'inverse de la
> convention HLSL — l'avoir inversé a produit un bug « fenêtre noire » (entités visibles remises à 0),
> corrigé et vérifié par readback GPU. Documenté en tête de `gpu_driven.wgsl` et dans `AGENTS.md`.
1. Répartition des Rôles : CPU vs GPU (La Source de Vérité)
@@ -53,10 +59,10 @@ L'exécution des tâches s'appuie sur une structure séquentielle stricte au sei
- Mise à jour CPU (Minimaliste) : Le CPU écrit les transformations brutes (Transform) modifiées dans un buffer GPU mappé (single buffer en phase initiale — la synchronisation est assurée par `queue.submit()` qui garantit la séquence d'exécution). Double buffering sera ajouté uniquement si des artefacts apparaissent à haute fréquence (> 90 fps).
- Pass de Calcul (Compute Pass) :
- Calcul des World Matrices : Un compute shader lit les transformations brutes et génère la matrice 4x4 finale pour chaque mesh.
- Frustum Culling GPU : Le même compute shader (ou un compute pass dédié) compare la Bounding Box (AABB) de chaque objet avec les plans de la caméra (matrice de projection/vue).
- Remplissage du Buffer Indirect : Si l'objet est visible, son identifiant est injecté dans un buffer de commandes de dessin indirect (Indirect Draw Buffer).
- Frustum Culling GPU : Un compute pass dédié (`cull`) compare la **sphère bounding** de chaque objet (D5 — conservative, dérivée de l'AABB locale du mesh et de l'échelle de l'entité) avec les 6 plans du frustum de la caméra.
- Remplissage du Buffer Indirect : le pass `cull` écrit le **compte de sommets/indices** de chaque objet dans son `DrawSlot` (80 o) — mis à 0 si l'objet est cullé ou inactif (no-op).
- Pass de Rendu (Render Pass) :
- Le CPU émet une unique commande globale : draw_indexed_indirect.
- Le CPU émet **un draw indirect par slot** (écart D1 — la spécification initiale prévoyait une commande unique fusionnée) ; les slots à compte 0 (cullés/inactifs/vides) sont des no-ops.
- Le GPU pioche directement dans le buffer préparé par le compute pass et dessine uniquement les objets visibles, sans intervention du CPU.
3. Stratégie de Synchronisation
@@ -66,12 +72,15 @@ L'exécution des tâches s'appuie sur une structure séquentielle stricte au sei
4. Synthèse des Structures de Données en VRAM
Pour implémenter cette architecture, prévoyez l'utilisation des buffers wGPU suivants :
Nom du Buffer,Rôle,Type wGPU,Direction du flux
Transform Buffer,Stocke les positions/rotations/échelles brutes.,Storage Buffer,CPU → GPU
Matrix Buffer,Stocke les World Matrices finales calculées.,Storage Buffer,GPU (Calculé) → GPU (Lu par le Render)
Bounding Box Buffer,Stocke les AABB de chaque mesh pour le culling.,Storage Buffer,CPU → GPU (Statique)
Indirect Draw Buffer,Contient la liste dynamique des objets à dessiner.,Indirect Buffer + Storage,GPU (Rempli par Compute) → GPU (Lu par Render)
L'implémentation utilise les buffers wGPU suivants (tous créés par le `Renderer` à l'initialisation, capacité fixe de 256 slots) :
| Buffer | Rôle | Type wGPU | Direction du flux |
|--------|------|-----------|-------------------|
| Transform Buffer | Positions/rotations/échelles brutes + flags par entité (64 o/slot) | Storage Buffer | CPU → GPU (chaque frame, `write_buffer`) |
| Matrix Buffer | World Matrices finales calculées (256 o/slot, padded — D12) | Storage + Uniform Buffer | GPU (Calculé) → GPU (Lu par le Render) |
| Bounding Box Buffer | Coins min/max de l'AABB de chaque mesh (32 o/slot) | Storage Buffer | CPU → GPU (quand l'ensemble des meshes change) |
| Indirect Draw Buffer | Comptes de draw par slot (80 o/slot, zéro = no-op) | Indirect + Storage Buffer | GPU (Rempli par Compute) → GPU (Lu par le Render) |
| CullUniforms | 6 plans du frustum + `num_slots` + `culling` (112 o) | Uniform Buffer | CPU → GPU (chaque frame) — réservé au compute (group 2) |
## Liens