# DRAFT — Étape 11 : Gestion du Resize (cycle de vie Surface + Depth) > 📅 **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.