Files
wsg/docs/DRAFT.md
T

15 KiB

DRAFT — Plan d'implémentation : « Rattachement PipelineCache → Scene & Mesh → Material »

Usage. Ce fichier (dans docs/) sert de brouillon pour 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. Consolidation des ressources (PLAN Phase 2 : associer le PipelineCache à la Scene ; Mesh référence son Material) — recouvre la partie non cochée de la Phase 1 (ROADMAP 1.2, refactor matériau de Mesh) sans toucher au refactor Arc<Geometry> (reporté).

Objectif. La gestion des matériaux est entièrement portée par la Scene (elle possède device + format + PipelineCache, et fabrique elle-même meshes et matériaux). Chaque Mesh possède une référence vers son Material ; l'entité ne porte plus de material_id (le lien vit sur le mesh). L'API déclarative reste AppBuilder + scène automatique, sans wgpu dans les exemples.

État de départ vérifié (2026-09-17).

  • App porte cache: Option<PipelineCache> (champ privé, accesseur App::cache()), indépendant de la Scene. Les exemples font Material::new(format, id, app.cache()) puis scene.add_material(...).
  • Scene ne possède ni device, ni format, ni cache : elle ne fait que stocker des Arc<Mesh> / Arc<Material> préfabriqués et résout les entités en (mesh_id, material_id).
  • Le lien mesh→matériau est porté par Entity { mesh_id, material_id, transform } ; Mesh (resources/mesh.rs) est un conteneur GPU pur (vertex_buffer, index_buffer, num_vertices, num_indices), sans matériau.
  • Renderer::render_scene itère scene.iter_entities() qui re-parse (label, &Mesh, &Material, &Transform) et appelle draw_entity ; les buffers frame/object sont déjà portés par le Renderer (Étape 3-4).
  • manual.rs est indépendant de App/Scene : il possède son propre PipelineCache local (chemin bas-niveau). Il n'est pas concerné par le déplacement du cache, seulement revalidé.
  • Tests : seul lib/tests/wgsl_validate.rs (naga) — aucun test ne dépend des signatures modifiées (add_entity, add_material, Mesh, Material, iter_entities).

Point d'étape — 2026-09-17. Étape 7 implémentée : Scene possède gpu: Option<SceneGpu> (device + format + PipelineCache), Mesh porte Option<Arc<Material>>, Entity n'a plus de material_id, iter_entities rend (&str, &Arc<Mesh>, &Transform), le Renderer résout le matériau (mesh.material() sinon scene.default_material()), App n'a plus de champ cache (init_gpu fait le branchement dans resumed), exemples cube/simple migrés vers register_shader + add_material_shader + create_mesh + add_entity, manual inchangé. Validation : cargo build --workspace --examples 0 warning, cargo test --workspace vert (3 tests), cargo doc --no-deps 0 warning, cargo fmt --all ; exécution fenêtrée cargo run -p wsg-lib --example simple/cube lancée (buffering, aucune panique).


Étape 7.1 — La Scene possède le contexte pipeline (device + format + cache)

But : donner à la Scene de quoi fabriquer elle-même pipelines et meshes, à la place de App.

  • 7.1.1 Nouveau champ Scene.gpu: Option<SceneGpu> dans scene/scene.rs, avec
    pub struct SceneGpu {
        device: Arc<wgpu::Device>,
        format: wgpu::TextureFormat,
        cache: PipelineCache,
    }
    
    Scene::new() → gpu: None (la Scene reste constructible sans GPU, cf. AppBuilder::build).
  • 7.1.2 Scene::init_gpu(&mut self, device: Arc<wgpu::Device>, format: wgpu::TextureFormat) -> &mut Self : pose gpu = Some(SceneGpu { device, format, cache: PipelineCache::new(device.clone()) }) puis &mut *self. Appelé une fois dans AppRunner::resumed, juste après la création du Context/Renderer et avant handler.setup(app) (setup enregistre shaders/matériaux/meshes/entités → doit trouver le gpu prêt).
  • 7.1.3 Accesseurs pub gardés (panique si gpu absent, message « scene pipeline not initialized yet ») :
    • scene.device() -> &wgpu::Device
    • scene.format() -> wgpu::TextureFormat
    • scene.cache() -> &PipelineCache et scene.cache_mut() -> &mut PipelineCache
    • scene.register_shader(id, path) -> Result<String, String> (délègue à cache.register_shader, erreur si ID déjà pris)
  • 7.1.4 Retirer la duplication de cache d'App (app.rs) :
    • supprimer le champ cache: Option<PipelineCache> et AppBuilder n'initialise plus cache: None ;
    • resumed ne crée plus let cache = PipelineCache::new(device) séparément — il appelle scene.init_gpu(...) ;
    • accesseur App::cache() : supprimé (les exemples passent à app.scene.cache()), ou conservé en mince délégation self.scene.cache_mut() si l'on veut préserver l'API (voir Décisions).
  • Validation : cargo check --workspace --examples 0 warning ; les exemples compilent (appelés à migrer en 7.4). cargo doc --no-deps 0 warning.

Étape 7.2 — La Scene fabrique matériaux & meshes (liage matériau sur le mesh)

But : le Mesh porte son Material ; la Scene construit matériaux (via son cache+format) et meshes (via son device) sans que l'utilisateur touche Material::new / Mesh::new.

  • 7.2.1 Mesh gagne un champ matériau (resources/mesh.rs) :
    pub struct Mesh {
        pub vertex_buffer: wgpu::Buffer,
        pub index_buffer: Option<wgpu::Buffer>,
        pub num_vertices: u32,
        pub num_indices: u32,
        material: Option<Arc<Material>>,          // nouveau
    }
    
    • Mesh::new(device, verts, indices) inchangé → material: None.
    • Nouveau Mesh::with_material(device, verts, indices, material: Arc<Material>) (helper).
    • Accesseurs : mesh.material() -> Option<&Arc<Material>>, mesh.set_material(Arc<Material>).
  • 7.2.2 Scene::add_material_shader(&mut self, id, shader_id) -> Result<String, String> : exige gpu ; construit Material::new(self.format(), shader_id, self.cache_mut()), insère dans materials, retourne id.
  • 7.2.3 Scene::create_mesh(&mut self, id, vertices: &[Vertex], indices: Option<&[u16]>, material: Option<&str>) -> Result<String, String> : exige gpu ; let mut m = Mesh::new(self.device(), verts, indices) ; si material = Some(name) → résout Arc<Material> depuis materials (erreur si absent) et m.set_material(...) ; insère Arc::new(m).
  • 7.2.4 Conserver les chemins custom pour l'utilisateur avancé :
    • add_material(id, Arc<Material>) et add_mesh(id, Arc<Mesh>) inchangés (le Mesh custom peut ensuite être lié via mesh.set_material(...) ou rester sans matériau → défaut en 7.3.5).
  • Validation : cargo check --workspace --examples 0 warning ; cargo doc --no-deps OK ; un test unitaire additionnel si souhaité (Scene::add_material_shader/create_mesh nécessitent un gpu — test hors-suite GPU, validation par compilation des exemples).

Étape 7.3 — Le lien mesh→matériau remplace le material_id d'entité

But : Entity ne référencie plus qu'un mesh_id ; le rendu résout le matériau depuis le mesh.

  • 7.3.1 Entity (scene/entity.rs) : retirer le champ material_id.
    pub struct Entity { mesh_id: String, transform: Transform }
    
    Entity::new(mesh_id, transform) ; supprimer material_id(), garder mesh_id(), transform(), set_transform().
  • 7.3.2 Scene — signatures d'entité sans material_id :
    • add_entity(&mut self, label, mesh_id) → identité.
    • add_entity_with_transform(&mut self, label, mesh_id, transform).
    • Validation d'existence inchangée : mesh_id doit exister dans meshes (le matériau est implicite → on ne valide plus material_id).
  • 7.3.3 iter_entities() → impl Iterator<Item = (&str, &Arc<Mesh>, &Transform)> + '_ : résout meshes.get(entity.mesh_id()), n'appelle plus get_material. Le matériau arrive via mesh.material().
  • 7.3.4 Renderer::render_scene (core/renderer.rs) : boucler sur (label, mesh, transform) ; matériau = mesh.material().cloned() sinon scene.default_material() (7.3.5). draw_entity inchangé (reçoit &Material, pose pipeline + bind groups frame/object).
  • 7.3.5 Scene::default_material(&self) -> Arc<Material> : matériau standard fabriqué paresseusement (une fois, mis en cache) depuis SceneGpu. Note : le flat « unlit » reste orthogonal — c'est le flag Renderer::set_unlit qui rend le flat (options.x du shader) ; le matériau par défaut est simplement le pipeline standard. La « variante unlit » du PLAN = le rendu flat activé par le Renderer, pas une propriété du Material.
  • Validation / cas limites : scène vide (aucune entité → pass vide, comme avant) ; mesh sans matériau → default_material() ; mesh 0-vertex → draw_entity continue de retourner tôt (num_vertices == 0).

Étape 7.4 — Migration des exemples

  • cube.rs (API déclarative, sans wgpu) — setup devient :
    app.scene.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH).unwrap();
    app.scene.add_material_shader("cube_material", "standard").unwrap();
    app.scene.create_mesh("cube_mesh", &cube_vertices(), Some(&cube_indices()), Some("cube_material")).unwrap();
    app.scene.add_entity("cube", "cube_mesh").unwrap();
    
    La rotation dans AppHandler::update (set_entity_transform) est inchangée.
  • simple.rs (quad plat, unlit) — setup :
    app.renderer_mut().set_unlit(true);
    app.scene.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH).unwrap();
    app.scene.add_material_shader("standard_material", "standard").unwrap();
    app.scene.create_mesh("quad_mesh", &vertices, Some(&indices), Some("standard_material")).unwrap();
    app.scene.add_entity("quad", "quad_mesh").unwrap();
    
    (Le comportement est identique : le quad flat vient du flag set_unlit(true).)
  • manual.rs : inchangé (bas-niveau, son propre PipelineCache local, hors Scene). À revalider uniquement : cargo check --workspace --examples 0 warning ; il compile et tourne (quad flat).
  • Validation : cargo run -p examples cube (rotation + éclairage visibles) et simple (quad flat) sans panique ; cargo check --workspace --examples 0 warning ; cargo test --workspace vert.

Étape 7.5 — Exports, docs & commits

  • Exports : vérifier que les nouveaux types/méthodes sont pub et re-exportés là où l'API le promet (resources/mod.rs déjà pub use, scene/mod.rs déjà pub use) ; #![warn(missing_docs)] → tous doc.
  • Docs :
    • docs/PLAN.md : cocher les 3 lignes non cochées de la Phase 2 — « Associer le PipelineCache à la Scene », « chaque Mesh possède une référence vers un Material », « injection automatique du standard_shader ».
    • docs/ROADMAP.md : cocher la sous-partie matériau du 1.2 ; laisser non coché le refactor Arc<Geometry> (stockage Geometry dans Mesh) qui reste reporté.
    • README.md : refléter l'API déclarative (si les exemples changent).
  • Validation finale : cargo check --workspace 0 warning ; cargo doc --no-deps 0 warning ; cargo fmt --all ; cargo test --workspace.
  • Commits conventionnels : refactor(resources): Scene owns pipeline cache, Mesh->Material link puis docs: mark PLAN Phase 2 / ROADMAP 1.2(material) (deux commits séparés code/docs, comme à l'Étape 5).

Décisions actées (à verrouiller avant l'implémentation)

Décision Option proposée / actée Justification
Où vit le PipelineCache Dans la Scene (SceneGpu.cache) ; App n'a plus de champ cache PLAN Phase 2 : « la gestion des matériaux entièrement portée par la scène » ; App = orchestrateur mince
Construction des matériaux Par la Scene (add_material_shader) depuis un shader_id, via son cache+format ; add_material(Arc<Material>) conservé pour usage custom un seul point de vérité device/format ; les exemples ne touchent plus Material::new
Liage mesh→matériau Le Mesh possède Option<Arc<Material>> ; l'entité ne porte plus material_id PLAN : « chaque Mesh possède une référence vers un Material » ; API déclarative simplifiée ; iter_entities plus léger
Entity { mesh_id, transform } uniquement le matériau est déduit du mesh ; une dimension de moins à synchroniser
Matériau par défaut Injeté par Scene::default_material() = shader standard ; le flat reste Renderer::set_unlit PLAN : injection auto du standard_shader ; « variante unlit » = flag rendu, orthogonal au Material
Partage « 1 mesh, N matériaux » Abandonné au MVP : un mesh = un matériau ; le cas d'usage est reporté au step « handles/arènes » (ROADMAP Phase 2) cohérence avec l'entité simplifiée ; trade-off assumé et tracé (voir Réflexions)
API App::cache() Supprimée, exemples migrés vers app.scene (ou mince délégation si compat souhaitée) évite deux chemins d'accès au cache ; manual.rs garde son cache local (découplé)

Réflexions / trade-offs

  • Perte du partage multi-matériaux par mesh. Aujourd'hui deux entités peuvent référencer le même mesh_id avec deux material_id différents. En liant le matériau au mesh, ce cas n'est plus exprimable. C'est un choix MVP assumé (le PLAN le demande) ; il reviendra naturellement avec les handles typés et le conteneur centralisé (ROADMAP Phase 2, ARCHI_RENDU : tri de rendu par matériau). Le batching n'est pas perdu : il se fera sur mesh.material().pipeline au lieu de material_id.
  • Le default_material() dépend de gpu. Une Scene non initialisée (avant resumed) ne peut pas fabriquer de matériau. Toute fabrication (matériau, mesh) panique avec un message clair si gpu est absent — ce ne peut arriver en pratique que si handler.setup est appelé hors de resumed (invariant garanti par l'API).
  • Ne pas confondre avec ROADMAP 1.2 (refactor Arc<Geometry>). Ce DRAFT lie le matériau au mesh. Le fait que Mesh stocke aussi Arc<Geometry> (positions/indices/normales/uvs en CPU) reste reporté et sera une étape distincte.

Liens / vérification finale

  • cargo check --workspace --examples 0 warning.
  • cargo test --workspace (Pod + naga) vert.
  • cargo doc --no-deps 0 warning.
  • cargo fmt --all.
  • Exemples : cube (lit, rotation) et simple (unlit, quad) tournent sans panique ; manual compile et tourne.