You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Uniform buffer objects (UBOs) are a standard mechanism to pass per‑frame or per‑draw constants to shaders. This document demonstrates a minimal 3D spinning triangle that uses a UBO to provide a model‑view‑projection matrix to the vertex shader.
-[Requirements and Constraints](#requirements-and-constraints)
28
+
-[Data Flow](#data-flow)
29
+
-[Implementation Steps](#implementation-steps)
30
+
-[Step 1 — Runtime and Component Skeleton](#step-1)
31
+
-[Step 2 — Vertex and Fragment Shaders](#step-2)
32
+
-[Step 3 — Mesh Data and Vertex Layout](#step-3)
33
+
-[Step 4 — Uniform Data Layout in Rust](#step-4)
34
+
-[Step 5 — Bind Group Layout at Set 0](#step-5)
35
+
-[Step 6 — Create the Uniform Buffer and Bind Group](#step-6)
36
+
-[Step 7 — Build the Render Pipeline](#step-7)
37
+
-[Step 8 — Per‑Frame Update and Write](#step-8)
38
+
-[Step 9 — Issue Draw Commands](#step-9)
39
+
-[Step 10 — Handle Window Resize](#step-10)
40
+
-[Validation](#validation)
41
+
-[Notes](#notes)
42
+
-[Exercises](#exercises)
43
+
-[Changelog](#changelog)
44
+
45
+
## Goals <aname="goals"></a>
46
+
23
47
- Build a spinning triangle that reads a model‑view‑projection matrix from a uniform buffer.
24
48
- Learn how to define a uniform block in shaders and mirror it in Rust.
25
49
- Learn how to create a bind group layout, allocate a uniform buffer, and write per‑frame data.
26
50
- Learn how to construct a render pipeline and issue draw commands using Lambda’s builders.
27
51
28
-
Prerequisites
52
+
## Prerequisites <aname="prerequisites"></a>
29
53
- Rust toolchain installed and the workspace builds: `cargo build --workspace`.
30
54
- Familiarity with basic Rust and the repository’s example layout.
31
55
- Ability to run examples: `cargo run --example minimal` verifies setup.
32
56
33
-
Requirements and constraints
57
+
## Requirements and Constraints <aname="requirements-and-constraints"></a>
34
58
- The uniform block layout in the shader and the Rust structure MUST match in size, alignment, and field order.
35
59
- The bind group layout in Rust MUST match the shader `set` and `binding` indices.
36
60
- Matrices MUST be provided in the order expected by the shader (column‑major in this example). Rationale: prevents implicit driver conversions and avoids incorrect transforms.
37
61
- Acronyms MUST be defined on first use (e.g., uniform buffer object (UBO)).
38
62
39
-
Data flow
63
+
## Data Flow <aname="data-flow"></a>
40
64
- CPU writes → UBO → bind group (set 0) → pipeline layout → vertex shader.
41
65
- A single UBO MAY be reused across multiple draws and pipelines.
### Step 1 — Runtime and Component Skeleton <aname="step-1"></a>
46
82
Before rendering, create a minimal application entry point and a `Component` that receives lifecycle callbacks. The engine routes initialization, input, updates, and rendering through the component interface, which provides the context needed to create GPU resources and submit commands.
47
83
48
84
```rust
@@ -83,7 +119,7 @@ fn main() {
83
119
}
84
120
```
85
121
86
-
2)Vertex and fragment shaders
122
+
### Step 2 — Vertex and Fragment Shaders <aname="step-2"></a>
87
123
Define shader stages next. The vertex shader declares three vertex attributes and a uniform block at set 0, binding 0. It multiplies the incoming position by the matrix stored in the UBO. The fragment shader returns the interpolated color. Declaring the uniform block now establishes the contract that the Rust side will satisfy via a matching bind group layout and buffer.
88
124
89
125
```glsl
@@ -138,7 +174,7 @@ let vertex_shader: Shader = shader_builder.build(vertex_virtual);
### Step 3 — Mesh Data and Vertex Layout <aname="step-3"></a>
142
178
Provide vertex data for a single triangle and describe how the pipeline reads it. Each vertex stores position, normal, and color as three `f32` values. The attribute descriptors specify locations and byte offsets so the pipeline can interpret the packed buffer consistently across platforms.
143
179
144
180
```rust
@@ -175,7 +211,7 @@ let mesh: Mesh = mesh_builder
175
211
.build();
176
212
```
177
213
178
-
4)Uniform data layout in Rust
214
+
### Step 4 — Uniform Data Layout in Rust <aname="step-4"></a>
179
215
Mirror the shader’s uniform block with a Rust structure. Use `#[repr(C)]` so the memory layout is predictable. A `mat4` in the shader corresponds to a 4×4 `f32` array here. Many GPU interfaces expect column‑major matrices; transpose before upload if the local math library is row‑major. This avoids implicit driver conversions and prevents incorrect transforms.
180
216
181
217
```rust
@@ -186,7 +222,7 @@ pub struct GlobalsUniform {
186
222
}
187
223
```
188
224
189
-
5)Bind group layout at set 0
225
+
### Step 5 — Bind Group Layout at Set 0 <aname="step-5"></a>
190
226
Create a bind group layout that matches the shader declaration. This layout says: at set 0, binding 0 there is a uniform buffer visible to the vertex stage. The pipeline layout will incorporate this, ensuring the shader and the bound resources agree at draw time.
191
227
192
228
```rust
@@ -197,7 +233,7 @@ let layout = BindGroupLayoutBuilder::new()
197
233
.build(render_context);
198
234
```
199
235
200
-
6)Create the uniform buffer and bind group
236
+
### Step 6 — Create the Uniform Buffer and Bind Group <aname="step-6"></a>
201
237
Allocate the uniform buffer, seed it with an initial matrix, and create a bind group using the layout. Mark the buffer usage as `UNIFORM` and properties as `CPU_VISIBLE` to permit direct per‑frame writes from the CPU. This is the simplest path for frequently updated data.
202
238
203
239
```rust
@@ -221,7 +257,7 @@ let bind_group = BindGroupBuilder::new()
221
257
.build(render_context);
222
258
```
223
259
224
-
7)Build the render pipeline
260
+
### Step 7 — Build the Render Pipeline <aname="step-7"></a>
225
261
Construct the render pipeline, supplying the bind group layouts, vertex buffer, and the shader pair. Disable face culling for simplicity so both sides of the triangle remain visible regardless of winding during early experimentation.
226
262
227
263
```rust
@@ -242,7 +278,7 @@ let pipeline = RenderPipelineBuilder::new()
### Step 8 — Per‑Frame Update and Write <aname="step-8"></a>
246
282
Animate by recomputing the model‑view‑projection matrix each frame and writing it into the uniform buffer. The helper `compute_model_view_projection_matrix_about_pivot` maintains a correct aspect ratio using the current window dimensions and rotates the model around a chosen pivot.
247
283
248
284
```rust
@@ -269,7 +305,7 @@ fn update_uniform_each_frame(
269
305
&camera,
270
306
width.max(1),
271
307
height.max(1),
272
-
[0.0, -1.0/3.0, 0.0], // pivot
308
+
[0.0, -1.0/3.0, 0.0], // pivot at triangle centroid (geometric center)
Record commands in the order the GPU expects: begin the render pass, set the pipeline, configure viewport and scissors, bind the vertex buffer and the uniform bind group, draw the vertices, then end the pass. This sequence describes the full state required for a single draw.
Track window dimensions and update the per‑frame matrix using the new aspect ratio. Forwarding resize events into stored `width` and `height` maintains consistent camera projection across resizes.
0 commit comments