doc : single buffer

This commit is contained in:
Jérôme Bousquié
2026-08-03 21:52:36 +02:00
parent 895965750f
commit e8e9124a9d
5 changed files with 40 additions and 11 deletions
+17 -6
View File
@@ -3,8 +3,12 @@ type: Architecture
title: wsg_lib Engine Architecture title: wsg_lib Engine Architecture
description: Technical architecture and design principles of the wsg_lib rendering engine description: Technical architecture and design principles of the wsg_lib rendering engine
tags: [architecture, rendering, graphics, wgpu, engine] tags: [architecture, rendering, graphics, wgpu, engine]
status: stable actor: person/jerome
sources: []
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
verified: true
status: current
stale_after: 2027-01-31
--- ---
# Architecture du Moteur wsg_lib # Architecture du Moteur wsg_lib
@@ -16,7 +20,8 @@ wsg_lib est un moteur de rendu modulaire basé sur wgpu. Il adopte une architect
- **Abstraction vs Transparence** : Le moteur masque la complexité (wgpu, winit, gestion des Frame) via `App`, tout en exposant les briques élémentaires pour les utilisateurs avancés. - **Abstraction vs Transparence** : Le moteur masque la complexité (wgpu, winit, gestion des Frame) via `App`, tout en exposant les briques élémentaires pour les utilisateurs avancés.
- **Architecture GPU-Driven** : Le CPU est le cerveau logique (gestion de la scène, IA, réseau), le GPU est l'exécutant visuel. Le moteur délègue au GPU le calcul des World Matrices, le Frustum Culling et la génération des listes de dessin indirectes — évitant ainsi les goulets d'étranglement PCIe. - **Architecture GPU-Driven** : Le CPU est le cerveau logique (gestion de la scène, IA, réseau), le GPU est l'exécutant visuel. Le moteur délègue au GPU le calcul des World Matrices, le Frustum Culling et la génération des listes de dessin indirectes — évitant ainsi les goulets d'étranglement PCIe.
- **Pipeline à deux passes** : Chaque frame suit un ordre strict : **Compute Pass** (calculs GPU) → **Render Pass** (dessin indirect). Les barrières de mémoire sont gérées automatiquement par le driver. - **Pipeline à deux passes** : Chaque frame suit un ordre strict : **Compute Pass** (calculs GPU) → **Render Pass** (dessin indirect). Les barrières de mémoire sont gérées automatiquement par le driver.
- **Ressources persistantes en VRAM** : Les buffers essentiels (Transform, Matrix, BoundingBox, Indirect Draw) vivent d'une frame à l'autre sans redescendre vers le CPU. Le Double Buffering évite les conflits lecture/écriture. - **Ressources persistantes en VRAM** : Les buffers essentiels (Transform, Matrix, BoundingBox, Indirect Draw) vivent d'une frame à l'autre sans redescendre vers le CPU.
> **Note sur la synchronisation** : La première itération utilise un **single buffer** pour les buffers Transform et Matrix (voir §4B). Le double buffering n'est pas nécessaire tant que `desired_maximum_frame_latency` ≥ 3 ou que le moteur fonctionne en FIFO avec une latence de ≥ 2 frames — dans ce cas, le GPU est toujours au moins 2 frames derrière, éliminant tout risque de collision CPU/GPU. Le double buffering sera ajouté uniquement si le moteur atteint des fréquences élevées (> 90 fps) où le CPU peut écrire une frame pendant que le GPU lit encore la précédente.
- **Lecture seule pendant render()** : La Scene est immuable durant le Render. L'utilisateur ne modifie que dans `update()` ; toute tentative de mutation pendant le rendu bloque les données du GPU. - **Lecture seule pendant render()** : La Scene est immuable durant le Render. L'utilisateur ne modifie que dans `update()` ; toute tentative de mutation pendant le rendu bloque les données du GPU.
- **Pipeline Cache** : Les shaders et pipelines sont compilés une fois puis réutilisés via `Arc`. Aucun readback (`map_async`) n'est effectué sauf débug critique. - **Pipeline Cache** : Les shaders et pipelines sont compilés une fois puis réutilisés via `Arc`. Aucun readback (`map_async`) n'est effectué sauf débug critique.
@@ -55,7 +60,7 @@ pub trait AppHandler {
} }
``` ```
- **`update()`** : appelé en premier. L'utilisateur peut modifier librement la scène (transformations, ajout/suppression d'entités). Ces modifications sont synchronisées vers le GPU via un Double Buffering avant la passe de calcul. - **`update()`** : appelé en premier. L'utilisateur peut modifier librement la scène (transformations, ajout/suppression d'entités). Ces modifications sont synchronisées vers le GPU via un **single buffer** Transform avant la passe de calcul.
- **`render()`** : appelé après. Il ne sert qu'à injecter du rendu personnalisé (debug, HUD, etc.). La Scene reste immuable : aucune mutation d'état métier. - **`render()`** : appelé après. Il ne sert qu'à injecter du rendu personnalisé (debug, HUD, etc.). La Scene reste immuable : aucune mutation d'état métier.
## 4. Workflow et Cycle de Vie ## 4. Workflow et Cycle de Vie
@@ -81,7 +86,8 @@ Le moteur gère la renderloop interne via un pipeline à **deux passes séquenti
Étape par étape : Étape par étape :
1. **Update** (`AppHandler::update`) — L'utilisateur modifie la scène (transformations, entités). Ces changements sont synchronisés vers le GPU via Double Buffering avant la passe de calcul. 1. **Update** (`AppHandler::update`) — L'utilisateur modifie la scène (transformations, entités). Ces changements sont synchronisés vers le GPU via un **single buffer** Transform avant la passe de calcul.
> La synchronisation est assurée par le pipeline wgpu : `queue.submit()` après le compute pass garantit que les données Transform sont valides avant le render pass suivant. Aucun double buffering n'est nécessaire tant que la latence maximale de la surface (via `desired_maximum_frame_latency`) est ≥ 3.
2. **Compute Pass** — Un compute shader lit les Transform bruts, calcule les World Matrices finales, effectue le Frustum Culling par AABB, et remplit l'Indirect Draw Buffer avec les identifiants des objets visibles. 2. **Compute Pass** — Un compute shader lit les Transform bruts, calcule les World Matrices finales, effectue le Frustum Culling par AABB, et remplit l'Indirect Draw Buffer avec les identifiants des objets visibles.
3. **Render Pass** — Le CPU émet une unique commande `draw_indexed_indirect`. Le GPU pioche dans l'Indirect Draw Buffer et dessine uniquement les objets visibles, sans intervention du CPU. 3. **Render Pass** — Le CPU émet une unique commande `draw_indexed_indirect`. Le GPU pioche dans l'Indirect Draw Buffer et dessine uniquement les objets visibles, sans intervention du CPU.
4. **Présentation** — La surface est présentée à l'écran. 4. **Présentation** — La surface est présentée à l'écran.
@@ -97,6 +103,8 @@ L'ordre d'appel des méthodes sur le `CommandEncoder` (`begin_compute_pass` puis
| Bounding Box Buffer | AABB de chaque mesh pour culling | Storage Buffer | CPU → GPU (Statique) | | Bounding Box Buffer | AABB de chaque mesh pour culling | Storage Buffer | CPU → GPU (Statique) |
| Indirect Draw Buffer | Liste dynamique des objets à dessiner | Indirect + Storage | GPU (Rempli par Compute) → GPU (Lu par Render) | | Indirect Draw Buffer | Liste dynamique des objets à dessiner | Indirect + Storage | GPU (Rempli par Compute) → GPU (Lu par Render) |
> **Synchronisation single buffer** : Les buffers Transform et Matrix utilisent un **single buffer** en phase initiale. Le CPU écrit dans le buffer pendant `update()`, puis le compute shader lit les données au frame suivant via `queue.submit()` qui garantit la séquence d'exécution. Cette approche fonctionne correctement tant que la surface a une latence maximale ≥ 2 frames (configuré via `desired_maximum_frame_latency`). Le double buffering sera ajouté uniquement si des artefacts visuels apparaissent à haute fréquence (typiquement > 90 fps sur machines rapides).
Toutes ces données vivent en VRAM — aucun readback (`map_async`) n'est effectué sauf débug critique. Le CPU fait confiance à sa propre structure de données initiale pour la logique métier. Toutes ces données vivent en VRAM — aucun readback (`map_async`) n'est effectué sauf débug critique. Le CPU fait confiance à sa propre structure de données initiale pour la logique métier.
## 5. Accès Avancé ## 5. Accès Avancé
@@ -117,7 +125,9 @@ App (Facade) -> Scene (Conteneur) -> Entities -> Mesh + Material (Shader)
└── Render Pass : draw_indexed_indirect (objets visibles uniquement) └── Render Pass : draw_indexed_indirect (objets visibles uniquement)
VRAM persistante : Transform Buffer → Matrix Buffer → BoundingBox Buffer → Indirect Draw Buffer VRAM persistante : Transform Buffer → Matrix Buffer → BoundingBox Buffer → Indirect Draw Buffer
Double buffering : Update écrit dans le buffer "back", Compute lit depuis "front" Single buffer (phase initiale) : Update écrit, Compute lit au frame suivant — garanti par queue.submit()
[À venir] Double buffering : buffers Transform/Matrix dupliqués + swap entre frames
``` ```
## Notes pour l'implémentation future ## Notes pour l'implémentation future
@@ -127,4 +137,5 @@ Double buffering : Update écrit dans le buffer "back", Compute lit depuis "fron
- **`PipelineCache`** : Invisible pour l'utilisateur standard lors de la création d'un `Material`, mais accessible publiquement pour shaders compute personnalisés et pipelines avancés. - **`PipelineCache`** : Invisible pour l'utilisateur standard lors de la création d'un `Material`, mais accessible publiquement pour shaders compute personnalisés et pipelines avancés.
- **Compute shader par défaut** : Un compute shader intégré gère le calcul des World Matrices et le Frustum Culling. Les utilisateurs avancés peuvent le remplacer entièrement via `PipelineCache`. - **Compute shader par défaut** : Un compute shader intégré gère le calcul des World Matrices et le Frustum Culling. Les utilisateurs avancés peuvent le remplacer entièrement via `PipelineCache`.
- **Synchronisation** : Toujours appeler `begin_compute_pass` avant `begin_render_pass` sur le même `CommandEncoder`. Les barrières entre passes sont automatiques — ne jamais insérer de barrière manuelle sauf besoin critique. - **Synchronisation** : Toujours appeler `begin_compute_pass` avant `begin_render_pass` sur le même `CommandEncoder`. Les barrières entre passes sont automatiques — ne jamais insérer de barrière manuelle sauf besoin critique.
- **Double Buffering** : Ne jamais lire un buffer GPU pendant que l'autre écrit dedans. Utiliser un mécanisme de double buffering (buffers "front" / "back") pour éviter les conflits lecture/écriture entre frames. - **Synchronisation single buffer (phase initiale)** : La séquence `queue.submit()` après chaque compute pass garantit que les données Transform sont valides avant le render pass suivant. Aucun conflit de lecture/écriture n'est possible tant que `desired_maximum_frame_latency` ≥ 3.
- **Double Buffering (future migration)** : Sera implémenté sur les buffers Transform et Matrix seulement, pas sur BoundingBox ni Indirect Draw. Le switch se résume à : dupliquer ces deux buffers, ajouter une méthode `swap()` appelée dans `AboutToWait`, modifier les bind groups pour pointer vers l'index courant. Pas besoin de refonte architecturale.
+5 -1
View File
@@ -3,8 +3,12 @@ type: Technical Specification
title: Generational Arena Resource Management with slotmap title: Generational Arena Resource Management with slotmap
description: Technical specification for efficient and safe resource management using generational arenas implemented via the slotmap crate description: Technical specification for efficient and safe resource management using generational arenas implemented via the slotmap crate
tags: [architecture, resources, performance, safety, slotmap, arena] tags: [architecture, resources, performance, safety, slotmap, arena]
status: stable actor: person/jerome
sources: []
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
verified: true
status: current
stale_after: 2027-01-31
--- ---
# Fiche Technique : Gestion des Ressources avec des Arènes Générationalles (`slotmap`) # Fiche Technique : Gestion des Ressources avec des Arènes Générationalles (`slotmap`)
+6 -2
View File
@@ -3,8 +3,12 @@ type: Technical Specification
title: GPU-Driven 3D Rendering Architecture with wGPU title: GPU-Driven 3D Rendering Architecture with wGPU
description: Technical specification for GPU-driven 3D rendering architecture using wgpu, focusing on CPU-GPU workload distribution and performance optimization description: Technical specification for GPU-driven 3D rendering architecture using wgpu, focusing on CPU-GPU workload distribution and performance optimization
tags: [architecture, rendering, gpu, cpu, performance, wgpu] tags: [architecture, rendering, gpu, cpu, performance, wgpu]
status: stable actor: person/jerome
sources: []
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
verified: true
status: current
stale_after: 2027-01-31
--- ---
Architecture de Rendu 3D GPU-Driven avec wGPU : Architecture de Rendu 3D GPU-Driven avec wGPU :
@@ -38,7 +42,7 @@ L'exécution des tâches s'appuie sur une structure séquentielle stricte au sei
``` ```
Étape par étape : Étape par étape :
- Mise à jour CPU (Minimaliste) : Le CPU écrit les transformations brutes (Transform) modifiées dans un buffer GPU mappé (via un mécanisme de Double Buffering pour éviter les conflits de lecture/écriture). - 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) : - 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. - 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). - 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).
+5 -1
View File
@@ -3,8 +3,12 @@ type: Technical Specification
title: Rendering Architecture: Update/Render Cycle and Data Management title: Rendering Architecture: Update/Render Cycle and Data Management
description: Technical specification for the rendering architecture of wsg_lib, defining strategies for mutability and data management to maximize performance and memory safety in Rust description: Technical specification for the rendering architecture of wsg_lib, defining strategies for mutability and data management to maximize performance and memory safety in Rust
tags: [architecture, rendering, rust, performance, memory-safety] tags: [architecture, rendering, rust, performance, memory-safety]
status: stable actor: person/jerome
sources: []
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
verified: true
status: current
stale_after: 2027-01-31
--- ---
# Architecture de Rendu : Cycle Update/Render et Gestion des Données # Architecture de Rendu : Cycle Update/Render et Gestion des Données
+7 -1
View File
@@ -3,8 +3,12 @@ type: Technical Specification
title: Frame Loop Architecture title: Frame Loop Architecture
description: Technical specification for the frame loop architecture in wsg_lib, detailing the immutable frame lifetime cycle and resource management description: Technical specification for the frame loop architecture in wsg_lib, detailing the immutable frame lifetime cycle and resource management
tags: [architecture, rendering, frame-loop, gpu, wgpu] tags: [architecture, rendering, frame-loop, gpu, wgpu]
status: stable actor: person/jerome
sources: []
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
verified: true
status: current
stale_after: 2027-01-31
--- ---
# La Boucle de Rendu (Frame Loop) # La Boucle de Rendu (Frame Loop)
@@ -36,4 +40,6 @@ Avec notre nouvelle architecture "Atelier", la distinction est devenue encore pl
| CommandEncoder | Par-Frame | Ton "carnet de notes" temporaire pour les ordres du GPU. | | CommandEncoder | Par-Frame | Ton "carnet de notes" temporaire pour les ordres du GPU. |
| TextureView | Par-Frame | Fenêtre temporaire sur la texture active du swapchain. | | TextureView | Par-Frame | Fenêtre temporaire sur la texture active du swapchain. |
> **Ressources GPU persistantes (single buffer)** : Les buffers Transform et Matrix sont stockés en VRAM avec un **single buffer** en phase initiale. Le CPU écrit pendant `update()`, le compute shader lit au frame suivant via la séquence garantie par `queue.submit()`. Double buffering sera ajouté uniquement si des artefacts visuels apparaissent à haute fréquence.
--- ---