docs(draft): plan Étape 11 — resize (surface + depth, Phase 4.4)

This commit is contained in:
Jérôme Bousquié
2026-09-18 17:16:04 +02:00
parent acf819737d
commit f4df63a136
+139 -4
View File
@@ -1,5 +1,140 @@
# DRAFT — Étape suivante
# DRAFT — Étape 11 : Gestion du Resize (cycle de vie Surface + Depth)
> 📅 **Document vidé le 2026-09-18** (fin de l'Étape 10, Textures — Phase 4.1, bilan archivé
> dans l'historique git). Ce fichier accueillera le plan de l'étape suivante.
> Source de vérité = code + README.md. Ce document est vidé à la complétion de chaque étape.
> 📅 **2026-09-18** — Plan de l'étape suivante.
> **Source de vérité** = code + README.md. Ce document est vidé à la complétion de l'étape.
> **Références** : ROADMAP Phase 4.4 · PLAN Phase 4 · décision D3 (2026-09-18).
---
## Contexte
L'application ne gère **aucun** resize aujourd'hui : la surface est configurée une seule fois,
au démarrage, dans `AppRunner::resumed` (`context.configure(&adapter, width, height)`), et la
**depth texture** est allouée une seule fois, à la taille initiale, dans `Renderer::new`
(Étape 9). Quand l'utilisateur redimensionne la fenêtre :
- la surface reste configurée à l'ancienne taille → `get_current_texture()` retourne
`Suboptimal`/`Outdated`, le rendu est cassé ou artefacté ;
- la depth texture reste à l'ancienne taille → les attachments color/depth n'ont plus la
même taille → **erreur de validation wgpu** (tailles des attachments divergentes).
Le helper isolé `create_depth_texture(device, width, height)` (Étape 9, D3) rend le recreate
de la depth texture trivial. C'est le chantier dédié planifié en **Phase 4.4**, hors périmètre
de l'Étape 9.
## Objectif
Rendre le moteur robuste au redimensionnement de la fenêtre : à chaque `WindowEvent::Resized`,
reconfigurer la surface et recréer la depth texture à la nouvelle taille, en gardant la
scène et les pipelines valides.
**Critère d'acceptation** : redimensionner la fenêtre (agrandir, rétrécir) ne produit ni
crash, ni artefact, ni erreur de validation wgpu ; le rendu se met à jour à la nouvelle
taille ; le rapport hauteur/largeur (aspect) de la projection suit la fenêtre en continu.
## Périmètre
**Inclus**
- Handler `WindowEvent::Resized` dans `AppRunner::window_event` (`lib/src/app.rs`).
- Reconfiguration de la surface (`Context::configure`) à la nouvelle taille.
- Recréation de la depth texture (`Renderer::resize_depth`).
- Garde contre les tailles invalides (0).
- Mise à jour du format de surface et re-synchronisation Renderer ↔ Scene si nécessaire.
**Hors périmètre**
- Le present mode (FIFO figé — voir Notes de Décision ROADMAP).
- Le culling/rendu GPU-driven (Phase 3).
- La LOD / batching / HDR (Phase 4.3).
- Toute modification du shader ou des bind groups.
## Décisions
| Id | Décision | Raison |
|----|----------|--------|
| D1 | Le resize est encapsulé dans une méthode `App::resize(&mut self, w, h)` appelée par `AppRunner::window_event` | La logique reste testable et réutilisable, distincte de la couche winit ; `window_event` reste fine (dispatch uniquement). |
| D2 | La depth texture est recréée via `Renderer::resize_depth(w, h)`, qui réutilise le helper `create_depth_texture` existant | Cohérent avec l'Étape 9 (D3) ; le helper isolé existe déjà et est prêt. |
| D3 | Garde `if w == 0 \|\| h == 0 { return; }` : on ne reconfigure jamais à une taille nulle (fenêtre minimisée) | `Resized(0x0)` arrive au minimize ; configurer une surface 0×0 est une erreur wgpu. |
| D4 | Le format de surface est comparé avant/après `configure` : s'il change, on re-synchronise Renderer et Scene ; dans le cas normal (stable sRGB) on se contente du swap surface+depth | Le format est déterministe pour une même fenêtre (premier sRGB) ; le cas « format changé » est rare mais documenté et structuré. |
| D5 | L'aspect de projection reste dérivé en continu de `window().inner_size()` dans `App::render_scene` (déjà le cas) — pas de champ de taille « stale » à maintenir | Évite un état redondant : la source de vérité du rendu est la taille live de la fenêtre. Les champs `App.width/height` ne servent qu'à l'init. |
| D6 | Pendant une taille invalide (0), on ne rend pas la frame (garde dans `RedrawRequested`) | Évite un `get_next_frame()`/rendu sur une surface de taille nulle. |
## Tâches détaillées
### 11.1 `App::resize` (orchestration du resize)
- [ ] Ajouter une méthode `resize(&mut self, width: u32, height: u32) -> Result<(), WsgError>` sur `App` (`lib/src/app.rs`) :
- [ ] Récupérer `self.context` (Option) → ok/err si absent.
- [ ] `let new_format = self.context().configure(&self.context().adapter, width, height)?;`
(reconfigure la surface à la nouvelle taille ; renvoie le format choisi).
- [ ] `self.renderer_mut().resize_depth(width, height);` (recrée depth à la nouvelle taille).
- [ ] Synchroniser le format : mettre à jour `Renderer::format` (via un setter, cf. 11.2)
et, si `new_format != format_anterieur`, re-synchroniser la Scene (cf. D4).
- [ ] Retourner `Ok(())`.
- [ ] Gérer l'erreur : si `configure` échoue, propager `WsgError` sans crash silencieux.
### 11.2 `Renderer::resize_depth` (+ synchronisation du format)
- [ ] Ajouter `resize_depth(&mut self, width: u32, height: u32)` sur `Renderer` (`lib/src/core/renderer.rs`) :
- [ ] `let (t, v) = create_depth_texture(&self.device, width, height);`
- [ ] Remplacer `self._depth_texture = t; self.depth_view = v;` (l'ancienne texture est
dropée par le remplacement — pas de fuite, pas de double allocation).
- [ ] (Optionnel, D4) Ajouter un accès pour mettre à jour `Renderer::format` si le format change ;
le champ `format` est actuellement `wgpu::TextureFormat` non-mutable depuis l'extérieur.
- [ ] Vérifier que `create_depth_texture` reste privé au module (pas d'exposition publique).
### 11.3 Handler `WindowEvent::Resized` dans `AppRunner::window_event`
- [ ] Dans le `match event` de `window_event` (`lib/src/app.rs`), ajouter :
- [ ] `WindowEvent::Resized(size)`:
- [ ] `let w = size.width as u32; let h = size.height as u32;`
- [ ] `if w == 0 || h == 0 { return; }` (D3).
- [ ] `if let Err(e) = app.resize(w, h) { /* log/propager ; ne pas paniquer */ }`
- [ ] `app.window().request_redraw();` pour rendre immédiatement la nouvelle taille.
- [ ] (D6) Dans `WindowEvent::RedrawRequested`, gardez la garde : si la taille courante de la
fenêtre est 0, sauter `get_next_frame()`/`render`/`present` pour cette frame.
### 11.4 Synchronisation du format Renderer ↔ Scene (D4)
- [ ] Après `configure`, comparer `new_format` avec le format en cours :
- [ ] Cas normal (identique) : rien à faire, le swap surface+depth suffit.
- [ ] Cas format changé : re-initialiser le GPU de la Scene (`Scene::init_gpu(device, queue,
new_format)`) et documenter que les pipelines doivent être revalids — pour le MVP,
lever une erreur explicite (le format est stable pour une même fenêtre ; ce chemin est
structuré mais pas exercé couramment).
- [ ] S'assurer que `Renderer::format` et `Scene` (via `SceneGpu`) portent le même format.
### 11.5 Exemple / vérification manuelle
- [ ] Aucun exemple ne doit changer de code (le resize est transparent).
- [ ] Vérification manuelle : lancer `cube` (ou `simple`), redimensionner la fenêtre
(agrandir + rétrécir), confirmer : pas de crash, pas d'artefact, rendu correct à la
nouvelle taille, aspect correct (pas de distorsion de la projection).
## Fichiers touchés
| Fichier | Changement |
|---------|-----------|
| `lib/src/app.rs` | `App::resize` (nouveau) ; `window_event` : branchement `Resized` + garde 0 dans `RedrawRequested`. |
| `lib/src/core/renderer.rs` | `Renderer::resize_depth` (nouveau) ; éventuellement setter du format (D4). |
| `lib/src/core/context.rs` | **Aucun** — `configure` existe déjà et reconfigure à une taille donnée. |
| `lib/src/scene/scene.rs` | **Aucun** en cas normal ; seul le chemin format-changé (D4) toucherait `init_gpu`. |
> `create_depth_texture` (helper) et `Context::configure` existent déjà — réutilisés, pas créés.
## Check-list de vérification
- [ ] Redimensionner la fenêtre n'émet aucune erreur de validation wgpu (attachments color/depth
de même taille).
- [ ] La surface est reconfigurée à la taille réelle de la fenêtre (`Context::configure`).
- [ ] La depth texture est recréée à la taille réelle (`Renderer::resize_depth`).
- [ ] Pas de crash au minimize (garde `w==0 || h==0`, D3) ni au restore.
- [ ] L'aspect de la projection suit la fenêtre (pas de distorsion au redimensionnement).
- [ ] Le format Renderer == format Scene (synchronisation D4).
- [ ] `cargo build --workspace` et `cargo test --workspace` passent ; exemples `cube`/`simple`
compiles sans changement.
- [ ] Pas de fuite : l'ancienne depth texture est bien dropée au remplacement.
## Risques & notes
- **Minimize/restore** : `Resized(0x0)` puis `Resized(n,m)` — la garde D3 + la reconfiguration au
restore couvrent le cycle complet.
- **Format changé** : pathologique pour une même fenêtre (le choix est déterministe). Structuré
mais non exercé couramment (D4) ; si jamais un backend change de format, le MVP lève une
erreur claire plutôt que de produire un rendu corrompu.
- **Présence du `adapter`** : `configure` nécessite `&adapter` — `Context` porte déjà `adapter`
(utilisé dans `resumed`), donc accessible sans nouvelle API.