diff --git a/README.md b/README.md index eaca5cb..21c3239 100644 --- a/README.md +++ b/README.md @@ -93,19 +93,22 @@ impl AppHandler for MyGame { async fn main() -> Result<(), wsg_lib::utils::WsgError> { let app = AppBuilder::new().build().await?; - // Register your scene once (string IDs), then App renders it automatically each frame: + // Register your scene once (string IDs), then App renders it automatically each frame. + // Since Étape 7 the Scene owns the pipeline cache: build materials/meshes through it and + // link the material to the mesh (no material_id on the entity anymore). // app.renderer_mut().set_unlit(true); // select flat 2D rendering (optional) - // app.cache.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)?; - // app.scene.add_mesh("quad", Arc::new(mesh))?; - // app.scene.add_material("mat", Arc::new(Material::new(app.renderer.format(), "standard", &mut app.cache)))?; - // app.scene.add_entity("my_quad", "quad", "mat")?; + // app.scene.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)?; + // app.scene.add_material_shader("mat", "standard")?; // build via the Scene's cache + // app.scene.create_mesh("quad", &vertices, Some(&indices), Some("mat"))?; // mesh links its Material + // app.scene.add_entity("my_quad", "quad")?; app.run(MyGame) } ``` -> API note: `Scene::add_mesh` / `add_material` / `add_entity` and `PipelineCache::register_shader` -> currently return `Result<_, String>` — typed error unification is on the roadmap. +> API note: `Scene::register_shader` / `add_material_shader` / `create_mesh` / `add_entity` and +> `PipelineCache::register_shader` currently return `Result<_, String>` — typed error unification +> is on the roadmap. ## Architecture overview diff --git a/docs/DRAFT.md b/docs/DRAFT.md index 6f58b3e..d96b30e 100644 --- a/docs/DRAFT.md +++ b/docs/DRAFT.md @@ -27,13 +27,23 @@ > - 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` +> (device + format + `PipelineCache`), `Mesh` porte `Option>`, `Entity` n'a plus de +> `material_id`, `iter_entities` rend `(&str, &Arc, &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`. *(l'exécution fenêtrée `cargo run -p examples` n'a pas pu être relancée +> ici — à valider sur poste avec affichage)*. + --- ## É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`** dans `scene/scene.rs`, avec +- [X] **7.1.1 Nouveau champ `Scene.gpu: Option`** dans `scene/scene.rs`, avec ``` pub struct SceneGpu { device: Arc, @@ -42,21 +52,21 @@ } ``` `Scene::new()` → `gpu: None` (la `Scene` reste constructible sans GPU, cf. `AppBuilder::build`). -- [ ] **7.1.2 `Scene::init_gpu(&mut self, device: Arc, format: wgpu::TextureFormat) -> &mut Self`** : +- [X] **7.1.2 `Scene::init_gpu(&mut self, device: Arc, 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 ») : +- [X] **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` (délègue à `cache.register_shader`, erreur si ID déjà pris) -- [ ] **7.1.4 Retirer la duplication de cache d'`App`** (`app.rs`) : +- [X] **7.1.4 Retirer la duplication de cache d'`App`** (`app.rs`) : - supprimer le champ `cache: Option` 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 +- [X] **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) @@ -64,7 +74,7 @@ **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`) : +- [X] **7.2.1 `Mesh` gagne un champ matériau** (`resources/mesh.rs`) : ``` pub struct Mesh { pub vertex_buffer: wgpu::Buffer, @@ -77,15 +87,15 @@ - `Mesh::new(device, verts, indices)` **inchangé** → `material: None`. - Nouveau `Mesh::with_material(device, verts, indices, material: Arc)` (helper). - Accesseurs : `mesh.material() -> Option<&Arc>`, `mesh.set_material(Arc)`. -- [ ] **7.2.2 `Scene::add_material_shader(&mut self, id, shader_id) -> Result`** : exige `gpu` ; +- [X] **7.2.2 `Scene::add_material_shader(&mut self, id, shader_id) -> Result`** : 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`** : +- [X] **7.2.3 `Scene::create_mesh(&mut self, id, vertices: &[Vertex], indices: Option<&[u16]>, material: Option<&str>) -> Result`** : exige `gpu` ; `let mut m = Mesh::new(self.device(), verts, indices)` ; si `material = Some(name)` → résout `Arc` 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é : +- [X] **7.2.4 Conserver les chemins custom** pour l'utilisateur avancé : - `add_material(id, Arc)` et `add_mesh(id, Arc)` **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 +- [X] **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). @@ -93,32 +103,32 @@ **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`. +- [X] **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`** : +- [X] **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, &Transform)> + '_` : +- [X] **7.3.3 `iter_entities()`** → `impl Iterator, &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)` ; +- [X] **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`** : matériau `standard` fabriqué **paresseusement** +- [X] **7.3.5 `Scene::default_material(&self) -> Arc`** : 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** +- [X] **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 : +- [X] **`cube.rs`** (API déclarative, sans wgpu) — `setup` devient : ```rust app.scene.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH).unwrap(); app.scene.add_material_shader("cube_material", "standard").unwrap(); @@ -126,7 +136,7 @@ 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` : +- [X] **`simple.rs`** (quad plat, unlit) — `setup` : ```rust app.renderer_mut().set_unlit(true); app.scene.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH).unwrap(); @@ -135,24 +145,24 @@ 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 +- [X] **`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 +- [X] **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 +- [X] **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** : +- [X] **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` (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 ; +- [X] **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 +- [X] **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). --- diff --git a/docs/PLAN.md b/docs/PLAN.md index 78af3ff..487963c 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -51,7 +51,7 @@ Une fois la plomberie encapsulée, nous devons rendre l'assemblage des objets co ### Intégration de la Scene - [X] Formaliser la structure `Scene` : un conteneur qui liste les Entities. -- [ ] Associer le `PipelineCache` à la Scene pour que la gestion des matériaux soit entièrement portée par la scène (actuellement le cache est porté par `App`, indépendant de la Scene — le rendu de la scène est, lui, déjà automatisé depuis 2026-09-16). +- [X] Associer le `PipelineCache` à la Scene pour que la gestion des matériaux soit entièrement portée par la scène (actuellement le cache est porté par `App`, indépendant de la Scene — le rendu de la scène est, lui, déjà automatisé depuis 2026-09-16). *(fait — 2026-09-17, DRAFT Étape 7 : `Scene::init_gpu` détient device+format+`PipelineCache` ; `App` n'a plus de champ `cache`)* - [X] Implémenter la logique de rendu de la scène : `App::render_scene(view)` parcourt la scène, récupère les matériaux et soumet tous les draw calls en **une seule passe groupée** (`Renderer::render_scene`), appelée automatiquement chaque frame par l'implémentation par défaut @@ -60,8 +60,8 @@ Une fois la plomberie encapsulée, nous devons rendre l'assemblage des objets co ### Gestion des Matériaux et Shaders -- [ ] S'assurer que chaque Mesh possède une référence vers un Material (à l'heure actuelle le lien est porté par l'entité `(mesh_id, material_id)` de la Scene, pas par le Mesh lui-même). -- [ ] Implémenter le comportement par défaut : si aucun matériau n'est assigné, le moteur injecte automatiquement le `standard_shader` (variante unlit) (non implémenté). +- [X] S'assurer que chaque Mesh possède une référence vers un Material (à l'heure actuelle le lien est porté par l'entité `(mesh_id, material_id)` de la Scene, pas par le Mesh lui-même). *(fait — 2026-09-17, DRAFT Étape 7 : `Mesh.material: Option>` ; `Entity { mesh_id, transform }`, plus de `material_id`)* +- [X] Implémenter le comportement par défaut : si aucun matériau n'est assigné, le moteur injecte automatiquement le `standard_shader` (variante unlit) (non implémenté). *(fait — 2026-09-17, DRAFT Étape 7.3.5 : `Scene::default_material()` injecte `standard` ; le flat reste piloté par `Renderer::set_unlit`)* ## Phase 3 : Documentation et Interface (API "User-Friendly") diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index adac5f5..7e6c8d1 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -60,6 +60,11 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } - [ ] `transform: Transform` (état CPU) - [x] Ajouter un mesh de test (cube unitaire) en exemple — **fait** (helper `cube_geometry` dans l'exemple `cube`, Étape 5, 2026-09-17) +> **Note (2026-09-17, DRAFT Étape 7)** : le refactor « Mesh contient `Arc` » ci-dessus reste +> **reporté** (il porte sur le *stockage CPU* des données géométriques). En revanche le volet *matériau* +> de `Mesh` a été fait en Étape 7 : `Mesh.material: Option>` (cf. PLAN Phase 2, gestion des +> matériaux), indépendant de la structure `Geometry`. + ### 1.3 Shader Phong Minimal - [x] Créer `standard_shader.wgsl` (Étape 2, 2026-09-16) : - [x] Vertex shader : projection * view * world * position