From f4df63a1363fb0d61c7b41a0e1a44345d2827865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Bousqui=C3=A9?= Date: Fri, 18 Sep 2026 17:16:04 +0200 Subject: [PATCH] =?UTF-8?q?docs(draft):=20plan=20=C3=89tape=2011=20?= =?UTF-8?q?=E2=80=94=20resize=20(surface=20+=20depth,=20Phase=204.4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/DRAFT.md | 143 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 4 deletions(-) diff --git a/docs/DRAFT.md b/docs/DRAFT.md index 7ef8e34..12f7541 100644 --- a/docs/DRAFT.md +++ b/docs/DRAFT.md @@ -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.