LOD GPU
This commit is contained in:
@@ -16,7 +16,7 @@ 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 : ACTUEL (implémenté — Phase 3 du ROADMAP, Étape 17, validé 2026-09-22).**
|
||||
> **État du document : ACTUEL (implémenté — Phase 3 du ROADMAP, Étapes 17–19, validé 2026-09-22).**
|
||||
> 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 sont en place : `shaders/gpu_driven.wgsl`
|
||||
> (deux entry points `compute_matrices` + `cull`, un module, layout explicite à 3 groupes) et les
|
||||
@@ -37,6 +37,16 @@ Ce document sert de spécification technique et de trame d'implémentation pour
|
||||
> le pass d'ombre — un seul pipeline — est inchangé). Réordonnancement sûr car tous les pipelines
|
||||
> sont opaques (`BlendState::REPLACE`) ; les no-ops cullés restent émis dans leur groupe.
|
||||
> Détail : `docs/user/gpu-driven.md` § « Batching by material ».
|
||||
> **LOD (Étape 19, 2026-09-23)** : le pass `cull` remplit désormais les arguments indirects à partir
|
||||
> du **niveau de détail** du slot, et non d'un seul jeu de comptes. Le choix du niveau est fait **côté CPU**
|
||||
> (rayon de la sphère bounding projeté en pixels + hystérésis asymétrique — `math/lod.rs`, pur et unit-testé) ;
|
||||
> le GPU n'effectue que le mappage niveau → ligne de la table LOD du mesh. Les niveaux d'un mesh sont
|
||||
> générés par décimation gloutonne au setup (`Geometry::decimated` : suppression des plus petits triangles,
|
||||
> soudure par position exacte, rebase u16) et **empilés dans les buffers vertex/index du mesh** (offsets en
|
||||
> unités d'élément, pas d'octet — c'est ce qu'exigent les arguments `drawIndirect*` de WebGPU ; plafond u16 :
|
||||
> 65 535 sommets/mesh, 4 niveaux max). LOD activé par défaut ; `set_lod_enabled(false)` restaure un rendu
|
||||
> bit-à-bit identique au pré-LOD (niveau 0 partout = comptes complets). Détail : `docs/user/gpu-driven.md`
|
||||
> § « Level of Detail ».
|
||||
|
||||
1. Répartition des Rôles : CPU vs GPU (La Source de Vérité)
|
||||
|
||||
@@ -45,7 +55,8 @@ Pour éviter les goulets d'étranglement dus aux allers-retours sur le bus PCIe,
|
||||
Côté CPU (Source de Vérité)
|
||||
- Ce qu'il conserve : Les données logiques et les transformations brutes des objets (ex: Vec<Transform> contenant la position, la rotation, et l'échelle).
|
||||
- Ce qu'il fait : Il gère la logique de jeu, l'IA, le réseau et les interactions globales.
|
||||
- Ce qu'il ne fait plus : Il ne calcule plus les matrices de transformation mondiales (World Matrices) en masse, et ne fait plus de tests de visibilité unitaires.
|
||||
- Ce qu'il ne fait plus : Il ne calcule plus les matrices de transformation mondiales (World Matrices) en masse, et ne fait plus de tests de visibilité unitaires (le culling frustum reste 100 % GPU).
|
||||
- Ce qu'il fait en plus (LOD, Étape 19) : le **choix du niveau de détail** par entité — O(N) projections de sphères en pixels + hystérésis, coût négligeable. C'est l'unique décision de visibilité/détail conservée côté CPU : elle dépend de la taille écran (un choix artistique), pas de la géométrie, et l'hystérésis a besoin de l'état de la frame précédente.
|
||||
|
||||
Côté GPU (Exécutant Autonome)
|
||||
- Ce qu'il calcule : Les World Matrices, le Frustum Culling, et la génération des listes de dessin indirectes.
|
||||
@@ -64,11 +75,11 @@ L'exécution des tâches s'appuie sur une structure séquentielle stricte au sei
|
||||
```
|
||||
|
||||
Étape par étape :
|
||||
- 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).
|
||||
- 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), **et le niveau LOD de chaque slot** (1 u32/slot, Étape 19). 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 : 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).
|
||||
- Remplissage du Buffer Indirect : le pass `cull` lit le **niveau LOD** du slot, en choisit la ligne dans la table LOD du mesh (`LodTable` : 4 lignes d'offsets/comptes en unités d'élément) et écrit les arguments dans le `DrawSlot` (80 o) — mis à 0 si l'objet est cullé ou inactif (no-op). Le niveau 0 porte les comptes du mesh complet, donc LOD désactivé ≡ pré-LOD bit-à-bit.
|
||||
- Pass de Rendu (Render Pass) :
|
||||
- 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.
|
||||
@@ -89,6 +100,15 @@ L'implémentation utilise les buffers wGPU suivants (tous créés 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) |
|
||||
| Lod Levels | Niveau LOD par slot choisi par le CPU (4 o/slot) | Storage Buffer (read-only) | CPU → GPU (chaque frame) |
|
||||
| Lod Tables | Table par mesh : `count` + 4 lignes de 16 o (offset/compte d'éléments) (80 o/mesh) | Storage Buffer (read-only) | CPU → GPU (quand l'ensemble des meshes change) |
|
||||
|
||||
**Buffers de géométrie LOD (Étape 19)** : les niveaux d'un mesh sont **empilés** — un seul buffer vertex et un
|
||||
seul buffer index par mesh, contenant les niveaux concaténés (L0, L1, …). Les lignes de la table LOD portent
|
||||
les offsets en **unités d'élément** (premier vertex / premier index), car les arguments `drawIndirect*` de
|
||||
WebGPU s'expriment en éléments, et le buffer est lié en entier à l'offset 0. Conséquences : un mesh LOD ne
|
||||
peux dépasser 65 535 sommets au total (indices u16) et 4 niveaux (`MAX_LOD_LEVELS`) ; le mélange indexé/non-indexé
|
||||
dans un même mesh est supporté (la commande de draw par slot suit le niveau choisi par le CPU).
|
||||
|
||||
## Liens
|
||||
|
||||
|
||||
Reference in New Issue
Block a user