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
161 changes: 161 additions & 0 deletions crates/lambda-rs-platform/src/wgpu/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
//! Buffer wrappers and builders for the platform layer.
//!
//! This module provides a thin wrapper over `wgpu::Buffer` plus a small
//! builder that handles common initialization patterns and keeps label and
//! usage metadata for debugging/inspection.
use crate::wgpu::{
types as wgpu,
types::util::DeviceExt,
};

#[derive(Clone, Copy, Debug)]
/// Platform buffer usage flags.
pub struct Usage(pub(crate) wgpu::BufferUsages);

impl Usage {
/// Vertex buffer usage.
pub const VERTEX: Usage = Usage(wgpu::BufferUsages::VERTEX);
/// Index buffer usage.
pub const INDEX: Usage = Usage(wgpu::BufferUsages::INDEX);
/// Uniform buffer usage.
pub const UNIFORM: Usage = Usage(wgpu::BufferUsages::UNIFORM);
/// Storage buffer usage.
pub const STORAGE: Usage = Usage(wgpu::BufferUsages::STORAGE);
/// Copy destination (for CPU-visible uploads).
pub const COPY_DST: Usage = Usage(wgpu::BufferUsages::COPY_DST);

pub(crate) fn to_wgpu(self) -> wgpu::BufferUsages {
return self.0;
Copy link

Copilot AI Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Unnecessary return statement. Consider removing return to follow idiomatic Rust style: self.0

Suggested change
return self.0;
self.0

Copilot uses AI. Check for mistakes.
}
}

impl std::ops::BitOr for Usage {
type Output = Usage;
fn bitor(self, rhs: Usage) -> Usage {
return Usage(self.0 | rhs.0);
Copy link

Copilot AI Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Unnecessary return statement. Consider removing return to follow idiomatic Rust style: Usage(self.0 | rhs.0)

Suggested change
return Usage(self.0 | rhs.0);
Usage(self.0 | rhs.0)

Copilot uses AI. Check for mistakes.
}
}

impl Default for Usage {
fn default() -> Self {
return Usage(wgpu::BufferUsages::VERTEX);
Copy link

Copilot AI Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Unnecessary return statement. Consider removing return to follow idiomatic Rust style: Usage(wgpu::BufferUsages::VERTEX)

Suggested change
return Usage(wgpu::BufferUsages::VERTEX);
Usage(wgpu::BufferUsages::VERTEX)

Copilot uses AI. Check for mistakes.
}
}

#[derive(Debug)]
/// Wrapper around `wgpu::Buffer` with metadata.
pub struct Buffer {
pub(crate) raw: wgpu::Buffer,
pub(crate) label: Option<String>,
pub(crate) size: wgpu::BufferAddress,
pub(crate) usage: wgpu::BufferUsages,
}

impl Buffer {
/// Borrow the underlying `wgpu::Buffer`.
pub fn raw(&self) -> &wgpu::Buffer {
return &self.raw;
Copy link

Copilot AI Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Unnecessary return statement. Consider removing return to follow idiomatic Rust style: &self.raw

Suggested change
return &self.raw;
&self.raw

Copilot uses AI. Check for mistakes.
}

/// Optional debug label.
pub fn label(&self) -> Option<&str> {
return self.label.as_deref();
}

/// Size in bytes at creation time.
pub fn size(&self) -> wgpu::BufferAddress {
return self.size;
}

/// Usage flags used to create the buffer.
pub fn usage(&self) -> wgpu::BufferUsages {
return self.usage;
}
}

#[derive(Default)]
/// Builder for creating a `Buffer` with optional initial contents.
pub struct BufferBuilder {
label: Option<String>,
size: usize,
usage: Usage,
cpu_visible: bool,
}

impl BufferBuilder {
/// Create a new builder with zero size and VERTEX usage.
pub fn new() -> Self {
return Self {
label: None,
size: 0,
usage: Usage::VERTEX,
cpu_visible: false,
};
}

/// Attach a label for debugging/profiling.
pub fn with_label(mut self, label: &str) -> Self {
self.label = Some(label.to_string());
return self;
}

/// Set the total size in bytes. If zero, size is inferred from data length.
pub fn with_size(mut self, size: usize) -> Self {
self.size = size;
return self;
}

/// Set usage flags.
pub fn with_usage(mut self, usage: Usage) -> Self {
self.usage = usage;
return self;
}

/// Hint that buffer will be updated from CPU via queue writes.
pub fn with_cpu_visible(mut self, cpu_visible: bool) -> Self {
self.cpu_visible = cpu_visible;
return self;
}

/// Create a buffer initialized with `contents`.
pub fn build_init(self, device: &wgpu::Device, contents: &[u8]) -> Buffer {
let size = if self.size == 0 {
contents.len()
} else {
self.size
};

let mut usage = self.usage.to_wgpu();
if self.cpu_visible {
usage |= wgpu::BufferUsages::COPY_DST;
}

let raw = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: self.label.as_deref(),
contents,
usage,
});

return Buffer {
raw,
label: self.label,
size: size as wgpu::BufferAddress,
usage,
};
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn usage_bitor_combines_flags() {
let u = Usage::VERTEX | Usage::INDEX | Usage::UNIFORM;
let flags = u.to_wgpu();
assert!(flags.contains(wgpu::BufferUsages::VERTEX));
assert!(flags.contains(wgpu::BufferUsages::INDEX));
assert!(flags.contains(wgpu::BufferUsages::UNIFORM));
}
}
112 changes: 112 additions & 0 deletions crates/lambda-rs-platform/src/wgpu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use wgpu::rwh::{
use crate::winit::WindowHandle;

pub mod bind;
pub mod buffer;

#[derive(Debug, Clone)]
/// Builder for creating a `wgpu::Instance` with consistent defaults.
Expand Down Expand Up @@ -342,6 +343,117 @@ impl Frame {
}
}

// ---------------------- Command Encoding Abstractions -----------------------

#[derive(Debug)]
/// Thin wrapper around `wgpu::CommandEncoder` with convenience helpers.
pub struct CommandEncoder {
raw: wgpu::CommandEncoder,
}

impl CommandEncoder {
/// Create a new command encoder with an optional label.
pub fn new(device: &wgpu::Device, label: Option<&str>) -> Self {
let raw =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label });
return Self { raw };
}

/// Begin a render pass targeting a single color attachment with the provided
/// load/store operations. Depth/stencil is not attached by this helper.
pub fn begin_render_pass<'view>(
&'view mut self,
label: Option<&str>,
view: &'view wgpu::TextureView,
ops: wgpu::Operations<wgpu::Color>,
) -> RenderPass<'view> {
let color_attachment = wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
depth_slice: None,
ops,
};
let color_attachments = [Some(color_attachment)];
let pass = self.raw.begin_render_pass(&wgpu::RenderPassDescriptor {
label,
color_attachments: &color_attachments,
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
return RenderPass { raw: pass };
Copy link

Copilot AI Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Unnecessary return statement. Consider removing return to follow idiomatic Rust style: RenderPass { raw: pass }

Suggested change
return RenderPass { raw: pass };
RenderPass { raw: pass }

Copilot uses AI. Check for mistakes.
}

/// Finish recording and return the command buffer.
pub fn finish(self) -> wgpu::CommandBuffer {
return self.raw.finish();
Copy link

Copilot AI Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Unnecessary return statement. Consider removing return to follow idiomatic Rust style: self.raw.finish()

Suggested change
return self.raw.finish();
self.raw.finish()

Copilot uses AI. Check for mistakes.
}
}

#[derive(Debug)]
/// Wrapper around `wgpu::RenderPass<'_>` exposing the operations needed by the
/// Lambda renderer without leaking raw `wgpu` types at the call sites.
pub struct RenderPass<'a> {
raw: wgpu::RenderPass<'a>,
}

impl<'a> RenderPass<'a> {
/// Set the active render pipeline.
pub fn set_pipeline(&mut self, pipeline: &wgpu::RenderPipeline) {
self.raw.set_pipeline(pipeline);
}

/// Apply viewport state.
pub fn set_viewport(
&mut self,
x: f32,
y: f32,
width: f32,
height: f32,
min_depth: f32,
max_depth: f32,
) {
self
.raw
.set_viewport(x, y, width, height, min_depth, max_depth);
}

/// Apply scissor rectangle.
pub fn set_scissor_rect(&mut self, x: u32, y: u32, width: u32, height: u32) {
self.raw.set_scissor_rect(x, y, width, height);
}

/// Bind a group with optional dynamic offsets.
pub fn set_bind_group(
&mut self,
set: u32,
group: &wgpu::BindGroup,
dynamic_offsets: &[u32],
) {
self.raw.set_bind_group(set, group, dynamic_offsets);
}

/// Bind a vertex buffer slot.
pub fn set_vertex_buffer(&mut self, slot: u32, buffer: &wgpu::Buffer) {
self.raw.set_vertex_buffer(slot, buffer.slice(..));
}

/// Upload push constants.
pub fn set_push_constants(
&mut self,
stages: wgpu::ShaderStages,
offset: u32,
data: &[u8],
) {
self.raw.set_push_constants(stages, offset, data);
}

/// Issue a non-indexed draw over a vertex range.
pub fn draw(&mut self, vertices: std::ops::Range<u32>) {
self.raw.draw(vertices, 0..1);
}
}

#[derive(Debug, Clone)]
/// Builder for a `Gpu` (adapter, device, queue) with feature validation.
pub struct GpuBuilder {
Expand Down
Loading