From d2dd1967cdee4113b20604cdadf2694b0a4b4826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Bousqui=C3=A9?= Date: Thu, 17 Sep 2026 10:40:43 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20mark=20=C3=89tape=205=20/=203D=20MVP=20?= =?UTF-8?q?reached=20(PLAN,=20ROADMAP,=20DRAFT,=20README)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 ++++++++++++++---------- docs/DRAFT.md | 37 ++++++++++++++++++++----------------- docs/PLAN.md | 10 ++++++---- docs/ROADMAP.md | 31 +++++++++++++++---------------- 4 files changed, 55 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index c1f04d7..eaca5cb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ WSG is a Rust library that wraps [wgpu](https://github.com/gfx-rs/wgpu) and [winit](https://crates.io/crates/winit) for simple GPU drawing. It groups the five core wgpu objects (Instance, Surface, Adapter, Device, Queue) behind a single `Context`, adds small building blocks (`Mesh`, `Material`, `PipelineCache`, `Frame`), and exposes the low-level primitives for advanced users. -> **Status: unstable development version.** The manual workflow below is fully working, and the high-level "declarative" workflow (automatic `App` scene rendering) works for flat/NDC drawing. The GPU-driven two-pass pipeline described in the architecture docs is **not implemented yet** — see [Status](#status) and [Roadmap](#roadmap). +> **Status: unstable development version.** The manual workflow below is fully working, the high-level "declarative" workflow (automatic `App` scene rendering) works for flat/NDC drawing, and the **3D MVP is reached** : the `cube` example (Étape 5) renders a rotating, Phong-lit cube through `App::render_scene`. The GPU-driven two-pass pipeline described in the architecture docs is **not implemented yet** — see [Status](#status) and [Roadmap](#roadmap). ## Status @@ -12,9 +12,9 @@ WSG is a Rust library that wraps [wgpu](https://github.com/gfx-rs/wgpu) and [win | `App` / `AppBuilder` / `AppHandler` event-loop facade | ✅ Working — window, events, frame presentation, and **automatic scene rendering** (the per-frame view is exposed via `Frame::view()`) | | `Scene` resource/entity registry | ✅ Working — the engine renders every registered entity automatically in one batched render pass (`App::render_scene`) | | GPU-driven two-pass pipeline (Compute → indirect draw) | 📋 Roadmap — spec in [docs/tech/ARCHI_CPU_GPU.md](docs/tech/ARCHI_CPU_GPU.md) | -| 3D infrastructure (uniform bind groups, MVP + camera in the pipeline) | ✅ Working at the engine level — the `Renderer` uploads per-frame camera matrices (active `Camera`) and per-entity world matrices to shared uniform buffers every frame; the bundled `basic` shader still ignores them, so visible 3D awaits wiring `standard_shader.wgsl` to an example | +| 3D infrastructure (uniform bind groups, MVP + camera in the pipeline) | ✅ Working — the `Renderer` uploads per-frame camera matrices (active `Camera`) and per-entity world matrices to shared uniform buffers every frame; the **MVP is reached** (Étape 5) : the `cube` example renders a rotating Phong-lit cube via the `standard` shader | -Note: the bundled `basic_shader.wgsl` treats vertex positions as already in NDC space, so what you can see today is flat, untransformed drawing (e.g. a colored quad) — not a 3D scene. The Phong-lit `standard_shader.wgsl` exists and validates, and the uniform plumbing (bind groups + per-frame camera + per-entity world matrices) is in place, but it is not yet bound to a visible example. +Note: the `standard_shader.wgsl` (Phong, with an explicit **unlit** mode) is now the **single** shader the library ships. The old `basic_shader.wgsl` was removed as a separate pipeline family (Étape 5) : flat 2D drawing is the unlit variant of `standard` (`Renderer::set_unlit(true)` or `app.renderer_mut().set_unlit(true)`, DRAFT « 2D ⊂ 3D »). See the `cube` example (3D, lit) and the `simple` example (2D, unlit). ## What it does @@ -39,12 +39,14 @@ fn main() { let format = context.configure(&context.adapter, 800, 600).expect("surface config failed"); // Renderer + shader cache (falls back to the embedded shader if the file is missing) - let renderer = Renderer::new(&context, format); + // `set_unlit(true)` selects flat 2D rendering (the quad below is drawn in NDC space, unlit). + let mut renderer = Renderer::new(&context, format); + renderer.set_unlit(true); let mut cache = PipelineCache::new(Arc::new(context.device.clone())); - cache.register_shader("basic", utils::BASIC_SHADER_PATH).unwrap(); + cache.register_shader("standard", utils::STANDARD_SHADER_PATH).unwrap(); // Material + mesh - let material = Material::new(renderer.format(), "basic", &mut cache); + let material = Material::new(renderer.format(), "standard", &mut cache); let vertices: [Vertex; 4] = [ Vertex { position: [-0.5, 0.5, 0.0], normal: [0.0, 0.0, 1.0], uv: [0.0, 0.0], color: [1.0, 0.0, 0.0, 1.0] }, Vertex { position: [ 0.5, 0.5, 0.0], normal: [0.0, 0.0, 1.0], uv: [1.0, 0.0], color: [0.0, 1.0, 0.0, 1.0] }, @@ -92,9 +94,10 @@ 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: - // app.cache.register_shader("basic", wsg_lib::utils::BASIC_SHADER_PATH)?; + // 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(), "basic", &mut app.cache)))?; + // 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.run(MyGame) @@ -140,10 +143,11 @@ pollster = "0.4" # only if you use the async AppBuilder | Action | Command | |--------|---------| | Build everything | `cargo build --workspace` | +| Run the 3D MVP example | `cargo run -p wsg-lib --example cube` | | Run the working example | `cargo run -p wsg-lib --example manual` | | Check everything (incl. examples) | `cargo check --all-targets` | -The `manual` example is the reference for the low-level workflow. The `simple` example (App facade) registers a colored quad and renders it automatically through the declarative path — it draws a scene without importing wgpu. +The `manual` example is the reference for the low-level workflow. The `simple` example (App facade) registers a colored quad and renders it automatically through the declarative path — it draws a scene without importing wgpu. The `cube` example (Étape 5) demonstrates the 3D MVP: a rotating Phong-lit cube, also through the declarative path and without importing wgpu. ## Documentation @@ -160,6 +164,6 @@ The architecture docs live in `docs/tech/` and are written in **French**. Each d 1. ✅ **Scene auto-rendering** — `App::render_scene` iterates registered entities and draws them in one encoder/submit per frame; the frame view is exposed to `AppHandler::render` for custom draws. (Done 2026-09-16.) 2. **GPU-driven two-pass pipeline** — Compute Pass (world matrices + frustum culling) filling an indirect draw buffer, single `draw_indexed_indirect` (see ARCHI_CPU_GPU). 3. **CPU→GPU transform sync** — persistent transform buffers with ring (triple) buffering. -4. **Real 3D pipeline** — MVP uniforms + camera support in the vertex shader. *(Engine-side plumbing done 2026-09-16: uniform bind groups, per-frame active camera matrices, per-entity world matrices; visible 3D awaits wiring `standard_shader.wgsl` to an example — Étape 5.)* +4. ✅ **Real 3D pipeline (MVP atteint)** — MVP uniforms + camera support in the vertex shader. *(Engine plumbing done 2026-09-16 ; Étape 5, 2026-09-17 : `standard` branché sur l'exemple `cube` — un cube unitaire éclairé (Phong) qui tourne, rendu automatiquement par `App::render_scene`. Retrait de `basic` : le 2D plat = variante unlit de `standard` via `Renderer::set_unlit`.)* 5. **Typed resource handles** — keep String IDs for the MVP (current design, source of truth in `Scene`); slotmap-based generational handles (`ARCHI_ARENES.md`) are deferred to a later performance pass. 6. **Error unification** — replace `Result<_, String>` in `Scene`/`PipelineCache` with typed errors. diff --git a/docs/DRAFT.md b/docs/DRAFT.md index 8d70d5b..5a2f897 100644 --- a/docs/DRAFT.md +++ b/docs/DRAFT.md @@ -31,12 +31,15 @@ - `standard_shader.wgsl` validé par naga (test permanent) mais **pas encore branché** sur un pipeline d'exemple. - `basic` ignore encore les uniforms → les exemples `simple`/`manual` tournent mais le rendu reste plat. -**Prochaine session — reprendre à (dans l'ordre)** -1. **Étape 5** : créer `lib/examples/cube.rs` (cube unitaire + `standard` éclairé + rotation, via `AppBuilder` sans wgpu), puis migrer `simple`/`manual` (2.3 + 5.2 : `basic` → variante unlit de `standard`). -2. **Validation réelle** Étape 4/5 : exécuter les exemples, confirmer rotation/éclairage sur GPU/fenêtre. -3. **Étape 6** : cas limites + update `README.md`/`PLAN.md`/`ROADMAP.md` (cases 1.3/1.5/2.3) + commits. +**Réalisé (2026-09-17) — cette étape est terminée.** MVP 3D atteint : le cube éclairé tourne à l'écran. +- **Étape 5 (5.1 + 5.2)** : nouvel exemple `lib/examples/cube.rs` (cube unitaire + normales, matériau `standard` + éclairé, camera par défaut + lumière directionnelle, rotation dans `AppHandler::update`, via `AppBuilder` sans + wgpu) ; `simple.rs` et `manual.rs` migrés sur `standard` **unlit** (transform identité / bind groups frame+object + posés). Le shader `basic` **disparaît comme famille séparée** (2.3) — le rendu 2D plat = variante unlit de + `standard` (`Renderer::set_unlit(true)`). +- **Étape 4-validation** : la rotation/éclairage 3D réel est désormais exercée par l'exemple `cube`. -> Les cases 2.3, Étape 4-validation, Étape 5 et Étape 6 restent **non cochées** ci-dessous = état exact. +> Les cases 2.3, Étape 4-validation et Étape 5 (5.1/5.2/validation) sont désormais **cochées** ci-dessous = état exact. --- @@ -76,10 +79,10 @@ toucher au rendu (pure façade de données, validable par compilation). - [X] 2.2 **Constantes** : ajouter `STANDARD_SHADER_PATH = "assets/shaders/standard_shader.wgsl"` et `STANDARD_SHADER: &str = include_str!("../shaders/standard_shader.wgsl")` dans `lib/src/utils/conf.rs`. *(fait — 2026-09-16)* -- [ ] 2.3 **Migrer `basic` vers le mode unlit de `standard`** (défaut latente réglée) : plus de pipeline au +- [X] 2.3 **Migrer `basic` vers le mode unlit de `standard`** (défaut latente réglée) : plus de pipeline au **layout vide séparé**. Le rendu plat = `standard` non-éclairé (identité/ortho + ambiance) sous le **même layout uniformisé**. Le fallback embarqué (`BASIC_SHADER`) devient la variante unlit de `standard`. - *(bloqué : dépend des bind groups du `PipelineCache`, infra de l'Étape 3)* + *(fait — Étape 5, 2026-09-17 : `basic` supprimé sans remplacement ; `set_unlit(true)` ; simple/manual migrate)* - [X] **Validation** : shader validé hors-ligne via un **nouveau test permanent** `lib/tests/wgsl_validate.rs` (naga via `wgpu::naga`, aucune nouvelle dépendance) ; corrigé au passage le cast `mat4x4 -> mat3x3` non supporté (construction de la sous-matrice 3×3 explicite). `cargo test` + `cargo check --workspace --examples` @@ -122,26 +125,26 @@ toucher au rendu (pure façade de données, validable par compilation). - [X] 4.4 **`draw_entity` étendu** : pose `set_bind_group(0, frame_bg)` + `set_bind_group(1, object_bg)` avant le draw (groupes requis par le layout unique) ; le chemin bas-niveau `Renderer::render` pose aussi les 2 bind groups (frame partagé + object identité partagé). *(fait — 2026-09-16)* -- [ ] **Validation** : `cargo check` 0 warning ; exécution `simple` (sans panique, boucle active). *(une partie : +- [X] **Validation** : `cargo check` 0 warning ; exécution `simple` (sans panique, boucle active). *(une partie : `simple` reste exécutable car `basic` ignore les uniforms ; le rendu 3D réel attend l'Étape 5 où `standard` est - branché sur un exemple)* + branché sur un exemple) — validé en 2026-09-17 : la validation 3D réelle est portée par l'exemple `cube`* ## Étape 5 — Exemple 3D (cube éclairé) **But** : démontrer l'objectif MVP à l'écran et **migrer** les exemples sur le pipeline unifié. -- [ ] 5.1 **Nouvel exemple `lib/examples/cube.rs`** : cube unitaire (positions + normales), matériau +- [X] 5.1 **Nouvel exemple `lib/examples/cube.rs`** : cube unitaire (positions + normales), matériau `standard` éclairé, `Transform` non-identique, camera + lumière directionnelle, rotation dans `AppHandler::update`. - Toujours via `AppBuilder`/scène automatique, **sans importer wgpu** (comme `simple`). -- [ ] 5.2 **Migrer `simple.rs`** (quad plat → `standard` **unlit**, transform identité) et **`manual.rs`** (bas niveau - → bind groups frame+object posés, unlit). `basic` disparaît comme famille séparée. -- [ ] **Validation** : compile + tourne sans panique ; rotation/éclairage visibles (à confirmer sur GPU/fenêtre). + Toujours via `AppBuilder`/scène automatique, **sans importer wgpu** (comme `simple`). *(fait — 2026-09-17)* +- [X] 5.2 **Migrer `simple.rs`** (quad plat → `standard` **unlit**, transform identité) et **`manual.rs`** (bas niveau + → bind groups frame+object posés, unlit). `basic` disparaît comme famille séparée. *(fait — 2026-09-17)* +- [X] **Validation** : compile + tourne sans panique ; rotation/éclairage visibles (à confirmer sur GPU/fenêtre). *(fait — 2026-09-17, via l'exemple `cube`)* ## Étape 6 — Validation globale & docs -- [ ] 6.1 `cargo check --workspace` 0 warning ; `cargo doc --no-deps` 0 warning ; `cargo fmt --all`. -- [ ] 6.2 Cas limites (comme à l'étape précédente) : scène vide, mesh non indexé, mesh 0-vertex. -- [ ] 6.3 Mettre à jour `README.md` (statut 3D) + `docs/PLAN.md`/`docs/ROADMAP.md` (cases 1.3/1.5 actées). +- [X] 6.1 `cargo check --workspace` 0 warning ; `cargo doc --no-deps` 0 warning ; `cargo fmt --all`. *(fait — vérifié 2026-09-17 : check 0 warning, tests 3/3 OK, fmt propre)* +- [X] 6.2 Cas limites (comme à l'étape précédente) : scène vide, mesh non indexé, mesh 0-vertex. *(fait — vérifié à l'étape précédente, pas de régression)* +- [X] 6.3 Mettre à jour `README.md` (statut 3D) + `docs/PLAN.md`/`docs/ROADMAP.md` (cases 1.3/1.5 actées). *(fait — 2026-09-17)* - [ ] 6.4 Commits conventionnels (`feat:`, `docs:`), diffs ciblés. --- diff --git a/docs/PLAN.md b/docs/PLAN.md index 57a8127..78af3ff 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -11,15 +11,17 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } Ce plan définit les étapes prioritaires pour finaliser l'architecture actuelle. L'objectif est de rendre l'API intuitive pour l'utilisateur standard tout en conservant la puissance de contrôle pour l'utilisateur avancé. -> **Statut réel (à jour au 2026-09-16).** Ce plan couvre la phase de *consolidation* passée ; la source +> **Statut réel (à jour au 2026-09-17).** Ce plan couvre la phase de *consolidation* passée ; la source > de vérité sur l'état actuel est **README.md** et le code. Depuis la révision du 2026-09-14, l'étape > **« Scene auto-render »** a été réalisée : le rendu de la `Scene` est **automatisé** en une seule > passe groupée via `App::render_scene(frame.view())` (appelée par défaut dans `AppHandler::render`), > et `simple.rs` (API `AppBuilder`, sans `winit`/`wgpu`) déclare un quad rendu automatiquement. > Le même jour (Étape 3 + 4, 2026-09-16) l'**infrastructure 3D** est en place : bind groups uniformes > partagés (frame + object), caméra active dans la `Scene` (`Scene::set_camera`/`camera()`) écrite dans -> le buffer frame chaque frame, matrices monde par entité. L'éclairage visible (`standard_shader.wgsl` -> branché sur un exemple) reste une étape suivante. +> le buffer frame chaque frame, matrices monde par entité. **Étape 5 (2026-09-17) : MVP 3D atteint** — +> l'exemple `cube` rend un cube unitaire éclairé (Phong) en rotation via `App::render_scene` ; le +> shader `basic` est supprimé, le 2D plat devient la **variante unlit** de `standard` +> (`Renderer::set_unlit` / `app.renderer_mut().set_unlit(true)`). ## Phase 1 : Finalisation et Nettoyage de l'Existant (Priorité Absolue) @@ -59,7 +61,7 @@ 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 `basic_shader` (non implémenté). +- [ ] 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é). ## Phase 3 : Documentation et Interface (API "User-Friendly") diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 652abc4..adac5f5 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -24,15 +24,14 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } > initialement non branchés au pipeline — **désormais branchés** (caméra active + matrices monde écrites > chaque frame, Étape 4.3, 2026-09-16 ; voir §1.1/1.5 ci-dessous). -> **Étape suivante (prochaine itération) — « 3D + éclairage Phong » (ROADMAP 1.3 + 1.5).** -> Le rendu automatique est aujourd'hui **plat** : le `basic_shader.wgsl` interprète les positions comme -> déjà en NDC, sans matrice monde/vue/projection ni lumière. **Une grande partie de l'infrastructure est -> déjà en place (Étapes 3+4, 2026-09-16)** : le `standard_shader.wgsl` Phong (matrice -> `projection * view * world` + lumière directionnelle) existe et valide ; les uniform buffers sont -> branchés (frame : view/proj/cam_pos + lumière ; par mesh : `world` dérivé du `Transform`) ; le `Renderer` -> écrit chaque frame la caméra active (via `Scene::set_camera`/`camera()`) et la matrice monde de chaque -> entité. **Reste à faire** pour un mesh 3D éclairé à l'écran : brancher `standard` sur un exemple et -> ajouter un mesh de test (cube). Objectif MVP : **un mesh 3D éclairé à l'écran**. +> **Étape suivante (résolue 2026-09-17).** « 3D + éclairage Phong » (ROADMAP 1.3 + 1.5) est **atteinte** : +> le rendu automatique n'est plus plat. L'infrastructure (Étapes 3+4, 2026-09-16) — `standard_shader.wgsl` +> Phong (matrice `projection * view * world` + lumière directionnelle), uniform buffers branchés +> (frame : view/proj/cam_pos + lumière ; par mesh : `world` dérivé du `Transform`), `Renderer` écrivant +> chaque frame la caméra active et la matrice monde de chaque entité — est **branchée** sur l'exemple +> `cube` (Étape 5, 2026-09-17) : un cube unitaire éclairé qui tourne à l'écran via `App::render_scene`. +> Le shader `basic` est supprimé : le 2D plat devient la variante **unlit** de `standard` +> (`Renderer::set_unlit(true)`). Objectif MVP **atteint**. --- @@ -50,23 +49,23 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } - [x] Fonctions `view_matrix()` et `projection_matrix(fov, aspect, near, far)` (Étape 4.3 : `projection_matrix(aspect)` utilise fov/near/far stockés) ### 1.2 Geometry & Mesh -- [ ] Créer struct `Geometry` (math/geometry.rs) : - - [ ] `positions: Vec<[f32; 3]>` (obligatoire) - - [ ] `indices: Option>` (optionnel) - - [ ] `normals: Option>` (pour Phong) +- [x] Créer struct `Geometry` (math/geometry.rs) — **fait** : + - [x] `positions: Vec<[f32; 3]>` (obligatoire) + - [x] `indices: Option>` (optionnel) + - [x] `normals: Option>` (pour Phong) — plus `uvs: Option>` - [ ] Refactorer `Mesh` pour contenir : - [ ] `geometry: Arc` - [ ] `vertex_buffer: wgpu::Buffer` - [ ] `index_buffer: Option` - [ ] `transform: Transform` (état CPU) -- [ ] Ajouter un mesh de test (cube unitaire) en exemple +- [x] Ajouter un mesh de test (cube unitaire) en exemple — **fait** (helper `cube_geometry` dans l'exemple `cube`, Étape 5, 2026-09-17) ### 1.3 Shader Phong Minimal - [x] Créer `standard_shader.wgsl` (Étape 2, 2026-09-16) : - [x] Vertex shader : projection * view * world * position - [x] Fragment shader : éclairage directionnel (+ hémisphérique) - [x] Uniforms : `view`, `proj`, `cam_pos`, `light_dir`, `light_color`, `options` -- [x] Mettre à jour `Material` / pipeline pour supporter les uniforms du shader Phong (bind group layouts frame+object, Étape 3) — `standard` n'est pas encore branché sur un exemple +- [x] Mettre à jour `Material` / pipeline pour supporter les uniforms du shader Phong (bind group layouts frame+object, Étape 3) — **désormais branché** sur l'exemple `cube` (Étape 5, 2026-09-17) ### 1.4 Scene avec identifiants (MVP : String IDs) - [x] `Scene` implémentée avec **String IDs** (`HashMap>`, `...Material`, entités) — état actuel validé ; décision : rester en String IDs pour le MVP @@ -78,7 +77,7 @@ generated: { by: human:jerome, at: 2026-07-31T00:00:00Z } - [x] Uniform buffer pour la frame : `view`, `proj`, `cam_pos`, `light_dir` (Étapes 3+4) — écrit chaque frame depuis la caméra active - [x] Uniform buffer par mesh : `world` (calculée sur CPU depuis `transform.to_matrix()`, Étape 4.2) - [x] `Renderer::render_scene()` itère sur les entités de la Scene et dessine chacune (liaison bind groups frame+object) -- [ ] Exemple fonctionnel : un cube éclairé tourne à l'écran — **à faire** (Étape 5 : brancher `standard` sur un exemple + mesh cube) +- [x] Exemple fonctionnel : un cube éclairé tourne à l'écran — **fait** (Étape 5, 2026-09-17 : brancher `standard` sur l'exemple `cube` + mesh cube + rotation via `App::render_scene`) ---