Files
wsg/docs/tech/FRAME_LOOP.md
T
Jérôme Bousquié 531c43a457 material batching
2026-09-22 17:07:52 +02:00

79 lines
4.6 KiB
Markdown

---
type: Technical Specification
title: Frame Loop Architecture
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]
actor: person/jerome
sources: []
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)
> **État du document : ACTUEL (implémenté).** Ce document décrit la frame lifetime telle qu'elle est
> réellement implémentée. **Deux flux coexistent** : le flux **facade `App`** (rendu automatique de la
> scène, `App::render_scene` — le workflow recommandé, exemples `simple`/`cube`/`demo`) et le flux
> **manuel** (`Context`/`Renderer`/`Frame` pilotés à la main — exemple `manual`, un objet par soumission).
> Le pipeline **GPU-driven** (compute pass + draw indirect) de l'état **visé** est décrit dans
> [ARCHI_APP](ARCHI_APP.md) / [ARCHI_CPU_GPU](ARCHI_CPU_GPU.md) (cible).
Pour afficher quelque chose, nous suivons un cycle immuable appelé la Frame Lifetime, basé sur `Frame` :
**Flux facade `App` (recommandé — `App::run` + `AppHandler`) :**
- **`Context::get_next_frame()`** : acquiert la surface texture et crée sa `TextureView` (dans `Frame`).
- **`AppHandler::render` (défaut) → `App::render_scene(view)`** : le moteur itère les entités de la
scène et les dessine en **une passe groupée** (un `CommandEncoder` + une soumission par frame ;
passe d'ombre en tête si un caster est actif).
- **`Renderer::present(frame)`** : présente l'image à l'écran.
- Chaque frame, avant `update`, le moteur appelle `device.poll()` (les callbacks asynchrones wgpu —
`on_submitted_work_done`, `map_async` — ne se déclenchent que lors d'un poll), et la fenêtre
redimensionnée est gérée par `App::resize` (surface + depth texture recréées ensemble).
**Flux `manual` (exemple `manual` — un objet par soumission) :**
- **`Context::get_next_frame()`** (ou `Frame::try_new(&context.surface)`) : acquiert la surface texture et crée sa `TextureView` (dans `Frame`).
- **`Renderer::render(&view, &mesh, &material)`** : crée un `CommandEncoder`, écrit les ordres de dessin dans la `TextureView`, puis soumet à la file (`queue`).
- **`Renderer::present(frame)`** : présente l'image à l'écran.
**Variante bas niveau (API `Context` brute, sans `Frame`) :**
- **`Context::begin_frame()`** : acquiert la surface et renvoie la `wgpu::SurfaceTexture` (sans vue).
- **`Context::end_frame(surface_texture)`** : soumet et présente cette texture.
## Liens
- [ARCHI_APP](ARCHI_APP.md) · [ARCHI_RENDU](ARCHI_RENDU.md) · [ARCHI_CPU_GPU](ARCHI_CPU_GPU.md) · [ARCHI_ARENES](ARCHI_ARENES.md)
- Documentation utilisateur : [docs/user](../user/README.md) · [README racine](../../README.md) · [ROADMAP](../ROADMAP.md)
- Référence API : `cargo doc -p wsg-lib --no-deps`
---
## Pourquoi cette séparation est vitale
Le bloc `{ let mut render_pass = ... }` est crucial. Dans Rust, `render_pass` emprunte mutablement `encoder`. Il doit être détruit (via la fin du bloc ou un `drop()`) avant que tu puisses appeler `encoder.finish()`. Si tu oublies cela, le compilateur Rust refusera de compiler, empêchant ainsi des bugs critiques de synchronisation GPU.
---
## Ressources : Persistantes vs Par-Frame
Avec notre nouvelle architecture "Atelier", la distinction est devenue encore plus nette :
| Élément | Durée de vie | Pourquoi ? |
|---------|-------------|------------|
| SurfaceConfiguration | Persistante | Ne change qu'au redimensionnement. |
| RenderPipeline | Persistante | Stocké dans le PipelineCache (`Arc`), compilation unique. |
| Material | Persistante | Définit le look ; partage le pipeline via `Arc`. |
| Mesh | Persistante | Les données géométriques sont envoyées une fois au 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. |
> **Ressources GPU persistantes (single buffer) — implémenté (Phase 3, 2026-09-22)** : les buffers
> Transform, Matrix, BBox et Indirect Draw vivent en VRAM (créés à l'initialisation du `Renderer`,
> capacité fixe de 256 slots). Le CPU écrit les transforms chaque frame par `queue.write_buffer`
> **dans le même `CommandEncoder`** que les compute passes, qui les lisent **dans la même frame**
> (l'ordre est garanti par l'encoder, pas par `queue.submit()` inter-frames). Le double buffering
> reste la **cible** si des artefacts apparaissent à haute fréquence (voir ARCHI_CPU_GPU / ARCHI_APP).
---