docs(draft): plan Étape 11 — resize (surface + depth, Phase 4.4)
This commit is contained in:
+139
-4
@@ -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é
|
> 📅 **2026-09-18** — Plan de l'étape suivante.
|
||||||
> 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 l'étape.
|
||||||
> Source de vérité = code + README.md. Ce document est vidé à la complétion de chaque é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.
|
||||||
|
|||||||
Reference in New Issue
Block a user