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 docsdocs/*restent stables.Étape. Consolidation des ressources (PLAN Phase 2 : associer le
PipelineCacheà laScene;Meshréférence sonMaterial) — recouvre la partie non cochée de la Phase 1 (ROADMAP 1.2, refactor matériau deMesh) sans toucher au refactorArc<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). ChaqueMeshpossède une référence vers sonMaterial; l'entité ne porte plus dematerial_id(le lien vit sur le mesh). L'API déclarative resteAppBuilder+ scène automatique, sans wgpu dans les exemples.
État de départ vérifié (2026-09-17).
Appportecache: Option<PipelineCache>(champ privé, accesseurApp::cache()), indépendant de laScene. Les exemples fontMaterial::new(format, id, app.cache())puisscene.add_material(...).Scenene possède ni device, ni format, ni cache : elle ne fait que stocker desArc<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_sceneitèrescene.iter_entities()qui re-parse(label, &Mesh, &Material, &Transform)et appelledraw_entity; les buffers frame/object sont déjà portés par leRenderer(Étape 3-4).manual.rsest indépendant deApp/Scene: il possède son proprePipelineCachelocal (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 :
Scenepossèdegpu: Option<SceneGpu>(device + format +PipelineCache),MeshporteOption<Arc<Material>>,Entityn'a plus dematerial_id,iter_entitiesrend(&str, &Arc<Mesh>, &Transform), leRendererrésout le matériau (mesh.material()sinonscene.default_material()),Appn'a plus de champcache(init_gpufait le branchement dansresumed), exemplescube/simplemigrés versregister_shader+add_material_shader+create_mesh+add_entity,manualinchangé. Validation :cargo build --workspace --examples0 warning,cargo test --workspacevert (3 tests),cargo doc --no-deps0 warning,cargo fmt --all; exécution fenêtréecargo run -p wsg-lib --example simple/cubelancé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>dansscene/scene.rs, avecpub struct SceneGpu { device: Arc<wgpu::Device>, format: wgpu::TextureFormat, cache: PipelineCache, }Scene::new()→gpu: None(laScenereste constructible sans GPU, cf.AppBuilder::build). - 7.1.2
Scene::init_gpu(&mut self, device: Arc<wgpu::Device>, format: wgpu::TextureFormat) -> &mut Self: posegpu = Some(SceneGpu { device, format, cache: PipelineCache::new(device.clone()) })puis&mut *self. Appelé une fois dansAppRunner::resumed, juste après la création duContext/Rendereret avanthandler.setup(app)(setup enregistre shaders/matériaux/meshes/entités → doit trouver legpuprêt). - 7.1.3 Accesseurs
pubgardés (panique sigpuabsent, message « scene pipeline not initialized yet ») :scene.device() -> &wgpu::Devicescene.format() -> wgpu::TextureFormatscene.cache() -> &PipelineCacheetscene.cache_mut() -> &mut PipelineCachescene.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>etAppBuildern'initialise pluscache: None; resumedne crée pluslet cache = PipelineCache::new(device)séparément — il appellescene.init_gpu(...);- accesseur
App::cache(): supprimé (les exemples passent àapp.scene.cache()), ou conservé en mince délégationself.scene.cache_mut()si l'on veut préserver l'API (voir Décisions).
- supprimer le champ
- Validation :
cargo check --workspace --examples0 warning ; les exemples compilent (appelés à migrer en 7.4).cargo doc --no-deps0 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
Meshgagne 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>: exigegpu; construitMaterial::new(self.format(), shader_id, self.cache_mut()), insère dansmaterials, retourneid. - 7.2.3
Scene::create_mesh(&mut self, id, vertices: &[Vertex], indices: Option<&[u16]>, material: Option<&str>) -> Result<String, String>: exigegpu;let mut m = Mesh::new(self.device(), verts, indices); simaterial = Some(name)→ résoutArc<Material>depuismaterials(erreur si absent) etm.set_material(...); insèreArc::new(m). - 7.2.4 Conserver les chemins custom pour l'utilisateur avancé :
add_material(id, Arc<Material>)etadd_mesh(id, Arc<Mesh>)inchangés (leMeshcustom peut ensuite être lié viamesh.set_material(...)ou rester sans matériau → défaut en 7.3.5).
- Validation :
cargo check --workspace --examples0 warning ;cargo doc --no-depsOK ; un test unitaire additionnel si souhaité (Scene::add_material_shader/create_meshnécessitent ungpu— 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 champmaterial_id.pub struct Entity { mesh_id: String, transform: Transform }Entity::new(mesh_id, transform); supprimermaterial_id(), gardermesh_id(),transform(),set_transform(). - 7.3.2
Scene— signatures d'entité sansmaterial_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_iddoit exister dansmeshes(le matériau est implicite → on ne valide plusmaterial_id).
- 7.3.3
iter_entities()→impl Iterator<Item = (&str, &Arc<Mesh>, &Transform)> + '_: résoutmeshes.get(entity.mesh_id()), n'appelle plusget_material. Le matériau arrive viamesh.material(). - 7.3.4
Renderer::render_scene(core/renderer.rs) : boucler sur(label, mesh, transform); matériau =mesh.material().cloned()sinonscene.default_material()(7.3.5).draw_entityinchangé (reçoit&Material, pose pipeline + bind groups frame/object). - 7.3.5
Scene::default_material(&self) -> Arc<Material>: matériaustandardfabriqué paresseusement (une fois, mis en cache) depuisSceneGpu. Note : le flat « unlit » reste orthogonal — c'est le flagRenderer::set_unlitqui rend le flat (options.x du shader) ; le matériau par défaut est simplement le pipelinestandard. La « variante unlit » du PLAN = le rendu flat activé par le Renderer, pas une propriété duMaterial. - Validation / cas limites : scène vide (aucune entité → pass vide, comme avant) ; mesh sans matériau
→
default_material(); mesh 0-vertex →draw_entitycontinue de retourner tôt (num_vertices == 0).
Étape 7.4 — Migration des exemples
cube.rs(API déclarative, sans wgpu) —setupdevient :La rotation dansapp.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();AppHandler::update(set_entity_transform) est inchangée.simple.rs(quad plat, unlit) —setup:(Le comportement est identique : le quad flat vient du flagapp.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();set_unlit(true).)manual.rs: inchangé (bas-niveau, son proprePipelineCachelocal, horsScene). À revalider uniquement :cargo check --workspace --examples0 warning ; il compile et tourne (quad flat).- Validation :
cargo run -p examplescube (rotation + éclairage visibles) et simple (quad flat) sans panique ;cargo check --workspace --examples0 warning ;cargo test --workspacevert.
Étape 7.5 — Exports, docs & commits
- Exports : vérifier que les nouveaux types/méthodes sont
pubet re-exportés là où l'API le promet (resources/mod.rsdéjàpub use,scene/mod.rsdé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 refactorArc<Geometry>(stockageGeometrydansMesh) qui reste reporté.README.md: refléter l'API déclarative (si les exemples changent).
- Validation finale :
cargo check --workspace0 warning ;cargo doc --no-deps0 warning ;cargo fmt --all;cargo test --workspace. - Commits conventionnels :
refactor(resources): Scene owns pipeline cache, Mesh->Material linkpuisdocs: 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_idavec deuxmaterial_iddiffé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 surmesh.material().pipelineau lieu dematerial_id. - Le
default_material()dépend degpu. UneScenenon initialisée (avantresumed) ne peut pas fabriquer de matériau. Toute fabrication (matériau, mesh) panique avec un message clair sigpuest absent — ce ne peut arriver en pratique que sihandler.setupest appelé hors deresumed(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 queMeshstocke aussiArc<Geometry>(positions/indices/normales/uvs en CPU) reste reporté et sera une étape distincte.
Liens / vérification finale
cargo check --workspace --examples0 warning.cargo test --workspace(Pod + naga) vert.cargo doc --no-deps0 warning.cargo fmt --all.- Exemples :
cube(lit, rotation) etsimple(unlit, quad) tournent sans panique ;manualcompile et tourne.