Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ba6e707
[add] initial spec for textures and samplers.
vmarcella Oct 30, 2025
549f0a1
[remove] docs from ignore.
vmarcella Oct 30, 2025
b6865b4
[add] enums and helpers for mapping wgpu types.
vmarcella Oct 30, 2025
6851576
[add] texture builder and texture implementations.
vmarcella Oct 30, 2025
d040ebe
[add] texture sampler API in both platform and lambda-rs crates.
vmarcella Oct 31, 2025
02a57cd
[add] 2D textured quad demo.
vmarcella Oct 31, 2025
33777ca
[fix] quad size.
vmarcella Oct 31, 2025
2d6e9ef
[fix] quad centering.
vmarcella Oct 31, 2025
d77334a
[add] integration tests.
vmarcella Oct 31, 2025
af14117
[add] integration tests.
vmarcella Oct 31, 2025
a1bc465
[add] support for 3d textures.
vmarcella Nov 1, 2025
aa27df5
[add] 3D example prototype (Still needs refining).
vmarcella Nov 1, 2025
4e02687
Merge branch 'main' into vmarcella/textures
vmarcella Nov 7, 2025
eb0cda6
Merge branch 'main' into vmarcella/textures
vmarcella Nov 8, 2025
dd8e54d
[fix] textured_cube demo.
vmarcella Nov 9, 2025
ffdca15
[update] cube to rotate around the x & y axis.
vmarcella Nov 10, 2025
9c98647
[fix] missing faces.
vmarcella Nov 10, 2025
d9b4e0a
[fix] brightness calculations.
vmarcella Nov 10, 2025
fc5eb52
[remove] textured volume demo.
vmarcella Nov 10, 2025
a3a92ce
[add] tutorial for textured_quad example.
vmarcella Nov 10, 2025
727dbe9
[update] step descriptions.
vmarcella Nov 10, 2025
fe79756
[add] textured_cube tutorial and update tutorial links.
vmarcella Nov 10, 2025
70bfaff
[update] specification to match current state of texture implementation.
vmarcella Nov 10, 2025
7f4307b
[update] tutorials to include conclusions.
vmarcella Nov 10, 2025
37e02b4
[update] pipelines to support headless builds on ubuntu.
vmarcella Nov 11, 2025
ad764ad
[remove] redundant cast.
vmarcella Nov 11, 2025
966bebf
[fix] redundant --
vmarcella Nov 11, 2025
03e1104
[remove] redundant field.
vmarcella Nov 11, 2025
c37e14c
[add] return statements.
vmarcella Nov 11, 2025
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
11 changes: 10 additions & 1 deletion .github/workflows/compile_lambda_rs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,16 @@ jobs:
sudo apt-get install -y \
pkg-config libx11-dev libxcb1-dev libxcb-render0-dev \
libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev \
libwayland-dev libudev-dev libvulkan-dev
libwayland-dev libudev-dev \
libvulkan-dev libvulkan1 mesa-vulkan-drivers vulkan-tools

- name: Configure Vulkan (Ubuntu)
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
echo "WGPU_BACKEND=vulkan" >> "$GITHUB_ENV"
# Prefer Mesa's software Vulkan (lavapipe) to ensure headless availability
echo "VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" >> "$GITHUB_ENV"
vulkaninfo --summary || true

# Windows runners already include the required toolchain for DX12 builds.

Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ jobs:
- name: Rust cache
uses: Swatinem/rust-cache@v2

- name: Install Linux deps for winit/wgpu
run: |
sudo apt-get update
sudo apt-get install -y \
pkg-config libx11-dev libxcb1-dev libxcb-render0-dev \
libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev \
libwayland-dev libudev-dev \
libvulkan-dev libvulkan1 mesa-vulkan-drivers vulkan-tools

- name: Configure Vulkan for headless CI
run: |
echo "WGPU_BACKEND=vulkan" >> "$GITHUB_ENV"
echo "VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" >> "$GITHUB_ENV"
vulkaninfo --summary || true

- name: Format check
run: cargo fmt --all -- --check

Expand Down
105 changes: 105 additions & 0 deletions crates/lambda-rs-platform/src/wgpu/bind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,35 @@ mod tests {
);
assert_eq!(Visibility::All.to_wgpu(), wgpu::ShaderStages::all());
}

#[test]
fn sampled_texture_2d_layout_entry_is_correct() {
let builder = BindGroupLayoutBuilder::new()
.with_sampled_texture_2d(1, Visibility::Fragment)
.with_sampler(2, Visibility::Fragment);
assert_eq!(builder.entries.len(), 2);
match builder.entries[0].ty {
wgpu::BindingType::Texture {
sample_type,
view_dimension,
multisampled,
} => {
assert_eq!(view_dimension, wgpu::TextureViewDimension::D2);
assert_eq!(multisampled, false);
match sample_type {
wgpu::TextureSampleType::Float { filterable } => assert!(filterable),
_ => panic!("expected float sample type"),
}
}
_ => panic!("expected texture binding type"),
}
match builder.entries[1].ty {
wgpu::BindingType::Sampler(kind) => {
assert_eq!(kind, wgpu::SamplerBindingType::Filtering);
}
_ => panic!("expected sampler binding type"),
}
}
}

/// Builder for creating a `wgpu::BindGroupLayout`.
Expand Down Expand Up @@ -155,6 +184,56 @@ impl BindGroupLayoutBuilder {
return self;
}

/// Declare a sampled texture binding (2D) at the provided index.
pub fn with_sampled_texture_2d(
mut self,
binding: u32,
visibility: Visibility,
) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding,
visibility: visibility.to_wgpu(),
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
});
return self;
}

/// Declare a sampled texture binding with an explicit view dimension.
pub fn with_sampled_texture_dim(
mut self,
binding: u32,
visibility: Visibility,
view_dimension: crate::wgpu::texture::ViewDimension,
) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding,
visibility: visibility.to_wgpu(),
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: view_dimension.to_wgpu(),
multisampled: false,
},
count: None,
});
return self;
}

/// Declare a filtering sampler binding at the provided index.
pub fn with_sampler(mut self, binding: u32, visibility: Visibility) -> Self {
self.entries.push(wgpu::BindGroupLayoutEntry {
binding,
visibility: visibility.to_wgpu(),
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
});
return self;
}

/// Build the layout using the provided device.
pub fn build(self, gpu: &Gpu) -> BindGroupLayout {
let raw =
Expand Down Expand Up @@ -220,6 +299,32 @@ impl<'a> BindGroupBuilder<'a> {
return self;
}

/// Bind a texture view at a binding index.
pub fn with_texture(
mut self,
binding: u32,
texture: &'a crate::wgpu::texture::Texture,
) -> Self {
self.entries.push(wgpu::BindGroupEntry {
binding,
resource: wgpu::BindingResource::TextureView(texture.view()),
});
return self;
}

/// Bind a sampler at a binding index.
pub fn with_sampler(
mut self,
binding: u32,
sampler: &'a crate::wgpu::texture::Sampler,
) -> Self {
self.entries.push(wgpu::BindGroupEntry {
binding,
resource: wgpu::BindingResource::Sampler(sampler.raw()),
});
return self;
}

/// Build the bind group with the accumulated entries.
pub fn build(self, gpu: &Gpu) -> BindGroup {
let layout = self
Expand Down
11 changes: 5 additions & 6 deletions crates/lambda-rs-platform/src/wgpu/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
//! Cross‑platform GPU abstraction built on top of `wgpu`.
//!
//! This module exposes a small, opinionated wrapper around core `wgpu` types
//! to make engine code concise while keeping configuration explicit. The
//! builders here (for the instance, surface, and device/queue) provide sane
//! defaults and narrow the surface area used by Lambda, without hiding
//! important handles when you need to drop down to raw `wgpu`.

// keep this module focused on exports and submodules
//! organized into focused submodules (instance, surface, gpu, pipeline, etc.).
//! Higher layers import these modules rather than raw `wgpu` to keep Lambda’s
//! API compact and stable.

// Keep this module focused on exports and submodules only.
pub mod bind;
pub mod buffer;
pub mod command;
Expand All @@ -16,4 +14,5 @@ pub mod instance;
pub mod pipeline;
pub mod render_pass;
pub mod surface;
pub mod texture;
pub mod vertex;
19 changes: 18 additions & 1 deletion crates/lambda-rs-platform/src/wgpu/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::wgpu::{
bind,
gpu::Gpu,
surface::SurfaceFormat,
texture::DepthFormat,
vertex::ColorFormat,
};

Expand Down Expand Up @@ -210,6 +211,7 @@ pub struct RenderPipelineBuilder<'a> {
vertex_buffers: Vec<(u64, Vec<VertexAttributeDesc>)>,
cull_mode: CullingMode,
color_target_format: Option<wgpu::TextureFormat>,
depth_stencil: Option<wgpu::DepthStencilState>,
}

impl<'a> RenderPipelineBuilder<'a> {
Expand All @@ -221,6 +223,7 @@ impl<'a> RenderPipelineBuilder<'a> {
vertex_buffers: Vec::new(),
cull_mode: CullingMode::Back,
color_target_format: None,
depth_stencil: None,
};
}

Expand Down Expand Up @@ -258,6 +261,20 @@ impl<'a> RenderPipelineBuilder<'a> {
return self;
}

/// Enable depth testing/writes using the provided depth format and default compare/write settings.
///
/// Defaults: compare Less, depth writes enabled, no stencil.
pub fn with_depth_stencil(mut self, format: DepthFormat) -> Self {
self.depth_stencil = Some(wgpu::DepthStencilState {
format: format.to_wgpu(),
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
});
return self;
}

/// Build the render pipeline from provided shader modules.
pub fn build(
self,
Expand Down Expand Up @@ -333,7 +350,7 @@ impl<'a> RenderPipelineBuilder<'a> {
layout: layout_ref,
vertex: vertex_state,
primitive: primitive_state,
depth_stencil: None,
depth_stencil: self.depth_stencil,
multisample: wgpu::MultisampleState::default(),
fragment,
multiview: None,
Expand Down
56 changes: 55 additions & 1 deletion crates/lambda-rs-platform/src/wgpu/render_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ impl Default for ColorOperations {
}
}

/// Depth load operation for a depth attachment.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DepthLoadOp {
/// Load the existing contents of the depth attachment.
Load,
/// Clear the depth attachment to the provided value in [0,1].
Clear(f32),
}

/// Depth operations (load/store) for the depth attachment.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DepthOperations {
pub load: DepthLoadOp,
pub store: StoreOp,
}

impl Default for DepthOperations {
fn default() -> Self {
return Self {
load: DepthLoadOp::Clear(1.0),
store: StoreOp::Store,
};
}
}

/// Configuration for beginning a render pass.
#[derive(Clone, Debug, Default)]
pub struct RenderPassConfig {
Expand Down Expand Up @@ -255,13 +280,17 @@ impl RenderPassBuilder {
return self;
}

// Depth attachment is supplied at build time by the caller.

/// Build (begin) the render pass on the provided encoder using the provided
/// color attachments list. The attachments list MUST outlive the returned
/// render pass value.
pub fn build<'view>(
&'view self,
encoder: &'view mut command::CommandEncoder,
attachments: &'view mut RenderColorAttachments<'view>,
depth_view: Option<crate::wgpu::surface::TextureViewRef<'view>>,
depth_ops: Option<DepthOperations>,
) -> RenderPass<'view> {
let operations = match self.config.color_operations.load {
ColorLoadOp::Load => wgpu::Operations {
Expand All @@ -288,10 +317,35 @@ impl RenderPassBuilder {
// Apply operations to all provided attachments.
attachments.set_operations_for_all(operations);

// Optional depth attachment
let depth_stencil_attachment = depth_view.map(|v| {
let dop = depth_ops.unwrap_or_default();
wgpu::RenderPassDepthStencilAttachment {
view: v.raw,
depth_ops: Some(match dop.load {
DepthLoadOp::Load => wgpu::Operations {
load: wgpu::LoadOp::Load,
store: match dop.store {
StoreOp::Store => wgpu::StoreOp::Store,
StoreOp::Discard => wgpu::StoreOp::Discard,
},
},
DepthLoadOp::Clear(value) => wgpu::Operations {
load: wgpu::LoadOp::Clear(value),
store: match dop.store {
StoreOp::Store => wgpu::StoreOp::Store,
StoreOp::Discard => wgpu::StoreOp::Discard,
},
},
}),
stencil_ops: None,
}
});

let desc: wgpu::RenderPassDescriptor<'view> = wgpu::RenderPassDescriptor {
label: self.config.label.as_deref(),
color_attachments: attachments.as_slice(),
depth_stencil_attachment: None,
depth_stencil_attachment,
timestamp_writes: None,
occlusion_query_set: None,
};
Expand Down
Loading