8.9 KiB
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()retourneSuboptimal/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::ResizeddansAppRunner::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>surApp(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, sinew_format != format_anterieur, re-synchroniser la Scene (cf. D4). - Retourner
Ok(()).
- Récupérer
- Gérer l'erreur : si
configureéchoue, propagerWsgErrorsans crash silencieux.
11.2 Renderer::resize_depth (+ synchronisation du format)
- Ajouter
resize_depth(&mut self, width: u32, height: u32)surRenderer(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::formatsi le format change ; le champformatest actuellementwgpu::TextureFormatnon-mutable depuis l'extérieur. - Vérifier que
create_depth_texturereste privé au module (pas d'exposition publique).
11.3 Handler WindowEvent::Resized dans AppRunner::window_event
- Dans le
match eventdewindow_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, sauterget_next_frame()/render/presentpour cette frame.
11.4 Synchronisation du format Renderer ↔ Scene (D4)
- Après
configure, comparernew_formatavec 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::formatetScene(viaSceneGpu) 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(ousimple), 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) etContext::configureexistent 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 --workspaceetcargo test --workspacepassent ; exemplescube/simplecompiles sans changement.- Pas de fuite : l'ancienne depth texture est bien dropée au remplacement.
Risques & notes
- Minimize/restore :
Resized(0x0)puisResized(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:configurenécessite&adapter—Contextporte déjàadapter(utilisé dansresumed), donc accessible sans nouvelle API.