Skip to content

Commit d3dc435

Browse files
authored
feat: add support for Uniform Buffer Objects
Adds support for Uniform Buffer objects along with an example and tutorial for how to build that example from scratch.
2 parents 0fdc489 + e82493a commit d3dc435

File tree

17 files changed

+2580
-90
lines changed

17 files changed

+2580
-90
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
1. [Optional dependencies](#opt_deps)
1818
1. [Linux, Macos, Windows](#bash)
1919
1. [Getting started](#get_started)
20+
1. [Tutorials](#tutorials)
2021
1. [Examples](#examples)
2122
1. [Planned additions](#plans)
2223
1. [Releases & Publishing](#publishing)
@@ -110,7 +111,19 @@ If this works successfully, then lambda is ready to work on your system!
110111
## Getting started <a name="get_started"></a>
111112
Coming soon.
112113

114+
## Tutorials <a name="tutorials"></a>
115+
Start with the tutorials to build features step by step:
116+
117+
- Tutorials index: [docs/tutorials/](./docs/tutorials/)
118+
- Uniform Buffers: Build a Spinning Triangle: [docs/tutorials/uniform-buffers.md](./docs/tutorials/uniform-buffers.md)
119+
113120
## Examples <a name="examples"></a>
121+
Browse example sources:
122+
123+
- Core API examples: [crates/lambda-rs/examples/](./crates/lambda-rs/examples/)
124+
- Logging examples: [crates/lambda-rs-logging/examples/](./crates/lambda-rs-logging/examples/)
125+
- Argument parsing examples: [crates/lambda-rs-args/examples/](./crates/lambda-rs-args/examples/)
126+
114127
### Minimal
115128
A minimal example of an application with a working window using lambda.
116129
```rust
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
//! Bind group and bind group layout builders for the platform layer.
2+
//!
3+
//! These types provide a thin, explicit wrapper around `wgpu` bind resources
4+
//! so higher layers can compose layouts and groups without pulling in raw
5+
//! `wgpu` descriptors throughout the codebase.
6+
7+
use std::num::NonZeroU64;
8+
9+
use crate::wgpu::types as wgpu;
10+
11+
#[derive(Debug)]
12+
/// Wrapper around `wgpu::BindGroupLayout` that preserves a label.
13+
pub struct BindGroupLayout {
14+
pub(crate) raw: wgpu::BindGroupLayout,
15+
pub(crate) label: Option<String>,
16+
}
17+
18+
impl BindGroupLayout {
19+
/// Borrow the underlying `wgpu::BindGroupLayout`.
20+
pub fn raw(&self) -> &wgpu::BindGroupLayout {
21+
return &self.raw;
22+
}
23+
24+
/// Optional debug label used during creation.
25+
pub fn label(&self) -> Option<&str> {
26+
return self.label.as_deref();
27+
}
28+
}
29+
30+
#[derive(Debug)]
31+
/// Wrapper around `wgpu::BindGroup` that preserves a label.
32+
pub struct BindGroup {
33+
pub(crate) raw: wgpu::BindGroup,
34+
pub(crate) label: Option<String>,
35+
}
36+
37+
impl BindGroup {
38+
/// Borrow the underlying `wgpu::BindGroup`.
39+
pub fn raw(&self) -> &wgpu::BindGroup {
40+
return &self.raw;
41+
}
42+
43+
/// Optional debug label used during creation.
44+
pub fn label(&self) -> Option<&str> {
45+
return self.label.as_deref();
46+
}
47+
}
48+
49+
#[derive(Clone, Copy, Debug)]
50+
/// Visibility of a binding across shader stages.
51+
pub enum Visibility {
52+
Vertex,
53+
Fragment,
54+
Compute,
55+
VertexAndFragment,
56+
All,
57+
}
58+
59+
impl Visibility {
60+
fn to_wgpu(self) -> wgpu::ShaderStages {
61+
return match self {
62+
Visibility::Vertex => wgpu::ShaderStages::VERTEX,
63+
Visibility::Fragment => wgpu::ShaderStages::FRAGMENT,
64+
Visibility::Compute => wgpu::ShaderStages::COMPUTE,
65+
Visibility::VertexAndFragment => {
66+
wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT
67+
}
68+
Visibility::All => wgpu::ShaderStages::all(),
69+
};
70+
}
71+
}
72+
73+
#[cfg(test)]
74+
mod tests {
75+
use super::*;
76+
77+
/// This test verifies that each public binding visibility option is
78+
/// converted into the correct set of shader stage flags expected by the
79+
/// underlying graphics layer. It checks single stage selections, a
80+
/// combination of vertex and fragment stages, and the catch‑all option that
81+
/// enables all stages. The goal is to demonstrate that the mapping logic is
82+
/// precise and predictable so higher level code can rely on it when building
83+
/// layouts and groups.
84+
#[test]
85+
fn visibility_maps_to_expected_shader_stages() {
86+
assert_eq!(Visibility::Vertex.to_wgpu(), wgpu::ShaderStages::VERTEX);
87+
assert_eq!(Visibility::Fragment.to_wgpu(), wgpu::ShaderStages::FRAGMENT);
88+
assert_eq!(Visibility::Compute.to_wgpu(), wgpu::ShaderStages::COMPUTE);
89+
assert_eq!(
90+
Visibility::VertexAndFragment.to_wgpu(),
91+
wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT
92+
);
93+
assert_eq!(Visibility::All.to_wgpu(), wgpu::ShaderStages::all());
94+
}
95+
}
96+
97+
#[derive(Default)]
98+
/// Builder for creating a `wgpu::BindGroupLayout`.
99+
pub struct BindGroupLayoutBuilder {
100+
label: Option<String>,
101+
entries: Vec<wgpu::BindGroupLayoutEntry>,
102+
}
103+
104+
impl BindGroupLayoutBuilder {
105+
/// Create a builder with no entries.
106+
pub fn new() -> Self {
107+
return Self {
108+
label: None,
109+
entries: Vec::new(),
110+
};
111+
}
112+
113+
/// Attach a human‑readable label.
114+
pub fn with_label(mut self, label: &str) -> Self {
115+
self.label = Some(label.to_string());
116+
return self;
117+
}
118+
119+
/// Declare a uniform buffer binding at the provided index.
120+
pub fn with_uniform(mut self, binding: u32, visibility: Visibility) -> Self {
121+
self.entries.push(wgpu::BindGroupLayoutEntry {
122+
binding,
123+
visibility: visibility.to_wgpu(),
124+
ty: wgpu::BindingType::Buffer {
125+
ty: wgpu::BufferBindingType::Uniform,
126+
has_dynamic_offset: false,
127+
min_binding_size: None,
128+
},
129+
count: None,
130+
});
131+
return self;
132+
}
133+
134+
/// Declare a uniform buffer binding with dynamic offsets at the provided index.
135+
pub fn with_uniform_dynamic(
136+
mut self,
137+
binding: u32,
138+
visibility: Visibility,
139+
) -> Self {
140+
self.entries.push(wgpu::BindGroupLayoutEntry {
141+
binding,
142+
visibility: visibility.to_wgpu(),
143+
ty: wgpu::BindingType::Buffer {
144+
ty: wgpu::BufferBindingType::Uniform,
145+
has_dynamic_offset: true,
146+
min_binding_size: None,
147+
},
148+
count: None,
149+
});
150+
return self;
151+
}
152+
153+
/// Build the layout using the provided device.
154+
pub fn build(self, device: &wgpu::Device) -> BindGroupLayout {
155+
let raw =
156+
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
157+
label: self.label.as_deref(),
158+
entries: &self.entries,
159+
});
160+
return BindGroupLayout {
161+
raw,
162+
label: self.label,
163+
};
164+
}
165+
}
166+
167+
#[derive(Default)]
168+
/// Builder for creating a `wgpu::BindGroup`.
169+
pub struct BindGroupBuilder<'a> {
170+
label: Option<String>,
171+
layout: Option<&'a wgpu::BindGroupLayout>,
172+
entries: Vec<wgpu::BindGroupEntry<'a>>,
173+
}
174+
175+
impl<'a> BindGroupBuilder<'a> {
176+
/// Create a new builder with no layout or entries.
177+
pub fn new() -> Self {
178+
return Self {
179+
label: None,
180+
layout: None,
181+
entries: Vec::new(),
182+
};
183+
}
184+
185+
/// Attach a human‑readable label.
186+
pub fn with_label(mut self, label: &str) -> Self {
187+
self.label = Some(label.to_string());
188+
return self;
189+
}
190+
191+
/// Specify the layout to use for this bind group.
192+
pub fn with_layout(mut self, layout: &'a BindGroupLayout) -> Self {
193+
self.layout = Some(layout.raw());
194+
return self;
195+
}
196+
197+
/// Bind a uniform buffer at a binding index with optional size slice.
198+
pub fn with_uniform(
199+
mut self,
200+
binding: u32,
201+
buffer: &'a wgpu::Buffer,
202+
offset: u64,
203+
size: Option<NonZeroU64>,
204+
) -> Self {
205+
self.entries.push(wgpu::BindGroupEntry {
206+
binding,
207+
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
208+
buffer,
209+
offset,
210+
size,
211+
}),
212+
});
213+
return self;
214+
}
215+
216+
/// Build the bind group with the accumulated entries.
217+
pub fn build(self, device: &wgpu::Device) -> BindGroup {
218+
let layout = self
219+
.layout
220+
.expect("BindGroupBuilder requires a layout before build");
221+
let raw = device.create_bind_group(&wgpu::BindGroupDescriptor {
222+
label: self.label.as_deref(),
223+
layout,
224+
entries: &self.entries,
225+
});
226+
return BindGroup {
227+
raw,
228+
label: self.label,
229+
};
230+
}
231+
}

crates/lambda-rs-platform/src/wgpu/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use wgpu::rwh::{
1515

1616
use crate::winit::WindowHandle;
1717

18+
pub mod bind;
19+
1820
#[derive(Debug, Clone)]
1921
/// Builder for creating a `wgpu::Instance` with consistent defaults.
2022
///

0 commit comments

Comments
 (0)