Compare commits

...

2 Commits

Author SHA1 Message Date
Jérôme Bousquié d2dd1967cd docs: mark Étape 5 / 3D MVP reached (PLAN, ROADMAP, DRAFT, README) 2026-09-17 10:40:43 +02:00
Jérôme Bousquié 0a85aff17b feat(examples): 3D MVP cube via standard shader, drop basic (Étape 5) 2026-09-17 10:40:43 +02:00
19 changed files with 273 additions and 158 deletions
+14 -10
View File
@@ -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.
+20 -17
View File
@@ -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.
---
+6 -4
View File
@@ -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")
+15 -16
View File
@@ -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<Vec<u16>>` (optionnel)
- [ ] `normals: Option<Vec<[f32; 3]>>` (pour Phong)
- [x] Créer struct `Geometry` (math/geometry.rs) — **fait** :
- [x] `positions: Vec<[f32; 3]>` (obligatoire)
- [x] `indices: Option<Vec<u16>>` (optionnel)
- [x] `normals: Option<Vec<[f32; 3]>>` (pour Phong) — plus `uvs: Option<Vec<[f32; 2]>>`
- [ ] Refactorer `Mesh` pour contenir :
- [ ] `geometry: Arc<Geometry>`
- [ ] `vertex_buffer: wgpu::Buffer`
- [ ] `index_buffer: Option<wgpu::Buffer>`
- [ ] `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<String, Arc<Mesh>>`, `...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`)
---
+122
View File
@@ -0,0 +1,122 @@
//! Étape 5 — MVP 3D : un cube unitaire éclairé qui tourne, rendu automatiquement par la boucle `App`.
//!
//! Démonstration de l'objectif MVP du ROADMAP 1.3 + 1.5 : un mesh 3D avec éclairage Phong à l'écran.
//! On suit le workflow déclaratif (comme `simple`) : `AppBuilder` + scène automatique, **sans importer
//! wgpu**. La seule nouveauté déclarative est l'enregistrement du shader `standard` (Phong) au lieu de
//! `basic`. La caméra active par défaut (`Scene::default`, position (0,0,3), fov 45°) cadre le cube, et
//! `AppHandler::update` fait tourner l'entité via `set_entity_transform` chaque frame.
use glam::Quat;
use std::sync::Arc;
use wsg_lib::AppHandler;
use wsg_lib::app::AppBuilder;
use wsg_lib::resources::{Material, Mesh, Vertex};
use wsg_lib::utils::WsgError;
/// Handler de démonstration : fait tourner le cube dans `update`.
struct Cube {
/// Angle de rotation cumulé (radians), incrémenté à chaque frame.
angle: f32,
}
/// Génère les sommets d'un cube unitaire centré à l'origine (arête de 1), une normale par face.
/// 24 sommets (4 par face) + 36 indices ; la couleur est blanche, l'UV est laissé à zéro (inutilisé
/// par `standard` pour un matériau sans texture).
fn cube_vertices() -> Vec<Vertex> {
let s = 0.5; // demi-arête
let color = [1.0, 1.0, 1.0, 1.0];
// Chaque face : (normale sortante, 4 coins). Le culling est désactivé par défaut (PrimitiveState
// par défaut), donc l'ordre d'enroulement n'affecte pas la visibilité ; seules les normales comptent
// pour l'éclairage.
let faces: [([f32; 3], [[f32; 3]; 4]); 6] = [
(
[0.0, 0.0, 1.0],
[[-s, -s, s], [s, -s, s], [s, s, s], [-s, s, s]],
), // +Z
(
[0.0, 0.0, -1.0],
[[s, -s, -s], [-s, -s, -s], [-s, s, -s], [s, s, -s]],
), // -Z
(
[1.0, 0.0, 0.0],
[[s, -s, -s], [s, s, -s], [s, s, s], [s, -s, s]],
), // +X
(
[-1.0, 0.0, 0.0],
[[-s, -s, s], [-s, s, s], [-s, s, -s], [-s, -s, -s]],
), // -X
(
[0.0, 1.0, 0.0],
[[-s, s, -s], [s, s, -s], [s, s, s], [-s, s, s]],
), // +Y
(
[0.0, -1.0, 0.0],
[[-s, -s, s], [s, -s, s], [s, -s, -s], [-s, -s, -s]],
), // -Y
];
let mut verts = Vec::with_capacity(24);
for (normal, corners) in faces {
for corner in corners {
verts.push(Vertex {
position: corner,
normal,
uv: [0.0, 0.0],
color,
});
}
}
verts
}
/// Génère les indices d'un cube à partir de ses 24 sommets (2 triangles par face, 36 indices).
fn cube_indices() -> Vec<u16> {
let mut indices = Vec::with_capacity(36);
for face in 0..6u16 {
let b = face * 4;
indices.extend_from_slice(&[b, b + 1, b + 2, b, b + 2, b + 3]);
}
indices
}
impl AppHandler for Cube {
fn setup(&mut self, app: &mut wsg_lib::App) {
let format = app.renderer().format();
// Shader Phong `standard` (porteur des bind groups frame + object) au lieu de `basic`.
app.cache()
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
.unwrap();
let mesh = Arc::new(Mesh::new(
app.renderer().device(),
&cube_vertices(),
Some(&cube_indices()),
));
let material = Arc::new(Material::new(format, "standard", app.cache()));
app.scene.add_mesh("cube_mesh", mesh).unwrap();
app.scene.add_material("cube_material", material).unwrap();
app.scene
.add_entity("cube", "cube_mesh", "cube_material")
.unwrap();
}
fn update(&mut self, app: &mut wsg_lib::App) {
// Rotation cumulée du cube (double axe pour un mouvement plus lisible).
self.angle += 0.02;
let base = *app
.scene
.entity_transform("cube")
.expect("cube entity present");
let mut transform = base;
transform.rotation =
Quat::from_rotation_y(self.angle) * Quat::from_rotation_x(self.angle * 0.3);
app.scene.set_entity_transform("cube", transform);
}
}
#[pollster::main]
async fn main() -> Result<(), WsgError> {
let app = AppBuilder::new().title("WSG Cube").build().await?;
app.run(Cube { angle: 0.0 })
}
+9 -6
View File
@@ -1,7 +1,7 @@
//! Workflow bas-niveau : utilisation directe du `Context`, `Renderer`, `PipelineCache`, `Mesh` et
//! `Material`, contournant la façade `App`. Rendu d'un quad plat éclairé via la boucle winit 0.30
//! (`EventLoop::run_app` + `ApplicationHandler`). La fenêtre et le GPU sont créés dans `resumed()`,
//! comme l'exigent winit 0.30 et la migration faite dans `app.rs`.
//! `Material`, contournant la façade `App`. Rendu d'un quad plat (shader `standard` **unlit**) via la
//! boucle winit 0.30 (`EventLoop::run_app` + `ApplicationHandler`). La fenêtre et le GPU sont créés
//! dans `resumed()`, comme l'exigent winit 0.30 et la migration faite dans `app.rs`.
use std::sync::Arc;
use winit::application::ApplicationHandler;
use winit::dpi::LogicalSize;
@@ -59,13 +59,16 @@ impl ApplicationHandler for App {
let device = Arc::new(context.device.clone());
let mut cache = PipelineCache::new(device);
cache
.register_shader("basic", utils::BASIC_SHADER_PATH)
.register_shader("standard", utils::STANDARD_SHADER_PATH)
.unwrap();
let renderer = Renderer::new(&context, format);
// Rendu 2D plat : `standard` en mode unlit (les bind groups frame+object sont posés par
// draw_entity, la matrice frame par défaut est l'identité → positions NDC inchangées).
let mut renderer = Renderer::new(&context, format);
renderer.set_unlit(true);
// 3. Material : On utilise renderer.device() et renderer.format()
let material = Material::new(renderer.format(), "basic", &mut cache);
let material = Material::new(renderer.format(), "standard", &mut cache);
// Mesh : On utilise le device du renderer
let vertices = [
+9 -5
View File
@@ -16,9 +16,11 @@ impl AppHandler for MonQuad {
fn setup(&mut self, app: &mut wsg_lib::App) {
let format = app.renderer().format();
// Enregistrement du shader, création du matériau et du mesh du quad (sans importer wgpu).
// Exemple 2D plat : le shader `standard` en mode **unlit** (options.x = 1) renvoie la couleur
// du vertex telle quelle. Ainsi le 2D est un cas particulier du 3D — un seul pipeline pour tous.
app.renderer_mut().set_unlit(true);
app.cache()
.register_shader("basic", wsg_lib::utils::BASIC_SHADER_PATH)
.register_shader("standard", wsg_lib::utils::STANDARD_SHADER_PATH)
.unwrap();
let vertices = [
Vertex {
@@ -53,12 +55,14 @@ impl AppHandler for MonQuad {
&vertices,
Some(&indices),
));
let material = Arc::new(Material::new(format, "basic", app.cache()));
let material = Arc::new(Material::new(format, "standard", app.cache()));
app.scene.add_mesh("quad_mesh", mesh).unwrap();
app.scene.add_material("basic_material", material).unwrap();
app.scene
.add_entity("quad", "quad_mesh", "basic_material")
.add_material("standard_material", material)
.unwrap();
app.scene
.add_entity("quad", "quad_mesh", "standard_material")
.unwrap();
}
}
+10
View File
@@ -72,6 +72,16 @@ impl App {
.expect("renderer not initialized yet — call app.run(handler) first")
}
/// Returns a mutable reference to the GPU renderer.
/// Panics if called before `App::run` has created the renderer (i.e. before `resumed` fires).
/// Callers can configure the renderer here, e.g. `app.renderer_mut().set_unlit(true)` in `setup`
/// to select flat 2D rendering (DRAFT Étape 5).
pub fn renderer_mut(&mut self) -> &mut Renderer {
self.renderer
.as_mut()
.expect("renderer not initialized yet — call app.run(handler) first")
}
/// Returns a reference to the GPU hardware context.
/// Panics if called before `App::run` has created the context (i.e. before `resumed` fires).
pub fn context(&self) -> &Context {
+35 -5
View File
@@ -55,6 +55,10 @@ pub struct Renderer {
/// entity label. Needed because `render_scene(&self, &Scene)` is immutable; the model matrix is
/// rewritten each frame for every entity.
object_cache: RefCell<HashMap<String, (wgpu::Buffer, wgpu::BindGroup)>>,
/// Flat (unlit) rendering flag, exposed via [`Renderer::set_unlit`]. When true, `options.x` of the
/// `FrameUniforms` is set to 1 so the `standard` shader returns vertex colors as-is — flat 2D
/// rendering is thus a special case of the 3D lit path (DRAFT Étape 5). Defaults to `false` (lit).
unlit: bool,
}
impl Renderer {
@@ -72,14 +76,12 @@ impl Renderer {
// Shared frame uniforms: identity camera + white directional light, lit mode by default.
// Values become meaningful once an active camera is wired (Étape 4.3); for now the default
// is a coherent scene when a shader actually reads them, and irrelevant to shaders that don't.
let default_frame = FrameUniforms::default();
let frame_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("frame uniform buffer"),
size: FRAME_UNIFORMS_SIZE,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
queue.write_buffer(&frame_buffer, 0, bytemuck::bytes_of(&default_frame));
let frame_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("frame bind group"),
layout: &frame_layout,
@@ -109,7 +111,7 @@ impl Renderer {
}],
});
Self {
let renderer = Self {
queue,
device,
format,
@@ -118,7 +120,35 @@ impl Renderer {
frame_bind_group,
shared_object_bind_group,
object_cache: RefCell::new(HashMap::new()),
}
unlit: false,
};
// Seed the shared frame buffer with an identity camera + current unlit flag so the low-level
// `render` path (which has no window/camera) sees coherent values before `render_scene` runs.
renderer.write_default_frame_uniforms();
renderer
}
/// Writes the shared per-frame uniform buffer using an identity camera (view = proj = identity)
/// and the current [`Renderer::set_unlit`] flag. This is the initial state for the low-level
/// `render` path, which is independent of any window and therefore has no camera or aspect ratio.
/// Called at construction and whenever the renderer transitions between lit and unlit mode.
fn write_default_frame_uniforms(&self) {
let frame = FrameUniforms {
options: [if self.unlit { 1 } else { 0 }, 0, 0, 0],
..FrameUniforms::default()
};
self.queue
.write_buffer(&self.frame_buffer, 0, bytemuck::bytes_of(&frame));
}
/// Toggles flat (unlit) rendering. When true, the `standard` shader returns vertex colors as-is
/// (`options.x = 1`), so flat 2D rendering is a special case of the 3D lit path (DRAFT Étape 5 :
/// « 2D ⊂ 3D »). Rewrites the shared frame buffer immediately so the low-level `render` path picks
/// up the change ; the `render_scene` path reads the flag each frame in `write_frame_uniforms`.
/// Inputs: unlit — true for flat rendering, false (default) for Phong-lit rendering.
pub fn set_unlit(&mut self, unlit: bool) {
self.unlit = unlit;
self.write_default_frame_uniforms();
}
/// Rewrites the shared per-frame uniform buffer from the scene's active camera and the current
@@ -135,7 +165,7 @@ impl Renderer {
cam_pos: camera.position.extend(1.0),
light_dir: Vec4::new(0.0, 0.0, 1.0, 0.0),
light_color: Vec4::ONE,
options: [0, 0, 0, 0],
options: [if self.unlit { 1 } else { 0 }, 0, 0, 0],
};
self.queue
.write_buffer(&self.frame_buffer, 0, bytemuck::bytes_of(&frame));
+1 -1
View File
@@ -22,7 +22,7 @@
//! ```ignore
//! use wsg_lib::core::{Context, Renderer};
//! use wsg_lib::resources::{Mesh, Material, Vertex};
//! use wsg_lib::utils::BASIC_SHADER;
//! use wsg_lib::utils::STANDARD_SHADER;
//! ```
// Warn if a public API item has no rustdoc comment, keeping API coverage at 100%.
+2 -2
View File
@@ -6,11 +6,11 @@ The `pipeline` module contains the shader compilation cache that avoids duplicat
| File | Responsibility |
|------|---------------|
| **pipeline_cache** | PipelineCache — maps (shader_id, format) keys to compiled RenderPipelines. Loads WGSL from disk or falls back to embedded BASIC_SHADER constant. Creates pipelines on-demand via build_pipeline(). |
| **pipeline_cache** | PipelineCache — maps (shader_id, format) keys to compiled RenderPipelines. Loads WGSL from disk or falls back to embedded STANDARD_SHADER constant. Creates pipelines on-demand via build_pipeline(). |
## Interaction with Other Modules
- **utils::conf**: Provides BASIC_SHADER_PATH (disk path) and BASIC_SHADER (embedded fallback).
- **utils::conf**: Provides STANDARD_SHADER_PATH (disk path) and STANDARD_SHADER (embedded fallback).
- **resources::vertex**: Vertex struct field offsets define the CPU-side layout that build_pipeline() uses as the vertex buffer contract.
- **resources::material**: Material::new() calls get_or_create() during construction to obtain a shared RenderPipeline.
+1 -1
View File
@@ -6,7 +6,7 @@
//!
//! ## Interaction with Other Modules
//! - `material` calls `get_or_create()` during its own construction to obtain a shared RenderPipeline.
//! - `conf::BASIC_SHADER` provides fallback WGSL source when an external file is not found.
//! - `conf::STANDARD_SHADER` provides fallback WGSL source when an external file is not found.
//! - `vertex::Vertex` defines the CPU-side layout that `build_pipeline()` uses as the vertex buffer contract.
pub mod pipeline_cache;
+5 -5
View File
@@ -7,7 +7,7 @@
//!
//! ## Interaction with Other Modules
//! - **Material** calls `get_or_create()` during its own construction to obtain a shared RenderPipeline.
//! - **conf::BASIC_SHADER** provides fallback WGSL source when an external file is not found.
//! - **conf::STANDARD_SHADER** provides fallback WGSL source when an external file is not found.
//! - **vertex::Vertex** defines the CPU-side layout that `build_pipeline` uses as the vertex buffer contract.
//!
//! ## Technical Points
@@ -16,7 +16,7 @@
//! - **Batching**: Multiple Materials with the same shader_id share one pipeline, enabling material-level batching in Renderer.
use crate::resources::Vertex;
use crate::utils::BASIC_SHADER;
use crate::utils::STANDARD_SHADER;
use std::collections::HashMap;
use std::sync::Arc;
@@ -79,7 +79,7 @@ impl PipelineCache {
device,
pipelines: HashMap::new(),
// Maps shader IDs to file paths on disk for WGSL loading in load_shader().
// When a path exists, it reads from it; otherwise falls back to BASIC_SHADER constant.
// When a path exists, it reads from it; otherwise falls back to STANDARD_SHADER constant.
shader_paths: HashMap::new(),
}
}
@@ -139,13 +139,13 @@ impl PipelineCache {
pipeline_arc
}
/// Loads a WGSL shader module: reads from disk first, falls back to the embedded BASIC_SHADER constant.
/// Loads a WGSL shader module: reads from disk first, falls back to the embedded STANDARD_SHADER constant.
/// Inputs: device (GPU command source for shader compilation), path (file path or shader_id string).
/// Returns a compiled wgpu::ShaderModule. Called internally by `get_or_create()` when compiling a new pipeline.
fn load_shader(&self, device: &wgpu::Device, path: &str) -> wgpu::ShaderModule {
let source = std::fs::read_to_string(path).unwrap_or_else(|_| {
println!("Shader not found: {}, falling back to default", path);
BASIC_SHADER.to_string()
STANDARD_SHADER.to_string()
});
device.create_shader_module(wgpu::ShaderModuleDescriptor {
+1 -1
View File
@@ -14,4 +14,4 @@ The `resources` module defines three immutable data types that flow through the
- **pipeline**: build_pipeline() reads Vertex field offsets to construct VertexBufferLayout attributes array.
- **scene**: Scene stores Arc<Mesh> and Arc<Material> instances keyed by identifier strings.
- **utils**: Mesh creation uses BASIC_SHADER fallback when external shader files are missing.
- **utils**: PipelineCache uses the embedded STANDARD_SHADER fallback when external shader files are missing.
+12 -25
View File
@@ -2,39 +2,24 @@
## Overview
Contains WGSL shader source files used by the PipelineCache module. These are loaded at runtime from disk when referenced by their registered ID in PipelineCache.register_shader(). If a file is missing, PipelineCache falls back to the embedded BASIC_SHADER constant defined in utils::conf.
Contains WGSL shader source files used by the PipelineCache module. These are loaded at runtime from
disk when referenced by their registered ID in PipelineCache.register_shader(). If a file is missing,
PipelineCache falls back to the embedded STANDARD_SHADER constant defined in utils::conf.
Depuis l'Étape 5, il n'existe plus qu'**un seul shader** : `standard_shader.wgsl` (Phong). L'ancien
`basic_shader.wgsl` a été supprimé comme pipeline séparé — le rendu 2D plat est désormais la **variante
unlit** de `standard` (décision actée dans le DRAFT : « 2D ⊂ 3D »).
## Files
| File | Purpose |
|------|---------|
| **basic_shader.wgsl** | Legacy flat/unlit vertex/fragment shader pair (vs_main / fs_main) with position, uv, and color attributes. Scheduled to be replaced by the unlit variant of `standard_shader.wgsl` (DRAFT Étape 2.3 / 5). |
| **standard_shader.wgsl** | Standard (Phong) vertex/fragment shader — ambient + directional diffuse with an explicit unlit mode. Carries the full uniform contract (frame @group(0) + object @group(1)). |
## Shader Contract (basic_shader.wgsl)
The WGSL shader defines:
- `@vertex fn vs_main(model: VertexInput) -> VertexOutput` — vertex entry point
- `@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32>` — fragment entry point writing RGBA output
### Vertex Input Layout
| Location | Attribute | Type | Offset (bytes) |
|----------|-----------|------|----------------|
| 0 | position | vec3<f32> | 0 |
| 1 | uv | vec2<f32> | 12 |
| 2 | color | vec3<f32> | 24 |
**Note**: This shader uses a 39-byte vertex stride (3+2+3 floats). It does NOT include normal data or alpha channel interpolation — it outputs fully opaque geometry with per-vertex color passthrough. This differs from the full `Vertex` struct layout (56 bytes with normal + alpha) defined in resources::Vertex; if a full shader matching the Vertex struct is needed, extend this shader accordingly.
> **Statut** : ce shader n'est plus un pipeline séparé ; il est destiné à disparaître au profit de la variante
> unlit de `standard_shader.wgsl` (DRAFT Étape 2.3 / Étape 5, « un seul layout pour tous »).
## Shader Contract (standard_shader.wgsl)
`standard_shader.wgsl` est le shader unifié (Phong) de WSG. Il corrige le défaut latente de `basic`
(contrat vertex incomplet) et expose les deux bind groups partagés par tout matériau.
`standard_shader.wgsl` est le shader unifié (Phong) de WSG. Il expose les deux bind groups partagés
par tout matériau (Étape 3 : un seul layout pour tous).
### Vertex Input Layout (56-byte stride — correspond au `resources::Vertex`)
@@ -57,4 +42,6 @@ The WGSL shader defines:
### Mode unlit
Un flag `options.x != 0` neutralise la directionnelle et renvoie la couleur du vertex telle quelle
(couleur plate). Ainsi le rendu 2D plat est un **cas particulier** de la 3D éclairée.
(couleur plate). Côté API, `Renderer::set_unlit(true)` (ou `app.renderer_mut().set_unlit(true)`)
positionne ce flag dans les frame uniforms. Ainsi le rendu 2D plat est un **cas particulier** de la 3D
éclairée.
-42
View File
@@ -1,42 +0,0 @@
//! # Basic Shader Module
//!
//! Default vertex/fragment shader pair used by PipelineCache when no external .wgsl file is found.
//! This shader implements a simple unlit rendering path: passes through position and color attributes
//! from VertexInput to fragment output, producing flat-colored geometry without lighting calculations.
//!
//! ## Shader Contract
//! Must define entry points matching PipelineCache::build_pipeline():
//! - @vertex fn vs_main(model: VertexInput) -> VertexOutput
//! - model.position → @location(0), vec3<f32>, offset 0 bytes in vertex buffer
//! - model.uv → @location(1), vec2<f32>, offset 12 bytes in vertex buffer
//! - model.color → @location(2), vec3<f32>, offset 24 bytes in vertex buffer
//! - @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32>
//! - Writes RGBA output where alpha is hardcoded to 1.0 (fully opaque).
//!
//! ## Technical Notes
//! - No normal or UV interpolation — this is an unlit shader that directly outputs the per-vertex color.
//! - The clip_position is computed as vec4<f32>(position, 1.0), assuming position is already in NDC space.
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) uv: vec2<f32>,
@location(2) color: vec3<f32>,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec3<f32>,
};
@vertex
fn vs_main(model: VertexInput) -> VertexOutput {
var out: VertexOutput;
out.clip_position = vec4<f32>(model.position, 1.0);
out.color = model.color; // On transmet la couleur au fragment shader
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(in.color, 1.0);
}
+2 -2
View File
@@ -6,11 +6,11 @@ The `utils` module defines two leaf concepts that other modules consume but have
| File | Responsibility |
|------|---------------|
| **conf** | Shared constants for shader paths (BASIC_SHADER_PATH) and embedded WGSL source code (BASIC_SHADER). Centralized here so all submodules import from one place instead of duplicating literal strings. Enables PipelineCache to fall back to an embedded default shader when the file-based one is missing. |
| **conf** | Shared constants for shader paths (STANDARD_SHADER_PATH) and embedded WGSL source code (STANDARD_SHADER). Centralized here so all submodules import from one place instead of duplicating literal strings. Enables PipelineCache to fall back to an embedded default shader when the file-based one is missing. |
| **error** | WsgError enum — application-level error type mapping specific wgpu failure modes to user-friendly messages via thiserror. Every variant maps a GPU initialization or rendering failure to a recoverable or fatal outcome. |
## Interaction with Other Modules
- **pipeline::pipeline_cache**: load_shader() reads BASIC_SHADER_PATH from disk; falls back to BASIC_SHADER if unreadable.
- **pipeline::pipeline_cache**: load_shader() reads STANDARD_SHADER_PATH from disk; falls back to STANDARD_SHADER if unreadable.
- **core::context**: Returns WsgError variants from all fallible methods (new, configure, begin_frame).
- **core::renderer**: Does not use errors directly — panics on invalid state rather than returning Result.
+6 -13
View File
@@ -6,26 +6,19 @@
//! Also provides application defaults for window title, width, and height used by AppBuilder.
//!
//! ## Interaction with Other Modules
//! - **pipeline_cache::load_shader()** reads `BASIC_SHADER_PATH` from disk; if unreadable, falls back to `BASIC_SHADER`.
//! - **pipeline_cache::load_shader()** reads `STANDARD_SHADER_PATH` from disk; if unreadable, falls back to `STANDARD_SHADER`.
//! - Both constants are compile-time values (`include_str!`) ensuring the fallback shader is always available even without external files.
//! - **app::AppBuilder** reads APP_DEFAULT_TITLE, APP_DEFAULT_WIDTH, and APP_DEFAULT_HEIGHT for default window configuration.
/// Path to the default WGSL shader file on disk (runtime). Used by PipelineCache::load_shader() for file-based loading.
pub const BASIC_SHADER_PATH: &str = "assets/shaders/basic_shader.wgsl";
/// The basic WGSL shader source code, embedded at compile time via `include_str!`.
/// Serves as a fallback when `BASIC_SHADER_PATH` cannot be read at runtime.
pub const BASIC_SHADER: &str = include_str!("../shaders/basic_shader.wgsl");
/// Path to the standard (Phong) WGSL shader file on disk (runtime). Used by PipelineCache::load_shader()
/// once standardized (Étape 3) : this shader carries the full uniform contract (frame + object bind groups)
/// and supports an unlit mode so flat 2D rendering is a special case of the 3D lit path.
/// for file-based loading. This is the unified pipeline shader (Étape 3 : un seul layout pour tous) :
/// it carries the full uniform contract (frame + object bind groups) and supports an unlit mode so flat
/// 2D rendering is a special case of the 3D lit path. The `basic` family was removed (Étape 5).
pub const STANDARD_SHADER_PATH: &str = "assets/shaders/standard_shader.wgsl";
/// The standard (Phong) WGSL shader source code, embedded at compile time via `include_str!`.
/// Not yet compiled by any pipeline (Étape 2 : shader seul, non branché). Becomes the unified
/// pipeline shader once the uniform infrastructure exists (Étape 3). The unlit variant is the
/// replacement for the flat `basic` family.
/// Serves as the fallback when `STANDARD_SHADER_PATH` cannot be read at runtime. Because every
/// pipeline uses the unified layout (frame @0 + object @1), this is the only shader the library ships.
pub const STANDARD_SHADER: &str = include_str!("../shaders/standard_shader.wgsl");
/// Default application title displayed in the OS taskbar/window decorations.
+3 -3
View File
@@ -5,7 +5,7 @@
//! Both are consumed by other modules but have no internal dependencies on them.
//!
//! ## Interaction with Other Modules
//! - `pipeline_cache` loads shaders from disk using conf::BASIC_SHADER_PATH; falls back to BASIC_SHADER.
//! - `pipeline_cache` loads shaders from disk using conf::STANDARD_SHADER_PATH; falls back to STANDARD_SHADER.
//! - `context` returns WsgError variants from all fallible methods (new, configure, begin_frame).
//! - `renderer` does not use errors directly (panics on invalid state rather than returning Result).
@@ -13,6 +13,6 @@ pub mod conf;
pub mod error;
// Re-exports
pub use conf::BASIC_SHADER;
pub use conf::BASIC_SHADER_PATH;
pub use conf::STANDARD_SHADER;
pub use conf::STANDARD_SHADER_PATH;
pub use error::WsgError;