feat(core): Étape 11 — resize (surface + depth, Phase 4.4)

This commit is contained in:
Jérôme Bousquié
2026-09-18 17:29:45 +02:00
parent f4df63a136
commit b0aafefed9
4 changed files with 73 additions and 6 deletions
+7
View File
@@ -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.
---
+8 -6
View File
@@ -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).
---
+41
View File
@@ -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<H: AppHandler> ApplicationHandler for AppRunner<H> {
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();
+17
View File
@@ -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).