Files
wsg/docs/DRAFT.md
T

8.9 KiB
Raw Blame History

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.