From 0a85aff17b3dcf6821c2770a06aa74630555cf7e 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?feat(examples):=203D=20MVP=20cube=20via=20stand?= =?UTF-8?q?ard=20shader,=20drop=20basic=20(=C3=89tape=205)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/examples/cube.rs | 122 +++++++++++++++++++++++++++++ lib/examples/manual.rs | 15 ++-- lib/examples/simple.rs | 14 ++-- lib/src/app.rs | 10 +++ lib/src/core/renderer.rs | 40 ++++++++-- lib/src/lib.rs | 2 +- lib/src/pipeline/README.md | 4 +- lib/src/pipeline/mod.rs | 2 +- lib/src/pipeline/pipeline_cache.rs | 10 +-- lib/src/resources/README.md | 2 +- lib/src/shaders/README.md | 37 +++------ lib/src/shaders/basic_shader.wgsl | 42 ---------- lib/src/utils/README.md | 4 +- lib/src/utils/conf.rs | 19 ++--- lib/src/utils/mod.rs | 6 +- 15 files changed, 218 insertions(+), 111 deletions(-) create mode 100644 lib/examples/cube.rs delete mode 100644 lib/src/shaders/basic_shader.wgsl diff --git a/lib/examples/cube.rs b/lib/examples/cube.rs new file mode 100644 index 0000000..039af10 --- /dev/null +++ b/lib/examples/cube.rs @@ -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 { + 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 { + 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 }) +} diff --git a/lib/examples/manual.rs b/lib/examples/manual.rs index 461e1cc..cacf5a6 100644 --- a/lib/examples/manual.rs +++ b/lib/examples/manual.rs @@ -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 = [ diff --git a/lib/examples/simple.rs b/lib/examples/simple.rs index 55cebfe..69afdcc 100644 --- a/lib/examples/simple.rs +++ b/lib/examples/simple.rs @@ -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(); } } diff --git a/lib/src/app.rs b/lib/src/app.rs index 0276451..ad094cd 100644 --- a/lib/src/app.rs +++ b/lib/src/app.rs @@ -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 { diff --git a/lib/src/core/renderer.rs b/lib/src/core/renderer.rs index 37b7bae..dcd7eb7 100644 --- a/lib/src/core/renderer.rs +++ b/lib/src/core/renderer.rs @@ -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>, + /// 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)); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6a29952..1a333e3 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -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%. diff --git a/lib/src/pipeline/README.md b/lib/src/pipeline/README.md index 79642f5..32aaa04 100644 --- a/lib/src/pipeline/README.md +++ b/lib/src/pipeline/README.md @@ -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. diff --git a/lib/src/pipeline/mod.rs b/lib/src/pipeline/mod.rs index 8be61b9..99f0a17 100644 --- a/lib/src/pipeline/mod.rs +++ b/lib/src/pipeline/mod.rs @@ -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; diff --git a/lib/src/pipeline/pipeline_cache.rs b/lib/src/pipeline/pipeline_cache.rs index 7fa6d06..9d182fe 100644 --- a/lib/src/pipeline/pipeline_cache.rs +++ b/lib/src/pipeline/pipeline_cache.rs @@ -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 { diff --git a/lib/src/resources/README.md b/lib/src/resources/README.md index 0d45d84..836d68a 100644 --- a/lib/src/resources/README.md +++ b/lib/src/resources/README.md @@ -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 and Arc 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. diff --git a/lib/src/shaders/README.md b/lib/src/shaders/README.md index 2cca936..fa6929c 100644 --- a/lib/src/shaders/README.md +++ b/lib/src/shaders/README.md @@ -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` — fragment entry point writing RGBA output - -### Vertex Input Layout - -| Location | Attribute | Type | Offset (bytes) | -|----------|-----------|------|----------------| -| 0 | position | vec3 | 0 | -| 1 | uv | vec2 | 12 | -| 2 | color | vec3 | 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. diff --git a/lib/src/shaders/basic_shader.wgsl b/lib/src/shaders/basic_shader.wgsl deleted file mode 100644 index c8656a5..0000000 --- a/lib/src/shaders/basic_shader.wgsl +++ /dev/null @@ -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, offset 0 bytes in vertex buffer -//! - model.uv → @location(1), vec2, offset 12 bytes in vertex buffer -//! - model.color → @location(2), vec3, offset 24 bytes in vertex buffer -//! - @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 -//! - 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(position, 1.0), assuming position is already in NDC space. - -struct VertexInput { - @location(0) position: vec3, - @location(1) uv: vec2, - @location(2) color: vec3, -}; - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) color: vec3, -}; - -@vertex -fn vs_main(model: VertexInput) -> VertexOutput { - var out: VertexOutput; - out.clip_position = vec4(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 { - return vec4(in.color, 1.0); -} diff --git a/lib/src/utils/README.md b/lib/src/utils/README.md index bb5a99a..c8337b9 100644 --- a/lib/src/utils/README.md +++ b/lib/src/utils/README.md @@ -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. diff --git a/lib/src/utils/conf.rs b/lib/src/utils/conf.rs index c6f2a10..c5b6cb4 100644 --- a/lib/src/utils/conf.rs +++ b/lib/src/utils/conf.rs @@ -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. diff --git a/lib/src/utils/mod.rs b/lib/src/utils/mod.rs index 17592c8..19606e1 100644 --- a/lib/src/utils/mod.rs +++ b/lib/src/utils/mod.rs @@ -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;