Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions crates/lambda-rs/examples/triangle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ impl Component<ComponentResult, String> 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));
Expand Down
1 change: 1 addition & 0 deletions crates/lambda-rs/examples/triangles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ impl Component<ComponentResult, String> for TrianglesComponent {

let push_constants_size = std::mem::size_of::<PushConstant>() as u32;
let pipeline = pipeline::RenderPipelineBuilder::new()
.with_culling(pipeline::CullingMode::None)
.with_push_constant(PipelineStage::VERTEX, push_constants_size)
.build(
render_context.gpu(),
Expand Down
9 changes: 6 additions & 3 deletions docs/tutorials/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ 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"]
---

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)
Expand All @@ -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.
Expand Down
236 changes: 236 additions & 0 deletions docs/tutorials/basic-triangle.md
Original file line number Diff line number Diff line change
@@ -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 <a name="overview"></a>

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 <a name="goals"></a>

- 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 <a name="prerequisites"></a>

- The workspace builds: `cargo build --workspace`.
- The `lambda-rs` crate examples run: `cargo run -p lambda-rs --example minimal`.

## Requirements and Constraints <a name="requirements-and-constraints"></a>

- 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 <a name="data-flow"></a>

- 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 <a name="implementation-steps"></a>

### Step 1 — Runtime and Component Skeleton <a name="step-1"></a>

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 <a name="step-2"></a>

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` <a name="step-3"></a>

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 <a name="step-4"></a>

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 <a name="step-5"></a>

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 <a name="step-6"></a>

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 <a name="validation"></a>

- Build: `cargo build --workspace`
- Run: `cargo run -p lambda-rs --example triangle`
- Expected behavior: a window opens and shows a solid-color triangle.

## Notes <a name="notes"></a>

- 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 <a name="conclusion"></a>

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 <a name="exercises"></a>

- 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 <a name="changelog"></a>

- 0.1.0 (2025-12-16): Initial draft aligned with
`crates/lambda-rs/examples/triangle.rs`.
Loading
Loading