Skip to content

Commit 288633a

Browse files
authored
feat(lambda-rs): Add support for textures
2 parents 07e221a + c37e14c commit 288633a

24 files changed

+4017
-30
lines changed

.github/workflows/compile_lambda_rs.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,16 @@ jobs:
4747
sudo apt-get install -y \
4848
pkg-config libx11-dev libxcb1-dev libxcb-render0-dev \
4949
libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev \
50-
libwayland-dev libudev-dev libvulkan-dev
50+
libwayland-dev libudev-dev \
51+
libvulkan-dev libvulkan1 mesa-vulkan-drivers vulkan-tools
52+
53+
- name: Configure Vulkan (Ubuntu)
54+
if: ${{ matrix.os == 'ubuntu-latest' }}
55+
run: |
56+
echo "WGPU_BACKEND=vulkan" >> "$GITHUB_ENV"
57+
# Prefer Mesa's software Vulkan (lavapipe) to ensure headless availability
58+
echo "VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" >> "$GITHUB_ENV"
59+
vulkaninfo --summary || true
5160
5261
# Windows runners already include the required toolchain for DX12 builds.
5362

.github/workflows/release.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@ jobs:
4141
- name: Rust cache
4242
uses: Swatinem/rust-cache@v2
4343

44+
- name: Install Linux deps for winit/wgpu
45+
run: |
46+
sudo apt-get update
47+
sudo apt-get install -y \
48+
pkg-config libx11-dev libxcb1-dev libxcb-render0-dev \
49+
libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev \
50+
libwayland-dev libudev-dev \
51+
libvulkan-dev libvulkan1 mesa-vulkan-drivers vulkan-tools
52+
53+
- name: Configure Vulkan for headless CI
54+
run: |
55+
echo "WGPU_BACKEND=vulkan" >> "$GITHUB_ENV"
56+
echo "VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" >> "$GITHUB_ENV"
57+
vulkaninfo --summary || true
58+
4459
- name: Format check
4560
run: cargo fmt --all -- --check
4661

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

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,35 @@ mod tests {
9797
);
9898
assert_eq!(Visibility::All.to_wgpu(), wgpu::ShaderStages::all());
9999
}
100+
101+
#[test]
102+
fn sampled_texture_2d_layout_entry_is_correct() {
103+
let builder = BindGroupLayoutBuilder::new()
104+
.with_sampled_texture_2d(1, Visibility::Fragment)
105+
.with_sampler(2, Visibility::Fragment);
106+
assert_eq!(builder.entries.len(), 2);
107+
match builder.entries[0].ty {
108+
wgpu::BindingType::Texture {
109+
sample_type,
110+
view_dimension,
111+
multisampled,
112+
} => {
113+
assert_eq!(view_dimension, wgpu::TextureViewDimension::D2);
114+
assert_eq!(multisampled, false);
115+
match sample_type {
116+
wgpu::TextureSampleType::Float { filterable } => assert!(filterable),
117+
_ => panic!("expected float sample type"),
118+
}
119+
}
120+
_ => panic!("expected texture binding type"),
121+
}
122+
match builder.entries[1].ty {
123+
wgpu::BindingType::Sampler(kind) => {
124+
assert_eq!(kind, wgpu::SamplerBindingType::Filtering);
125+
}
126+
_ => panic!("expected sampler binding type"),
127+
}
128+
}
100129
}
101130

102131
/// Builder for creating a `wgpu::BindGroupLayout`.
@@ -155,6 +184,56 @@ impl BindGroupLayoutBuilder {
155184
return self;
156185
}
157186

187+
/// Declare a sampled texture binding (2D) at the provided index.
188+
pub fn with_sampled_texture_2d(
189+
mut self,
190+
binding: u32,
191+
visibility: Visibility,
192+
) -> Self {
193+
self.entries.push(wgpu::BindGroupLayoutEntry {
194+
binding,
195+
visibility: visibility.to_wgpu(),
196+
ty: wgpu::BindingType::Texture {
197+
sample_type: wgpu::TextureSampleType::Float { filterable: true },
198+
view_dimension: wgpu::TextureViewDimension::D2,
199+
multisampled: false,
200+
},
201+
count: None,
202+
});
203+
return self;
204+
}
205+
206+
/// Declare a sampled texture binding with an explicit view dimension.
207+
pub fn with_sampled_texture_dim(
208+
mut self,
209+
binding: u32,
210+
visibility: Visibility,
211+
view_dimension: crate::wgpu::texture::ViewDimension,
212+
) -> Self {
213+
self.entries.push(wgpu::BindGroupLayoutEntry {
214+
binding,
215+
visibility: visibility.to_wgpu(),
216+
ty: wgpu::BindingType::Texture {
217+
sample_type: wgpu::TextureSampleType::Float { filterable: true },
218+
view_dimension: view_dimension.to_wgpu(),
219+
multisampled: false,
220+
},
221+
count: None,
222+
});
223+
return self;
224+
}
225+
226+
/// Declare a filtering sampler binding at the provided index.
227+
pub fn with_sampler(mut self, binding: u32, visibility: Visibility) -> Self {
228+
self.entries.push(wgpu::BindGroupLayoutEntry {
229+
binding,
230+
visibility: visibility.to_wgpu(),
231+
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
232+
count: None,
233+
});
234+
return self;
235+
}
236+
158237
/// Build the layout using the provided device.
159238
pub fn build(self, gpu: &Gpu) -> BindGroupLayout {
160239
let raw =
@@ -220,6 +299,32 @@ impl<'a> BindGroupBuilder<'a> {
220299
return self;
221300
}
222301

302+
/// Bind a texture view at a binding index.
303+
pub fn with_texture(
304+
mut self,
305+
binding: u32,
306+
texture: &'a crate::wgpu::texture::Texture,
307+
) -> Self {
308+
self.entries.push(wgpu::BindGroupEntry {
309+
binding,
310+
resource: wgpu::BindingResource::TextureView(texture.view()),
311+
});
312+
return self;
313+
}
314+
315+
/// Bind a sampler at a binding index.
316+
pub fn with_sampler(
317+
mut self,
318+
binding: u32,
319+
sampler: &'a crate::wgpu::texture::Sampler,
320+
) -> Self {
321+
self.entries.push(wgpu::BindGroupEntry {
322+
binding,
323+
resource: wgpu::BindingResource::Sampler(sampler.raw()),
324+
});
325+
return self;
326+
}
327+
223328
/// Build the bind group with the accumulated entries.
224329
pub fn build(self, gpu: &Gpu) -> BindGroup {
225330
let layout = self
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
//! Cross‑platform GPU abstraction built on top of `wgpu`.
22
//!
33
//! This module exposes a small, opinionated wrapper around core `wgpu` types
4-
//! to make engine code concise while keeping configuration explicit. The
5-
//! builders here (for the instance, surface, and device/queue) provide sane
6-
//! defaults and narrow the surface area used by Lambda, without hiding
7-
//! important handles when you need to drop down to raw `wgpu`.
8-
9-
// keep this module focused on exports and submodules
4+
//! organized into focused submodules (instance, surface, gpu, pipeline, etc.).
5+
//! Higher layers import these modules rather than raw `wgpu` to keep Lambda’s
6+
//! API compact and stable.
107
8+
// Keep this module focused on exports and submodules only.
119
pub mod bind;
1210
pub mod buffer;
1311
pub mod command;
@@ -16,4 +14,5 @@ pub mod instance;
1614
pub mod pipeline;
1715
pub mod render_pass;
1816
pub mod surface;
17+
pub mod texture;
1918
pub mod vertex;

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::wgpu::{
88
bind,
99
gpu::Gpu,
1010
surface::SurfaceFormat,
11+
texture::DepthFormat,
1112
vertex::ColorFormat,
1213
};
1314

@@ -210,6 +211,7 @@ pub struct RenderPipelineBuilder<'a> {
210211
vertex_buffers: Vec<(u64, Vec<VertexAttributeDesc>)>,
211212
cull_mode: CullingMode,
212213
color_target_format: Option<wgpu::TextureFormat>,
214+
depth_stencil: Option<wgpu::DepthStencilState>,
213215
}
214216

215217
impl<'a> RenderPipelineBuilder<'a> {
@@ -221,6 +223,7 @@ impl<'a> RenderPipelineBuilder<'a> {
221223
vertex_buffers: Vec::new(),
222224
cull_mode: CullingMode::Back,
223225
color_target_format: None,
226+
depth_stencil: None,
224227
};
225228
}
226229

@@ -258,6 +261,20 @@ impl<'a> RenderPipelineBuilder<'a> {
258261
return self;
259262
}
260263

264+
/// Enable depth testing/writes using the provided depth format and default compare/write settings.
265+
///
266+
/// Defaults: compare Less, depth writes enabled, no stencil.
267+
pub fn with_depth_stencil(mut self, format: DepthFormat) -> Self {
268+
self.depth_stencil = Some(wgpu::DepthStencilState {
269+
format: format.to_wgpu(),
270+
depth_write_enabled: true,
271+
depth_compare: wgpu::CompareFunction::Less,
272+
stencil: wgpu::StencilState::default(),
273+
bias: wgpu::DepthBiasState::default(),
274+
});
275+
return self;
276+
}
277+
261278
/// Build the render pipeline from provided shader modules.
262279
pub fn build(
263280
self,
@@ -333,7 +350,7 @@ impl<'a> RenderPipelineBuilder<'a> {
333350
layout: layout_ref,
334351
vertex: vertex_state,
335352
primitive: primitive_state,
336-
depth_stencil: None,
353+
depth_stencil: self.depth_stencil,
337354
multisample: wgpu::MultisampleState::default(),
338355
fragment,
339356
multiview: None,

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

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,31 @@ impl Default for ColorOperations {
5454
}
5555
}
5656

57+
/// Depth load operation for a depth attachment.
58+
#[derive(Clone, Copy, Debug, PartialEq)]
59+
pub enum DepthLoadOp {
60+
/// Load the existing contents of the depth attachment.
61+
Load,
62+
/// Clear the depth attachment to the provided value in [0,1].
63+
Clear(f32),
64+
}
65+
66+
/// Depth operations (load/store) for the depth attachment.
67+
#[derive(Clone, Copy, Debug, PartialEq)]
68+
pub struct DepthOperations {
69+
pub load: DepthLoadOp,
70+
pub store: StoreOp,
71+
}
72+
73+
impl Default for DepthOperations {
74+
fn default() -> Self {
75+
return Self {
76+
load: DepthLoadOp::Clear(1.0),
77+
store: StoreOp::Store,
78+
};
79+
}
80+
}
81+
5782
/// Configuration for beginning a render pass.
5883
#[derive(Clone, Debug, Default)]
5984
pub struct RenderPassConfig {
@@ -255,13 +280,17 @@ impl RenderPassBuilder {
255280
return self;
256281
}
257282

283+
// Depth attachment is supplied at build time by the caller.
284+
258285
/// Build (begin) the render pass on the provided encoder using the provided
259286
/// color attachments list. The attachments list MUST outlive the returned
260287
/// render pass value.
261288
pub fn build<'view>(
262289
&'view self,
263290
encoder: &'view mut command::CommandEncoder,
264291
attachments: &'view mut RenderColorAttachments<'view>,
292+
depth_view: Option<crate::wgpu::surface::TextureViewRef<'view>>,
293+
depth_ops: Option<DepthOperations>,
265294
) -> RenderPass<'view> {
266295
let operations = match self.config.color_operations.load {
267296
ColorLoadOp::Load => wgpu::Operations {
@@ -288,10 +317,35 @@ impl RenderPassBuilder {
288317
// Apply operations to all provided attachments.
289318
attachments.set_operations_for_all(operations);
290319

320+
// Optional depth attachment
321+
let depth_stencil_attachment = depth_view.map(|v| {
322+
let dop = depth_ops.unwrap_or_default();
323+
wgpu::RenderPassDepthStencilAttachment {
324+
view: v.raw,
325+
depth_ops: Some(match dop.load {
326+
DepthLoadOp::Load => wgpu::Operations {
327+
load: wgpu::LoadOp::Load,
328+
store: match dop.store {
329+
StoreOp::Store => wgpu::StoreOp::Store,
330+
StoreOp::Discard => wgpu::StoreOp::Discard,
331+
},
332+
},
333+
DepthLoadOp::Clear(value) => wgpu::Operations {
334+
load: wgpu::LoadOp::Clear(value),
335+
store: match dop.store {
336+
StoreOp::Store => wgpu::StoreOp::Store,
337+
StoreOp::Discard => wgpu::StoreOp::Discard,
338+
},
339+
},
340+
}),
341+
stencil_ops: None,
342+
}
343+
});
344+
291345
let desc: wgpu::RenderPassDescriptor<'view> = wgpu::RenderPassDescriptor {
292346
label: self.config.label.as_deref(),
293347
color_attachments: attachments.as_slice(),
294-
depth_stencil_attachment: None,
348+
depth_stencil_attachment,
295349
timestamp_writes: None,
296350
occlusion_query_set: None,
297351
};

0 commit comments

Comments
 (0)