From a1f1c2814ec199e638b8d34d245c8e0880f22196 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 16 Dec 2025 13:04:05 -0800 Subject: [PATCH 1/3] [remove] culling from the render pipeline. --- crates/lambda-rs/examples/triangle.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/lambda-rs/examples/triangle.rs b/crates/lambda-rs/examples/triangle.rs index 8cb77e1a..7b93de9d 100644 --- a/crates/lambda-rs/examples/triangle.rs +++ b/crates/lambda-rs/examples/triangle.rs @@ -48,14 +48,16 @@ impl Component for DemoComponent { render_context.depth_format(), ); - let pipeline = pipeline::RenderPipelineBuilder::new().build( - render_context.gpu(), - render_context.surface_format(), - render_context.depth_format(), - &render_pass, - &self.vertex_shader, - Some(&self.fragment_shader), - ); + let pipeline = pipeline::RenderPipelineBuilder::new() + .with_culling(pipeline::CullingMode::None) + .build( + render_context.gpu(), + render_context.surface_format(), + render_context.depth_format(), + &render_pass, + &self.vertex_shader, + Some(&self.fragment_shader), + ); // Attach the render pass and pipeline to the render context self.render_pass_id = Some(render_context.attach_render_pass(render_pass)); From 797047468a927f1e4ba111b43381a607ac53c0d1 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 16 Dec 2025 13:04:35 -0800 Subject: [PATCH 2/3] [remove] culling from the render pipeline. --- crates/lambda-rs/examples/triangles.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/lambda-rs/examples/triangles.rs b/crates/lambda-rs/examples/triangles.rs index 1c88157c..ddd5b92a 100644 --- a/crates/lambda-rs/examples/triangles.rs +++ b/crates/lambda-rs/examples/triangles.rs @@ -54,6 +54,7 @@ impl Component for TrianglesComponent { let push_constants_size = std::mem::size_of::() as u32; let pipeline = pipeline::RenderPipelineBuilder::new() + .with_culling(pipeline::CullingMode::None) .with_push_constant(PipelineStage::VERTEX, push_constants_size) .build( render_context.gpu(), From d9bbe94a4b4d2dbcf31dac75d457c919c1ac7fbe Mon Sep 17 00:00:00 2001 From: vmarcella Date: Tue, 16 Dec 2025 13:19:45 -0800 Subject: [PATCH 3/3] [add] tutorials for these examples. --- docs/tutorials/README.md | 9 +- docs/tutorials/basic-triangle.md | 236 ++++++++++++++++++ .../push-constants-multiple-triangles.md | 217 ++++++++++++++++ 3 files changed, 459 insertions(+), 3 deletions(-) create mode 100644 docs/tutorials/basic-triangle.md create mode 100644 docs/tutorials/push-constants-multiple-triangles.md diff --git a/docs/tutorials/README.md b/docs/tutorials/README.md index c7ba793f..a46ed084 100644 --- a/docs/tutorials/README.md +++ b/docs/tutorials/README.md @@ -3,13 +3,13 @@ title: "Tutorials Index" document_id: "tutorials-index-2025-10-17" status: "living" created: "2025-10-17T00:20:00Z" -last_updated: "2025-11-25T00:00:00Z" -version: "0.4.0" +last_updated: "2025-12-16T00:00:00Z" +version: "0.5.0" engine_workspace_version: "2023.1.30" wgpu_version: "26.0.1" shader_backend_default: "naga" winit_version: "0.29.10" -repo_commit: "2ff0a581af014a754e982881193c36f47e685602" +repo_commit: "797047468a927f1e4ba111b43381a607ac53c0d1" owners: ["lambda-sh"] reviewers: ["engine", "rendering"] tags: ["index", "tutorials", "docs"] @@ -17,6 +17,8 @@ tags: ["index", "tutorials", "docs"] This index lists tutorials that teach specific `lambda-rs` tasks through complete, incremental builds. +- Basic Triangle: Vertex‑Only Draw — [basic-triangle.md](basic-triangle.md) +- Push Constants: Draw Multiple 2D Triangles — [push-constants-multiple-triangles.md](push-constants-multiple-triangles.md) - Uniform Buffers: Build a Spinning Triangle — [uniform-buffers.md](uniform-buffers.md) - Textured Quad: Sample a 2D Texture — [textured-quad.md](textured-quad.md) - Textured Cube: 3D Push Constants + 2D Sampling — [textured-cube.md](textured-cube.md) @@ -26,6 +28,7 @@ This index lists tutorials that teach specific `lambda-rs` tasks through complet Browse all tutorials in this directory. Changelog +- 0.5.0 (2025-12-16): Add basic triangle and multi-triangle push constants tutorials; update metadata and commit. - 0.4.0 (2025-11-25): Add Instanced Quads tutorial; update metadata and commit. - 0.3.0 (2025-11-17): Add Reflective Room tutorial; update metadata and commit. - 0.2.0 (2025-11-10): Add links for textured quad and textured cube; update metadata and commit. diff --git a/docs/tutorials/basic-triangle.md b/docs/tutorials/basic-triangle.md new file mode 100644 index 00000000..06de5696 --- /dev/null +++ b/docs/tutorials/basic-triangle.md @@ -0,0 +1,236 @@ +--- +title: "Basic Triangle: Vertex‑Only Draw" +document_id: "basic-triangle-tutorial-2025-12-16" +status: "draft" +created: "2025-12-16T00:00:00Z" +last_updated: "2025-12-16T00:00:00Z" +version: "0.1.0" +engine_workspace_version: "2023.1.30" +wgpu_version: "26.0.1" +shader_backend_default: "naga" +winit_version: "0.29.10" +repo_commit: "797047468a927f1e4ba111b43381a607ac53c0d1" +owners: ["lambda-sh"] +reviewers: ["engine", "rendering"] +tags: ["tutorial", "graphics", "triangle", "rust", "wgpu"] +--- + +## Overview + +This tutorial renders a single 2D triangle using a vertex shader that derives +positions from `gl_VertexIndex`. The implementation uses no vertex buffers and +demonstrates the minimal render pass, pipeline, and command sequence in +`lambda-rs`. + +Reference implementation: `crates/lambda-rs/examples/triangle.rs`. + +## Table of Contents + +- [Overview](#overview) +- [Goals](#goals) +- [Prerequisites](#prerequisites) +- [Requirements and Constraints](#requirements-and-constraints) +- [Data Flow](#data-flow) +- [Implementation Steps](#implementation-steps) + - [Step 1 — Runtime and Component Skeleton](#step-1) + - [Step 2 — Vertex and Fragment Shaders](#step-2) + - [Step 3 — Compile Shaders with `ShaderBuilder`](#step-3) + - [Step 4 — Build Render Pass and Pipeline](#step-4) + - [Step 5 — Issue Render Commands](#step-5) + - [Step 6 — Handle Window Resize](#step-6) +- [Validation](#validation) +- [Notes](#notes) +- [Conclusion](#conclusion) +- [Exercises](#exercises) +- [Changelog](#changelog) + +## Goals + +- Render a triangle with a vertex shader driven by `gl_VertexIndex`. +- Learn the minimal `RenderCommand` sequence for a draw. +- Construct a `RenderPass` and `RenderPipeline` using builder APIs. + +## Prerequisites + +- The workspace builds: `cargo build --workspace`. +- The `lambda-rs` crate examples run: `cargo run -p lambda-rs --example minimal`. + +## Requirements and Constraints + +- Rendering commands MUST be issued inside an active render pass + (`RenderCommand::BeginRenderPass` ... `RenderCommand::EndRenderPass`). +- The pipeline MUST be set before draw commands (`RenderCommand::SetPipeline`). +- The shader interface MUST match the pipeline configuration (no vertex buffers + are declared for this example). +- Back-face culling MUST be disabled or the triangle winding MUST be adjusted. + Rationale: the example’s vertex positions are defined in clockwise order. + +## Data Flow + +- CPU builds shaders and pipeline once in `on_attach`. +- CPU emits render commands each frame in `on_render`. +- The GPU generates vertex positions from `gl_VertexIndex` (no vertex buffers). + +ASCII diagram + +``` +Component::on_attach + ├─ ShaderBuilder → Shader modules + ├─ RenderPassBuilder → RenderPass + └─ RenderPipelineBuilder → RenderPipeline + +Component::on_render (each frame) + BeginRenderPass → SetPipeline → SetViewports/Scissors → Draw → EndRenderPass +``` + +## Implementation Steps + +### Step 1 — Runtime and Component Skeleton + +Create an `ApplicationRuntime` and register a `Component` that receives +`on_attach`, `on_render`, and `on_event` callbacks. + +```rust +fn main() { + let runtime = ApplicationRuntimeBuilder::new("2D Triangle Demo") + .with_window_configured_as(|window_builder| { + return window_builder + .with_dimensions(1200, 600) + .with_name("2D Triangle Window"); + }) + .with_component(|runtime, demo: DemoComponent| { + return (runtime, demo); + }) + .build(); + + start_runtime(runtime); +} +``` + +The runtime drives component lifecycle and calls `on_render` on each frame. + +### Step 2 — Vertex and Fragment Shaders + +The vertex shader generates positions from `gl_VertexIndex` so the draw call +only needs a vertex count of `3`. + +```glsl +vec2 positions[3]; +positions[0] = vec2(0.0, -0.5); +positions[1] = vec2(-0.5, 0.5); +positions[2] = vec2(0.5, 0.5); + +gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); +``` + +The fragment shader outputs a constant color. + +### Step 3 — Compile Shaders with `ShaderBuilder` + +Load shader sources from `crates/lambda-rs/assets/shaders/` and compile them +using `ShaderBuilder`. + +```rust +let triangle_vertex = VirtualShader::Source { + source: include_str!("../assets/shaders/triangle.vert").to_string(), + kind: ShaderKind::Vertex, + name: String::from("triangle"), + entry_point: String::from("main"), +}; +``` + +The compiled `Shader` objects are stored in component state and passed to the +pipeline builder during `on_attach`. + +### Step 4 — Build Render Pass and Pipeline + +Construct a `RenderPass` targeting the surface format, then build a pipeline. +Disable culling to ensure the triangle is visible regardless of winding. + +```rust +let render_pass = render_pass::RenderPassBuilder::new().build( + render_context.gpu(), + render_context.surface_format(), + render_context.depth_format(), +); + +let pipeline = pipeline::RenderPipelineBuilder::new() + .with_culling(pipeline::CullingMode::None) + .build( + render_context.gpu(), + render_context.surface_format(), + render_context.depth_format(), + &render_pass, + &self.vertex_shader, + Some(&self.fragment_shader), + ); +``` + +Attach the created resources to the `RenderContext` and store their IDs. + +### Step 5 — Issue Render Commands + +Emit a pass begin, bind the pipeline, set viewport/scissor, and issue a draw. + +```rust +RenderCommand::Draw { + vertices: 0..3, + instances: 0..1, +} +``` + +This produces one triangle using three implicit vertices. + +### Step 6 — Handle Window Resize + +Track `WindowEvent::Resize` and rebuild the `Viewport` each frame using the +stored dimensions. + +The viewport and scissor MUST match the surface dimensions to avoid clipping or +undefined behavior when the window resizes. + +## Validation + +- Build: `cargo build --workspace` +- Run: `cargo run -p lambda-rs --example triangle` +- Expected behavior: a window opens and shows a solid-color triangle. + +## Notes + +- Culling and winding + - This tutorial disables culling via `.with_culling(CullingMode::None)`. + - If culling is enabled, the vertex order in + `crates/lambda-rs/assets/shaders/triangle.vert` SHOULD be updated to + counter-clockwise winding for a default `front_face = CCW` pipeline. +- Debugging + - If the window is blank, verify that the pipeline is set inside the render + pass and the draw uses `0..3` vertices. + +## Conclusion + +This tutorial demonstrates the minimal `lambda-rs` rendering path: compile +shaders, build a render pass and pipeline, and issue a draw using +`RenderCommand`s. + +## Exercises + +- Exercise 1: Change the triangle color + - Modify `crates/lambda-rs/assets/shaders/triangle.frag` to output a different + constant color. +- Exercise 2: Enable back-face culling + - Set `.with_culling(CullingMode::Back)` and update the vertex order in + `crates/lambda-rs/assets/shaders/triangle.vert` to counter-clockwise. +- Exercise 3: Add a second triangle + - Issue a second `Draw` and offset positions in the shader for one of the + triangles. +- Exercise 4: Introduce push constants + - Add a push constant color and position and port the shader interface to + match `crates/lambda-rs/examples/triangles.rs`. +- Exercise 5: Replace `gl_VertexIndex` with a vertex buffer + - Create a vertex buffer for positions and update the pipeline and shader + inputs accordingly. + +## Changelog + +- 0.1.0 (2025-12-16): Initial draft aligned with + `crates/lambda-rs/examples/triangle.rs`. diff --git a/docs/tutorials/push-constants-multiple-triangles.md b/docs/tutorials/push-constants-multiple-triangles.md new file mode 100644 index 00000000..e8969915 --- /dev/null +++ b/docs/tutorials/push-constants-multiple-triangles.md @@ -0,0 +1,217 @@ +--- +title: "Push Constants: Draw Multiple 2D Triangles" +document_id: "push-constants-multiple-triangles-tutorial-2025-12-16" +status: "draft" +created: "2025-12-16T00:00:00Z" +last_updated: "2025-12-16T00:00:00Z" +version: "0.1.0" +engine_workspace_version: "2023.1.30" +wgpu_version: "26.0.1" +shader_backend_default: "naga" +winit_version: "0.29.10" +repo_commit: "797047468a927f1e4ba111b43381a607ac53c0d1" +owners: ["lambda-sh"] +reviewers: ["engine", "rendering"] +tags: ["tutorial", "graphics", "push-constants", "triangle", "rust", "wgpu"] +--- + +## Overview + +Push constants provide a small block of per-draw data that is cheap to update +and does not require buffers or bind groups. This tutorial draws multiple 2D +triangles by looping over a set of push constant values and issuing one draw +per triangle. + +Reference implementation: `crates/lambda-rs/examples/triangles.rs`. + +## Table of Contents + +- [Overview](#overview) +- [Goals](#goals) +- [Prerequisites](#prerequisites) +- [Requirements and Constraints](#requirements-and-constraints) +- [Data Flow](#data-flow) +- [Implementation Steps](#implementation-steps) + - [Step 1 — Define the Push Constant Layout](#step-1) + - [Step 2 — Shaders for Position, Scale, and Color](#step-2) + - [Step 3 — Build a Pipeline with Push Constants](#step-3) + - [Step 4 — Push Constants per Draw](#step-4) + - [Step 5 — Input and Resize Handling](#step-5) +- [Validation](#validation) +- [Notes](#notes) +- [Conclusion](#conclusion) +- [Exercises](#exercises) +- [Changelog](#changelog) + +## Goals + +- Define a push constant block in GLSL and mirror it in Rust. +- Build a pipeline that declares a vertex-stage push constant range. +- Draw multiple triangles by pushing per-draw constants and issuing draws. + +## Prerequisites + +- The workspace builds: `cargo build --workspace`. +- The `lambda-rs` crate examples run: `cargo run -p lambda-rs --example minimal`. + +## Requirements and Constraints + +- Push constant layout MUST match between shader and Rust in size, alignment, + and field order (`#[repr(C)]` is required on the Rust struct). +- The pipeline MUST declare a push constant range for the stage that reads it + (`PipelineStage::VERTEX` in this example). +- The pushed byte slice length MUST match the pipeline’s declared push constant + size. +- Back-face culling MUST be disabled or the triangle winding MUST be adjusted. + Rationale: the example’s vertex positions are defined in clockwise order. + +## Data Flow + +- CPU constructs pipeline and render pass once in `on_attach`. +- CPU builds a list of per-triangle `PushConstant` values on each frame. +- CPU emits a loop of `PushConstants` → `Draw` inside a single render pass. + +ASCII diagram + +``` +PushConstant (CPU) ──▶ RenderCommand::PushConstants ──▶ Vertex Shader + │ │ + └────────────── per triangle draw ──────────────┘ +``` + +## Implementation Steps + +### Step 1 — Define the Push Constant Layout + +Define the push constant block in the vertex shader and mirror it in Rust. + +```glsl +layout(push_constant) uniform PushConstant { + vec4 color; + vec2 pos; + vec2 scale; +} pcs; +``` + +```rust +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct PushConstant { + color: [f32; 4], + pos: [f32; 2], + scale: [f32; 2], +} +``` + +This layout provides per-draw color, translation, and scale. + +### Step 2 — Shaders for Position, Scale, and Color + +The vertex shader uses `gl_VertexIndex` to select base positions, then applies +`scale` and `pos`. The fragment shader outputs the interpolated color. + +Reference shader sources: +- `crates/lambda-rs/assets/shaders/triangles.vert` +- `crates/lambda-rs/assets/shaders/triangles.frag` + +### Step 3 — Build a Pipeline with Push Constants + +Compute the push constant size and configure the pipeline to accept vertex-stage +push constants. Disable culling for consistent visibility. + +```rust +let push_constants_size = std::mem::size_of::() as u32; + +let pipeline = pipeline::RenderPipelineBuilder::new() + .with_culling(pipeline::CullingMode::None) + .with_push_constant(PipelineStage::VERTEX, push_constants_size) + .build( + render_context.gpu(), + render_context.surface_format(), + render_context.depth_format(), + &render_pass, + &self.vertex_shader, + Some(&self.triangle_vertex), + ); +``` + +The pipeline definition controls which stages can read the pushed bytes. + +### Step 4 — Push Constants per Draw + +Build a list of `PushConstant` values, then emit a loop that pushes bytes and +issues a draw for each triangle. + +```rust +for triangle in triangle_data { + commands.push(RenderCommand::PushConstants { + pipeline: render_pipeline.clone(), + stage: PipelineStage::VERTEX, + offset: 0, + bytes: Vec::from(push_constants_to_bytes(triangle)), + }); + commands.push(RenderCommand::Draw { + vertices: 0..3, + instances: 0..1, + }); +} +``` + +This produces multiple triangles without creating any GPU buffers. + +### Step 5 — Input and Resize Handling + +Update component state from events: +- `WindowEvent::Resize` updates the stored width/height for viewport creation. +- `KeyW`, `KeyA`, `KeyS`, `KeyD` update the translation for one triangle. + +These updates are reflected on the next `on_render` call. + +## Validation + +- Build: `cargo build --workspace` +- Run: `cargo run -p lambda-rs --example triangles` +- Expected behavior: a window opens and shows multiple colored triangles; the + `W`, `A`, `S`, and `D` keys move one triangle. + +## Notes + +- Push constant limits + - Push constants are device-limited; the declared size MUST remain within the + GPU’s supported push constant range. +- Layout correctness + - The Rust `PushConstant` type MUST remain `#[repr(C)]` and must not include + padding-sensitive fields without validating the matching GLSL layout. +- Naming + - The reference implementation stores the fragment shader in the field named + `triangle_vertex`; treat it as the fragment shader when extending the code. + +## Conclusion + +This tutorial demonstrates per-draw customization using push constants by +looping over a set of constants and issuing repeated draws within one render +pass. + +## Exercises + +- Exercise 1: Animate color or scale + - Update `animation_scalar` each frame and modulate one triangle’s color or + scale over time. +- Exercise 2: Add per-triangle rotation + - Extend the push constant block with an angle and rotate positions in the + vertex shader. +- Exercise 3: Enable back-face culling + - Set `.with_culling(CullingMode::Back)` and update + `crates/lambda-rs/assets/shaders/triangles.vert` to counter-clockwise + winding. +- Exercise 4: Consolidate into instancing + - Convert the per-triangle loop into one instanced draw and provide per- + instance data via a vertex buffer. +- Exercise 5: Add a UI-controlled triangle count + - Generate `triangle_data` dynamically from runtime state and draw an + arbitrary number of triangles. + +## Changelog + +- 0.1.0 (2025-12-16): Initial draft aligned with + `crates/lambda-rs/examples/triangles.rs`.