78 lines
4.5 KiB
Markdown
78 lines
4.5 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) — CIBLE, non implémenté** : À l'état **visé**, les
|
|
> buffers Transform et Matrix vivent en VRAM avec un single buffer en phase initiale (le CPU écrit
|
|
> pendant `update()`, le compute shader lit au frame suivant, séquencé par `queue.submit()`), puis un
|
|
> double buffering si des artefacts apparaissent à haute fréquence. **Aucune de ces ressources n'existe
|
|
> encore dans le code** — c'est la cible GPU-driven (ROADMAP Phase 3 / ARCHI_CPU_GPU).
|
|
|
|
---
|