Files
wsg/docs/DRAFT.md
T
Jérôme Bousquié 628c125925 docs: move DRAFT.md into docs/ and confirm step decisions
DRAFT.md now lives in docs/. Confirmed the pending design decisions:
render() auto-renders the scene by default (Option A, alternative B removed),
and simple.rs uses app.renderer.device()/format() via the library API
without importing wgpu.
2026-09-16 10:23:42 +02:00

7.7 KiB

DRAFT — Brouillon d'implémentation

Usage. Ce fichier (dans docs/) sert de brouillon pour noter les idées et le plan détaillé de l'étape en cours. Son contenu est effacé au début de chaque nouvelle étape. La source de vérité de l'état est le code + README.md ; les autres docs docs/* restent stables.


Étape : Rendu automatique de la scène + vue de frame exposée

1. Contexte (état réel au 2026-09-16)

  • App::run() : acquiert Frame via context.get_next_frame(), appelle handler.render(&mut self), puis renderer.present(frame).
  • ⚠️ handler.render() ne reçoit pas la frame : le callback ne peut rien dessiner. C'est précisément le point bloquant signalé par docs/PLAN.md (§ Phase 2 intégra Scene, check-list) et docs/ROADMAP.md (point de départ : « render() ne peut pas encore dessiner — vue de frame non exposée »).
  • Renderer::render(view, mesh, material) existe et fonctionne (usage bas-niveau dans manual.rs) : il ouvre 1 encoder + 1 render pass par objet, dessine, soumet.
  • Scene a déjà : add_mesh, add_material, add_entity, iter_entities() -> (label, mesh, mat), get_mesh, get_material, remove_entity.
  • Material porte déjà sa Arc<RenderPipeline> (compilée via PipelineCache). La scène stocke des Arc<Material>. Donc pour dessiner une scène, le code n'a pas besoin de consulter le cache : chaque matériau détient sa pipeline. Le « lien PipelineCache → Scene » du PLAN est donc conceptuel, pas indispensable côté rendu pour cette étape.

2. Objectif

  1. Que AppHandler::render() reçoive la vue/frame courante.
  2. Que la scène se rende automatiquement (app.render(scene)), sans que simple.rs touche à wgpu.
  3. Que simple.rs affiche le quad (4 sommets, 6 indices, matériau basic), en gardant ~15 lignes.

3. Plan d'implémentation (détail, dans l'ordre)

Étape 3.1 — Exposer la vue de frame au callback

Fichier : lib/src/handler.rs (+ app.rs).

Changer la signature :

fn render(&mut self, app: &mut App, frame: &Frame);
  • Frame est un type de bibliothèque (core::Frame) qui expose frame.view() → &wgpu::TextureView. C'est plus riche et plus stable que de passer le TextureView brut : on garde une API bibliothèque.
  • Adapter handler.rs docs (consignes docs/DOCUMENTATION.md : backticks, description ≤3 lignes, ce que/qui/quand).

Fichier : lib/src/app.rs, dans App::run, branche RedrawRequested :

let frame = self.context.get_next_frame();
handler.render(&mut self, &frame); // frame est owned (valeur locale) → pas de conflit de borrow avec &mut self
self.renderer.present(frame);

Point d'attention borrow : frame est une valeur owned détachée de self.context une fois acquise, on peut donc la passer par référence en même temps que &mut self sans erreur du borrow checker.

Étape 3.2 — Méthode de rendu de scène groupé

Fichier : lib/src/core/renderer.rs.

Le Renderer::render(view, mesh, material) actuel ouvre un encoder+pass par objet (N submits par frame si appelé en boucle). Pour rendre une scène entière proprement, ajouter un rendu batch :

pub fn render_scene(&self, view: &wgpu::TextureView, scene: &Scene) {
    let mut encoder = self.device.create_command_encoder(...);
    {
        let mut pass = encoder.begin_render_pass(/* color attachment: view */);
        for (_label, mesh, material) in scene.iter_entities() {
            pass.set_pipeline(&material.pipeline);
            pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
            if let Some(ib) = &mesh.index_buffer {
                pass.set_index_buffer(ib.slice(..), wgpu::IndexFormat::Uint16);
                pass.draw_indexed(0..mesh.num_indices, 0, 0..1);
            } else {
                pass.draw(0..mesh.num_vertices, 0..1);
            }
        }
    }
    self.queue.submit(once(encoder.finish()));
}
  • Batching : un seul pass pour toutes les entités (aligné sur le principe « batching par matériau » évoqué dans renderer.rs / README Phase 4.3). On évite N submits/encoder alloués à la volée.
  • Conserver render(view, mesh, material) (API bas-niveau utilisée par manual.rs). Le battle placer du code commun (layout pass / draw d'un mesh) dans un petit helper privé pour éviter la duplication.
  • Import crate::scene::Scene.
  • Documenter selon docs/DOCUMENTATION.md.

Étape 3.3 — Automatisation côté App

Fichier : lib/src/app.rs.

Ajouter sur App :

pub fn render_scene(&self, view: &wgpu::TextureView) {
    self.renderer.render_scene(view, &self.scene);
}

Le rendu automatique est branché par défaut dans le trait (Option A, décidée) :

fn render(&mut self, app: &mut App, frame: &Frame) {
    app.render_scene(frame.view());
}

→ simple.rs n'implémente même pas render : la scène se rend toute seule, exactement l'esprit « scene auto-render ». L'utilisateur avancé peut surcharger render pour contrôler le dessin.

Étape 3.4 — Remplir simple.rs

Fichier : lib/examples/simple.rs.

  • Créer le quad (mêmes 4 sommets + 6 indices que dans manual.rs, mais sans toucher à wgpu : tout se fait via Scene + AppBuilder).
  • Enregistrer le shader : app.cache.register_shader("basic", utils::BASIC_SHADER_PATH) (le shader_id "basic" fonctionne déjà en fallback sur BASIC_SHADER, cf. pipeline_cache.rs).
  • Créer le matériau avec Material::new(app.renderer.format(), "basic", &mut app.cache).
  • Créer le mesh avec Mesh::new(app.renderer.device(), &vertices, Some(&indices)).
  • Enregistrer dans app.scene : add_mesh, add_material, add_entity.
  • Tout ce remplissage se fait dans AppHandler::update() (ou dans run() avant app.run(...) — à voir selon où app est constructible ; le plus simple : dans update(&mut self, app) une fois).

⚠️ Les vertex passent par Mesh::new(device, ...) qui exige wgpu::Device. Décision prise : pour l'étape 1, utiliser app.renderer.device()/app.renderer.format() (accès bibliothèque — l'utilisateur n'importe pas wgpu). Un helper haut niveau Scene::add_quad_entity pourra être ajouté plus tard.

Étape 3.5 — Validation

cargo check --workspace
cargo doc -p wsg-lib --no-deps      # exigence : "generated 0 warnings"
cargo run -p wsg-lib --example simple   # le quad doit s'afficher
cargo run -p wsg-lib --example manual   # le workflow manuel doit rester fonctionnel
cargo fmt --all
  • Vérifier docs (docs/DOCUMENTATION.md) : zéro warning, backticks, chaque item public documenté.
  • Committer proprement (conventional commits, ex. feat(app): expose frame view and auto-render scene).

4. Décisions (actées)

Sujet Décision
Signature de render Ajouter &Frame en paramètre (Option A : default → auto-render)
Où dessiner la scène Renderer::render_scene(view, &Scene) en batch (1 pass unique)
PipelineCache dans Scene Non bougé pour cette étape : les Material portent déjà leur pipeline ; le lien conceptuel cache↔scene est reporté
wgpu dans simple.rs Via app.renderer.device()/format() : l'utilisateur n'importe pas wgpu
Helper quad haut niveau Reporté (éventuel Scene::add_quad_entity)

5. Notes ouvertes / idées

  • Le "label" d'entité n'est pour l'instant pas utilisé au rendu (juste itéré). OK pour le MVP.
  • num_indices == 0 dans le cas non indexé : bien gérer le branchement indexé/non indexé (copié depuis Renderer::render actuel).
  • Après cette étape, l'ajout de lumières/textures/caméras = simple ajout de données à la Scene (voir docs/ROADMAP.md Phases 2-4 et docs/PLAN.md Phase 4).