doc : single buffer
This commit is contained in:
+17
-6
@@ -3,8 +3,12 @@ type: Architecture
|
||||
title: wsg_lib Engine Architecture
|
||||
description: Technical architecture and design principles of the wsg_lib rendering engine
|
||||
tags: [architecture, rendering, graphics, wgpu, engine]
|
||||
status: stable
|
||||
actor: person/jerome
|
||||
sources: []
|
||||
generated: { by: human:jerome, at: 2026-07-31T00:00:00Z }
|
||||
verified: true
|
||||
status: current
|
||||
stale_after: 2027-01-31
|
||||
---
|
||||
|
||||
# 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.
|
||||
- **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.
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
## 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 :
|
||||
|
||||
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.
|
||||
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.
|
||||
@@ -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) |
|
||||
| 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.
|
||||
|
||||
## 5. Accès Avancé
|
||||
@@ -117,7 +125,9 @@ App (Facade) -> Scene (Conteneur) -> Entities -> Mesh + Material (Shader)
|
||||
└── Render Pass : draw_indexed_indirect (objets visibles uniquement)
|
||||
|
||||
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
|
||||
@@ -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.
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
Reference in New Issue
Block a user