From b0aafefed99270e173729196506a1fdb6da781c9 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:29:45 +0200 Subject: [PATCH] =?UTF-8?q?feat(core):=20=C3=89tape=2011=20=E2=80=94=20res?= =?UTF-8?q?ize=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 | 7 +++++++ docs/ROADMAP.md | 14 ++++++++------ lib/src/app.rs | 41 ++++++++++++++++++++++++++++++++++++++++ lib/src/core/renderer.rs | 17 +++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/docs/DRAFT.md b/docs/DRAFT.md index 12f7541..e748346 100644 --- a/docs/DRAFT.md +++ b/docs/DRAFT.md @@ -3,6 +3,13 @@ > 📅 **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). +> +> ✅ **FAIT (2026-09-18).** `App::resize(w,h)` (`lib/src/app.rs`) reconfigure la surface via +> `Context::configure` et recrée la depth texture via `Renderer::resize_depth` (+ `set_format`), +> avec re-synchronisation de la Scene si le format change (D4). `AppRunner::window_event` +> branche `WindowEvent::Resized` (garde 0×0, D3) et `RedrawRequested` (garde taille nulle, D6). +> `cargo build --workspace`, `cargo test --workspace` et la compilation des exemples passent. +> La vérification visuelle (redimensionner `cube`) reste à faire au runtime. --- diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 54c69eb..08f8a0f 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -148,15 +148,17 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } - [ ] HDR + Tone Mapping (optionnel) ### 4.4 Gestion du Resize (cycle de vie Surface + Depth) -- [ ] Handler `WindowEvent::Resized` dans `AppRunner::window_event` (`app.rs`) +- [x] Handler `WindowEvent::Resized` dans `AppRunner::window_event` (`app.rs`) *(Étape 11, 2026-09-18)* → recalculer `size`, prévenir de ne pas rendre tant que la taille est invalide (0). -- [ ] Reconfigurer la surface (`Context::configure`) à la nouvelle taille. -- [ ] Recréer la depth texture à la nouvelle taille (`Renderer::resize_depth(width, height)`) +- [x] Reconfigurer la surface (`Context::configure`) à la nouvelle taille. +- [x] Recréer la depth texture à la nouvelle taille (`Renderer::resize_depth(width, height)`) — le helper `create_depth_texture` isolé (Étape 9, D3) rend ce recreate trivial. -- [ ] Collecte du nouveau format si la configuration change (srgb etc.) → re-valider la compat pipeline. +- [x] Collecte du nouveau format si la configuration change (srgb etc.) → re-valider la compat pipeline. + *(D4 : `App::resize` compare l'ancien/nouveau format et re-synchronise Renderer (`set_format`) + Scene (`init_gpu`) ; cas pathologique, structuré non exercé couramment)* -> Reporté hors de l'Étape 9 (depth buffer) : l'app ne gère aujourd'hui aucun resize — la surface -> n'est configurée qu'au démarrage (`resumed`). Chantier dédié, acté en D3 (2026-09-18). +> Géré en Étape 11 (2026-09-18) : la surface est désormais reconfigurée à chaque `Resized` et la +> depth texture recréée en même temps (helper `create_depth_texture` isolé, Étape 9, D3). Le present +> mode FIFO reste figé (voir Notes de Décision). --- diff --git a/lib/src/app.rs b/lib/src/app.rs index 44350c3..a7f9091 100644 --- a/lib/src/app.rs +++ b/lib/src/app.rs @@ -135,6 +135,29 @@ impl App { let aspect = size.width as f32 / size.height.max(1) as f32; self.renderer().render_scene(view, &self.scene, aspect); } + + /// Resizes the surface and depth texture to a new window size (ROADMAP Phase 4.4). + /// Reconfigures the surface via `Context::configure` (which returns the chosen format) and + /// recreates the depth texture via `Renderer::resize_depth` so the color and depth attachments + /// stay the same size. If the surface format changes (rare, deterministic per window), the + /// Scene's GPU context is re-initialized to the new format; otherwise the swap alone suffices. + /// Inputs: width/height — the new surface dimensions in pixels. + /// Returns Ok(()) on success or a `WsgError` if the surface cannot be reconfigured. + pub fn resize(&mut self, width: u32, height: u32) -> Result<(), WsgError> { + let context = self.context.as_ref().ok_or(WsgError::SurfaceIncompatible)?; + let old_format = self.renderer().format(); + let new_format = context.configure(&context.adapter, width, height)?; + self.renderer_mut().resize_depth(width, height); + self.renderer_mut().set_format(new_format); + if new_format != old_format { + // Surface format changed: re-wire the Scene's GPU context (device + queue + format) + // so its PipelineCache/pipelines match the new surface format. + let device = std::sync::Arc::new(self.renderer_mut().device().clone()); + self.scene + .init_gpu(device, self.context().queue.clone(), new_format); + } + Ok(()) + } } /// Builder for constructing a configured `App` instance with custom title and dimensions. @@ -277,7 +300,25 @@ impl ApplicationHandler for AppRunner { return; }; match event { + WindowEvent::Resized(size) => { + // Garde (D3) : minimiser la fenêtre envoie Resized(0x0) ; ne jamais reconfigurer à 0. + let w = size.width as u32; + let h = size.height as u32; + if w == 0 || h == 0 { + return; + } + // Étape 11 : reconfigurer surface + depth à la nouvelle taille, puis re-rendre. + if let Err(e) = app.resize(w, h) { + eprintln!("WSG : erreur de resize ({e:?})"); + } + app.window().request_redraw(); + } WindowEvent::RedrawRequested => { + // Garde (D6) : ne pas rendre sur une surface de taille nulle (fenêtre minimisée). + let size = app.window().inner_size(); + if size.width == 0 || size.height == 0 { + return; + } // Rendering logic let frame = app.context().get_next_frame(); diff --git a/lib/src/core/renderer.rs b/lib/src/core/renderer.rs index 09cf27f..1be52b2 100644 --- a/lib/src/core/renderer.rs +++ b/lib/src/core/renderer.rs @@ -167,6 +167,23 @@ impl Renderer { self.write_default_frame_uniforms(); } + /// Recreates the depth texture at a new size, used on window resize (ROADMAP Phase 4.4). + /// The previous depth texture is dropped when its field is replaced — no leak, no double + /// allocation. The helper `create_depth_texture` (Étape 9, D3) is reused so the recreate stays + /// trivial. Inputs: width/height — the new surface dimensions in pixels. + pub fn resize_depth(&mut self, width: u32, height: u32) { + let (depth_texture, depth_view) = create_depth_texture(&self.device, width, height); + self._depth_texture = depth_texture; + self.depth_view = depth_view; + } + + /// Updates the stored surface texture format after a surface reconfigure (ROADMAP Phase 4.4). + /// Used when `Context::configure` returns a different format so the Renderer stays in sync + /// with the surface. Inputs: format — the new surface texture format. + pub fn set_format(&mut self, format: wgpu::TextureFormat) { + self.format = format; + } + /// Rewrites the shared per-frame uniform buffer from the scene's active camera and the current /// viewport aspect, then returns the frame bind group wired to that buffer. Called at the start of /// every `render_scene` so the GPU sees the latest camera matrices and camera position (Étape 4.3).