From 3a8ffb975ad67351cea7f7d5ee288e8452929382 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 23 Jan 2026 15:32:44 -0800 Subject: [PATCH 1/5] [add] write_slice and write_bytes to the buffer. --- crates/lambda-rs/src/render/buffer.rs | 89 ++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/crates/lambda-rs/src/render/buffer.rs b/crates/lambda-rs/src/render/buffer.rs index 2294469e..b56864a3 100644 --- a/crates/lambda-rs/src/render/buffer.rs +++ b/crates/lambda-rs/src/render/buffer.rs @@ -142,15 +142,67 @@ impl Buffer { /// byte offset. This is intended for updating uniform buffer contents from /// the CPU. The `data` type must be trivially copyable. pub fn write_value(&self, gpu: &Gpu, offset: u64, data: &T) { - let bytes = unsafe { - std::slice::from_raw_parts( - (data as *const T) as *const u8, - std::mem::size_of::(), - ) - }; + let bytes = value_as_bytes(data); + self.write_bytes(gpu, offset, bytes); + } - self.buffer.write_bytes(gpu.platform(), offset, bytes); + /// Write raw bytes into this buffer at the specified byte offset. + /// + /// This is useful when data is already available as a byte slice (for + /// example, asset blobs or staging buffers). + /// + /// Example + /// ```rust,ignore + /// let raw_data: &[u8] = load_binary_data(); + /// buffer.write_bytes(render_context.gpu(), 0, raw_data); + /// ``` + pub fn write_bytes(&self, gpu: &Gpu, offset: u64, data: &[u8]) { + self.buffer.write_bytes(gpu.platform(), offset, data); } + + /// Write a slice of plain-old-data values into this buffer at the + /// specified byte offset. + /// + /// This is intended for uploading arrays of vertices, indices, instance + /// data, or uniform blocks. The `T` type MUST be plain-old-data (POD) and + /// safely representable as bytes. + /// + /// Example + /// ```rust,ignore + /// let transforms: Vec = compute_transforms(); + /// instance_buffer.write_slice(render_context.gpu(), 0, &transforms); + /// ``` + pub fn write_slice(&self, gpu: &Gpu, offset: u64, data: &[T]) { + let bytes = slice_as_bytes(data); + self.write_bytes(gpu, offset, bytes); + } +} + +fn value_as_bytes(data: &T) -> &[u8] { + let bytes = unsafe { + std::slice::from_raw_parts( + (data as *const T) as *const u8, + std::mem::size_of::(), + ) + }; + return bytes; +} + +fn slice_as_bytes(data: &[T]) -> &[u8] { + let element_size = std::mem::size_of::(); + let Some(byte_len) = element_size.checked_mul(data.len()) else { + debug_assert!( + false, + "Buffer::write_slice byte length overflow: element_size={}, len={}", + element_size, + data.len() + ); + return &[]; + }; + + let bytes = + unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) }; + return bytes; } /// Strongly‑typed uniform buffer wrapper for ergonomics and safety. @@ -375,4 +427,27 @@ mod tests { // Test module is a child of this module and can access private fields. assert_eq!(builder.label.as_deref(), Some("buffer-test")); } + + #[test] + fn value_as_bytes_matches_native_bytes() { + let value: u32 = 0x1122_3344; + let expected = value.to_ne_bytes(); + assert_eq!(value_as_bytes(&value), expected.as_slice()); + } + + #[test] + fn slice_as_bytes_matches_native_bytes() { + let values: [u16; 3] = [0x1122, 0x3344, 0x5566]; + let mut expected: Vec = Vec::new(); + for value in values { + expected.extend_from_slice(&value.to_ne_bytes()); + } + assert_eq!(slice_as_bytes(&values), expected.as_slice()); + } + + #[test] + fn slice_as_bytes_empty_is_empty() { + let values: [u32; 0] = []; + assert_eq!(slice_as_bytes(&values), &[]); + } } From 51e91af368b4f29ff91e35cdc4171bd3936ab2e6 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 23 Jan 2026 17:07:24 -0800 Subject: [PATCH 2/5] [update] write_slice to return a result. --- crates/lambda-rs/src/render/buffer.rs | 57 +++++++++++++++++++-------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/crates/lambda-rs/src/render/buffer.rs b/crates/lambda-rs/src/render/buffer.rs index b56864a3..2aa90d43 100644 --- a/crates/lambda-rs/src/render/buffer.rs +++ b/crates/lambda-rs/src/render/buffer.rs @@ -170,11 +170,19 @@ impl Buffer { /// Example /// ```rust,ignore /// let transforms: Vec = compute_transforms(); - /// instance_buffer.write_slice(render_context.gpu(), 0, &transforms); + /// instance_buffer + /// .write_slice(render_context.gpu(), 0, &transforms) + /// .unwrap(); /// ``` - pub fn write_slice(&self, gpu: &Gpu, offset: u64, data: &[T]) { - let bytes = slice_as_bytes(data); + pub fn write_slice( + &self, + gpu: &Gpu, + offset: u64, + data: &[T], + ) -> Result<(), &'static str> { + let bytes = slice_as_bytes(data)?; self.write_bytes(gpu, offset, bytes); + return Ok(()); } } @@ -188,21 +196,23 @@ fn value_as_bytes(data: &T) -> &[u8] { return bytes; } -fn slice_as_bytes(data: &[T]) -> &[u8] { - let element_size = std::mem::size_of::(); - let Some(byte_len) = element_size.checked_mul(data.len()) else { - debug_assert!( - false, - "Buffer::write_slice byte length overflow: element_size={}, len={}", - element_size, - data.len() - ); - return &[]; +fn checked_byte_len( + element_size: usize, + element_count: usize, +) -> Result { + let Some(byte_len) = element_size.checked_mul(element_count) else { + return Err("Buffer byte length overflow."); }; + return Ok(byte_len); +} + +fn slice_as_bytes(data: &[T]) -> Result<&[u8], &'static str> { + let element_size = std::mem::size_of::(); + let byte_len = checked_byte_len(element_size, data.len())?; let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) }; - return bytes; + return Ok(bytes); } /// Strongly‑typed uniform buffer wrapper for ergonomics and safety. @@ -398,7 +408,7 @@ impl BufferBuilder { data_len: usize, ) -> Result { let buffer_length = if self.buffer_length == 0 { - element_size * data_len + checked_byte_len(element_size, data_len)? } else { self.buffer_length }; @@ -428,6 +438,13 @@ mod tests { assert_eq!(builder.label.as_deref(), Some("buffer-test")); } + #[test] + fn resolve_length_rejects_overflow() { + let builder = BufferBuilder::new(); + let result = builder.resolve_length(usize::MAX, 2); + assert!(result.is_err()); + } + #[test] fn value_as_bytes_matches_native_bytes() { let value: u32 = 0x1122_3344; @@ -442,12 +459,18 @@ mod tests { for value in values { expected.extend_from_slice(&value.to_ne_bytes()); } - assert_eq!(slice_as_bytes(&values), expected.as_slice()); + assert_eq!(slice_as_bytes(&values).unwrap(), expected.as_slice()); } #[test] fn slice_as_bytes_empty_is_empty() { let values: [u32; 0] = []; - assert_eq!(slice_as_bytes(&values), &[]); + assert_eq!(slice_as_bytes(&values).unwrap(), &[]); + } + + #[test] + fn checked_byte_len_rejects_overflow() { + let result = checked_byte_len(usize::MAX, 2); + assert!(result.is_err()); } } From df476b77e1f2a17818869c3218cf223ab935c456 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 23 Jan 2026 17:36:21 -0800 Subject: [PATCH 3/5] [add] plain old data type for making the semantics around writing data more clear and update examples. --- .../examples/indexed_multi_vertex_buffers.rs | 4 ++ crates/lambda-rs/examples/instanced_quads.rs | 4 ++ .../examples/uniform_buffer_triangle.rs | 2 + crates/lambda-rs/src/render/buffer.rs | 67 +++++++++++++++---- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs b/crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs index 51c13970..901256d3 100644 --- a/crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs +++ b/crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs @@ -89,12 +89,16 @@ struct PositionVertex { position: [f32; 3], } +unsafe impl lambda::render::buffer::PlainOldData for PositionVertex {} + #[repr(C)] #[derive(Clone, Copy, Debug)] struct ColorVertex { color: [f32; 3], } +unsafe impl lambda::render::buffer::PlainOldData for ColorVertex {} + // --------------------------------- COMPONENT --------------------------------- pub struct IndexedMultiBufferExample { diff --git a/crates/lambda-rs/examples/instanced_quads.rs b/crates/lambda-rs/examples/instanced_quads.rs index 2a72c86a..c442dd7c 100644 --- a/crates/lambda-rs/examples/instanced_quads.rs +++ b/crates/lambda-rs/examples/instanced_quads.rs @@ -92,6 +92,8 @@ struct QuadVertex { position: [f32; 3], } +unsafe impl lambda::render::buffer::PlainOldData for QuadVertex {} + #[repr(C)] #[derive(Clone, Copy, Debug)] struct InstanceData { @@ -99,6 +101,8 @@ struct InstanceData { color: [f32; 3], } +unsafe impl lambda::render::buffer::PlainOldData for InstanceData {} + // --------------------------------- COMPONENT --------------------------------- /// Component that renders a grid of instanced quads. diff --git a/crates/lambda-rs/examples/uniform_buffer_triangle.rs b/crates/lambda-rs/examples/uniform_buffer_triangle.rs index 52e86b9e..d00ae10f 100644 --- a/crates/lambda-rs/examples/uniform_buffer_triangle.rs +++ b/crates/lambda-rs/examples/uniform_buffer_triangle.rs @@ -101,6 +101,8 @@ pub struct GlobalsUniform { pub render_matrix: [[f32; 4]; 4], } +unsafe impl lambda::render::buffer::PlainOldData for GlobalsUniform {} + // --------------------------------- COMPONENT --------------------------------- pub struct UniformBufferExample { diff --git a/crates/lambda-rs/src/render/buffer.rs b/crates/lambda-rs/src/render/buffer.rs index 2aa90d43..748026bd 100644 --- a/crates/lambda-rs/src/render/buffer.rs +++ b/crates/lambda-rs/src/render/buffer.rs @@ -28,6 +28,43 @@ use super::{ RenderContext, }; +/// Marker trait for types that are safe to reinterpret as raw bytes. +/// +/// This trait is required by `Buffer::write_value`, `Buffer::write_slice`, and +/// `BufferBuilder::build` because those APIs upload the in-memory representation +/// of a value to the GPU. +/// +/// # Safety +/// Types implementing `PlainOldData` MUST satisfy all of the following: +/// - Every byte of the value is initialized (including any padding bytes). +/// - The type has no pointers or references that would be invalidated by a +/// raw byte copy. +/// - The type's byte representation is stable for GPU consumption. Prefer +/// `#[repr(C)]` or `#[repr(transparent)]`. +/// +/// Implementing this trait incorrectly can cause undefined behavior. +pub unsafe trait PlainOldData: Copy {} + +unsafe impl PlainOldData for u8 {} +unsafe impl PlainOldData for i8 {} +unsafe impl PlainOldData for u16 {} +unsafe impl PlainOldData for i16 {} +unsafe impl PlainOldData for u32 {} +unsafe impl PlainOldData for i32 {} +unsafe impl PlainOldData for u64 {} +unsafe impl PlainOldData for i64 {} +unsafe impl PlainOldData for u128 {} +unsafe impl PlainOldData for i128 {} +unsafe impl PlainOldData for usize {} +unsafe impl PlainOldData for isize {} +unsafe impl PlainOldData for f32 {} +unsafe impl PlainOldData for f64 {} +unsafe impl PlainOldData for bool {} +unsafe impl PlainOldData for char {} +unsafe impl PlainOldData for [T; N] {} + +unsafe impl PlainOldData for super::vertex::Vertex {} + /// High‑level classification for buffers created by the engine. #[derive(Clone, Copy, Debug, PartialEq, Eq)] /// @@ -140,8 +177,8 @@ impl Buffer { /// Write a single plain-old-data value into this buffer at the specified /// byte offset. This is intended for updating uniform buffer contents from - /// the CPU. The `data` type must be trivially copyable. - pub fn write_value(&self, gpu: &Gpu, offset: u64, data: &T) { + /// the CPU. The `data` type must implement `PlainOldData`. + pub fn write_value(&self, gpu: &Gpu, offset: u64, data: &T) { let bytes = value_as_bytes(data); self.write_bytes(gpu, offset, bytes); } @@ -165,7 +202,8 @@ impl Buffer { /// /// This is intended for uploading arrays of vertices, indices, instance /// data, or uniform blocks. The `T` type MUST be plain-old-data (POD) and - /// safely representable as bytes. + /// safely representable as bytes. This is enforced by requiring `T` to + /// implement `PlainOldData`. /// /// Example /// ```rust,ignore @@ -174,7 +212,7 @@ impl Buffer { /// .write_slice(render_context.gpu(), 0, &transforms) /// .unwrap(); /// ``` - pub fn write_slice( + pub fn write_slice( &self, gpu: &Gpu, offset: u64, @@ -186,7 +224,7 @@ impl Buffer { } } -fn value_as_bytes(data: &T) -> &[u8] { +fn value_as_bytes(data: &T) -> &[u8] { let bytes = unsafe { std::slice::from_raw_parts( (data as *const T) as *const u8, @@ -206,7 +244,7 @@ fn checked_byte_len( return Ok(byte_len); } -fn slice_as_bytes(data: &[T]) -> Result<&[u8], &'static str> { +fn slice_as_bytes(data: &[T]) -> Result<&[u8], &'static str> { let element_size = std::mem::size_of::(); let byte_len = checked_byte_len(element_size, data.len())?; @@ -238,7 +276,7 @@ pub struct UniformBuffer { _phantom: core::marker::PhantomData, } -impl UniformBuffer { +impl UniformBuffer { /// Create a new uniform buffer initialized with `initial`. pub fn new( gpu: &Gpu, @@ -349,22 +387,23 @@ impl BufferBuilder { /// Create a buffer initialized with the provided `data`. /// /// Returns an error if the resolved length would be zero. - pub fn build( + /// + /// The element type MUST implement `PlainOldData` because the engine uploads + /// the in-memory representation to the GPU. + pub fn build( &self, gpu: &Gpu, data: Vec, ) -> Result { let element_size = std::mem::size_of::(); let buffer_length = self.resolve_length(element_size, data.len())?; + let byte_len = checked_byte_len(element_size, data.len())?; // SAFETY: Converting data to bytes is safe because its underlying - // type, Data, is constrained to Copy and the lifetime of the slice does - // not outlive data. + // type, Data, is constrained to PlainOldData and the lifetime of the slice + // does not outlive data. let bytes = unsafe { - std::slice::from_raw_parts( - data.as_ptr() as *const u8, - element_size * data.len(), - ) + std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len) }; let mut builder = platform_buffer::BufferBuilder::new() From d6774e1340a088a94ec1ccb10cd7ef93d2d1c8ab Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 23 Jan 2026 18:10:48 -0800 Subject: [PATCH 4/5] [move] pod to it's own module and update examples. --- .../examples/indexed_multi_vertex_buffers.rs | 4 +- crates/lambda-rs/examples/instanced_quads.rs | 4 +- .../examples/uniform_buffer_triangle.rs | 2 +- crates/lambda-rs/src/lib.rs | 1 + crates/lambda-rs/src/pod.rs | 42 +++++++++++++++++++ crates/lambda-rs/src/render/buffer.rs | 38 +---------------- crates/lambda-rs/src/render/vertex.rs | 2 + 7 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 crates/lambda-rs/src/pod.rs diff --git a/crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs b/crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs index 901256d3..5488ec9c 100644 --- a/crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs +++ b/crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs @@ -89,7 +89,7 @@ struct PositionVertex { position: [f32; 3], } -unsafe impl lambda::render::buffer::PlainOldData for PositionVertex {} +unsafe impl lambda::pod::PlainOldData for PositionVertex {} #[repr(C)] #[derive(Clone, Copy, Debug)] @@ -97,7 +97,7 @@ struct ColorVertex { color: [f32; 3], } -unsafe impl lambda::render::buffer::PlainOldData for ColorVertex {} +unsafe impl lambda::pod::PlainOldData for ColorVertex {} // --------------------------------- COMPONENT --------------------------------- diff --git a/crates/lambda-rs/examples/instanced_quads.rs b/crates/lambda-rs/examples/instanced_quads.rs index c442dd7c..3abac283 100644 --- a/crates/lambda-rs/examples/instanced_quads.rs +++ b/crates/lambda-rs/examples/instanced_quads.rs @@ -92,7 +92,7 @@ struct QuadVertex { position: [f32; 3], } -unsafe impl lambda::render::buffer::PlainOldData for QuadVertex {} +unsafe impl lambda::pod::PlainOldData for QuadVertex {} #[repr(C)] #[derive(Clone, Copy, Debug)] @@ -101,7 +101,7 @@ struct InstanceData { color: [f32; 3], } -unsafe impl lambda::render::buffer::PlainOldData for InstanceData {} +unsafe impl lambda::pod::PlainOldData for InstanceData {} // --------------------------------- COMPONENT --------------------------------- diff --git a/crates/lambda-rs/examples/uniform_buffer_triangle.rs b/crates/lambda-rs/examples/uniform_buffer_triangle.rs index d00ae10f..36669407 100644 --- a/crates/lambda-rs/examples/uniform_buffer_triangle.rs +++ b/crates/lambda-rs/examples/uniform_buffer_triangle.rs @@ -101,7 +101,7 @@ pub struct GlobalsUniform { pub render_matrix: [[f32; 4]; 4], } -unsafe impl lambda::render::buffer::PlainOldData for GlobalsUniform {} +unsafe impl lambda::pod::PlainOldData for GlobalsUniform {} // --------------------------------- COMPONENT --------------------------------- diff --git a/crates/lambda-rs/src/lib.rs b/crates/lambda-rs/src/lib.rs index 6bf6ae89..83d25f79 100644 --- a/crates/lambda-rs/src/lib.rs +++ b/crates/lambda-rs/src/lib.rs @@ -14,6 +14,7 @@ pub mod component; pub mod events; pub mod math; +pub mod pod; pub mod render; pub mod runtime; pub mod runtimes; diff --git a/crates/lambda-rs/src/pod.rs b/crates/lambda-rs/src/pod.rs new file mode 100644 index 00000000..40dccecc --- /dev/null +++ b/crates/lambda-rs/src/pod.rs @@ -0,0 +1,42 @@ +//! Plain-old-data marker trait for safe byte uploads. +//! +//! The engine frequently uploads CPU data into GPU buffers by reinterpreting +//! a value or slice as raw bytes. This is only sound for types that are safe +//! to view as bytes. + +/// Marker trait for types that are safe to reinterpret as raw bytes. +/// +/// This trait is required by typed buffer upload APIs (for example +/// `render::buffer::Buffer::write_value` and `render::buffer::Buffer::write_slice`) +/// and typed buffer creation APIs (for example +/// `render::buffer::BufferBuilder::build`) because those operations upload the +/// in-memory representation of a value to the GPU. +/// +/// # Safety +/// Types implementing `PlainOldData` MUST satisfy all of the following: +/// - Every byte of the value is initialized (including any padding bytes). +/// - The type has no pointers or references that would be invalidated by a +/// raw byte copy. +/// - The type's byte representation is stable for GPU consumption. Prefer +/// `#[repr(C)]` or `#[repr(transparent)]`. +/// +/// Implementing this trait incorrectly can cause undefined behavior. +pub unsafe trait PlainOldData: Copy {} + +unsafe impl PlainOldData for u8 {} +unsafe impl PlainOldData for i8 {} +unsafe impl PlainOldData for u16 {} +unsafe impl PlainOldData for i16 {} +unsafe impl PlainOldData for u32 {} +unsafe impl PlainOldData for i32 {} +unsafe impl PlainOldData for u64 {} +unsafe impl PlainOldData for i64 {} +unsafe impl PlainOldData for u128 {} +unsafe impl PlainOldData for i128 {} +unsafe impl PlainOldData for usize {} +unsafe impl PlainOldData for isize {} +unsafe impl PlainOldData for f32 {} +unsafe impl PlainOldData for f64 {} +unsafe impl PlainOldData for bool {} +unsafe impl PlainOldData for char {} +unsafe impl PlainOldData for [T; N] {} diff --git a/crates/lambda-rs/src/render/buffer.rs b/crates/lambda-rs/src/render/buffer.rs index 748026bd..aaea1903 100644 --- a/crates/lambda-rs/src/render/buffer.rs +++ b/crates/lambda-rs/src/render/buffer.rs @@ -27,43 +27,7 @@ use super::{ mesh::Mesh, RenderContext, }; - -/// Marker trait for types that are safe to reinterpret as raw bytes. -/// -/// This trait is required by `Buffer::write_value`, `Buffer::write_slice`, and -/// `BufferBuilder::build` because those APIs upload the in-memory representation -/// of a value to the GPU. -/// -/// # Safety -/// Types implementing `PlainOldData` MUST satisfy all of the following: -/// - Every byte of the value is initialized (including any padding bytes). -/// - The type has no pointers or references that would be invalidated by a -/// raw byte copy. -/// - The type's byte representation is stable for GPU consumption. Prefer -/// `#[repr(C)]` or `#[repr(transparent)]`. -/// -/// Implementing this trait incorrectly can cause undefined behavior. -pub unsafe trait PlainOldData: Copy {} - -unsafe impl PlainOldData for u8 {} -unsafe impl PlainOldData for i8 {} -unsafe impl PlainOldData for u16 {} -unsafe impl PlainOldData for i16 {} -unsafe impl PlainOldData for u32 {} -unsafe impl PlainOldData for i32 {} -unsafe impl PlainOldData for u64 {} -unsafe impl PlainOldData for i64 {} -unsafe impl PlainOldData for u128 {} -unsafe impl PlainOldData for i128 {} -unsafe impl PlainOldData for usize {} -unsafe impl PlainOldData for isize {} -unsafe impl PlainOldData for f32 {} -unsafe impl PlainOldData for f64 {} -unsafe impl PlainOldData for bool {} -unsafe impl PlainOldData for char {} -unsafe impl PlainOldData for [T; N] {} - -unsafe impl PlainOldData for super::vertex::Vertex {} +pub use crate::pod::PlainOldData; /// High‑level classification for buffers created by the engine. #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/lambda-rs/src/render/vertex.rs b/crates/lambda-rs/src/render/vertex.rs index 4ce9b214..57519ab0 100644 --- a/crates/lambda-rs/src/render/vertex.rs +++ b/crates/lambda-rs/src/render/vertex.rs @@ -78,6 +78,8 @@ pub struct Vertex { pub color: [f32; 3], } +unsafe impl crate::pod::PlainOldData for Vertex {} + /// Builder for constructing a `Vertex` instance incrementally. #[derive(Clone, Copy, Debug)] pub struct VertexBuilder { From c8cd4265775b6953c80fe5199defa809560404d7 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Fri, 23 Jan 2026 18:11:12 -0800 Subject: [PATCH 5/5] [update] tutorials. --- .../indexed-draws-and-multiple-vertex-buffers.md | 14 ++++++++++---- docs/tutorials/instanced-quads.md | 14 ++++++++++---- docs/tutorials/uniform-buffers.md | 12 ++++++++---- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/tutorials/indexed-draws-and-multiple-vertex-buffers.md b/docs/tutorials/indexed-draws-and-multiple-vertex-buffers.md index a6725d1b..961a7187 100644 --- a/docs/tutorials/indexed-draws-and-multiple-vertex-buffers.md +++ b/docs/tutorials/indexed-draws-and-multiple-vertex-buffers.md @@ -3,13 +3,13 @@ title: "Indexed Draws and Multiple Vertex Buffers" document_id: "indexed-draws-multiple-vertex-buffers-tutorial-2025-11-22" status: "draft" created: "2025-11-22T00:00:00Z" -last_updated: "2026-01-16T00:00:00Z" -version: "0.3.1" +last_updated: "2026-01-24T00:00:00Z" +version: "0.3.3" engine_workspace_version: "2023.1.30" wgpu_version: "26.0.1" shader_backend_default: "naga" winit_version: "0.29.10" -repo_commit: "9435ad1491b5930054117406abe08dd1c37f2102" +repo_commit: "df476b77e1f2a17818869c3218cf223ab935c456" owners: ["lambda-sh"] reviewers: ["engine", "rendering"] tags: ["tutorial", "graphics", "indexed-draws", "vertex-buffers", "rust", "wgpu"] @@ -123,14 +123,18 @@ struct PositionVertex { position: [f32; 3], } +unsafe impl lambda::pod::PlainOldData for PositionVertex {} + #[repr(C)] #[derive(Clone, Copy, Debug)] struct ColorVertex { color: [f32; 3], } + +unsafe impl lambda::pod::PlainOldData for ColorVertex {} ``` -The shader `location` qualifiers match the vertex buffer layouts declared on the pipeline, and the `PositionVertex` and `ColorVertex` types mirror the `vec3` inputs as `[f32; 3]` arrays in Rust. +The shader `location` qualifiers match the vertex buffer layouts declared on the pipeline, and the `PositionVertex` and `ColorVertex` types mirror the `vec3` inputs as `[f32; 3]` arrays in Rust. The `PlainOldData` implementations mark the types as safe for `BufferBuilder` uploads. ### Step 2 — Component State and Shader Construction @@ -498,6 +502,8 @@ This tutorial demonstrates how indexed draws and multiple vertex buffers combine ## Changelog +- 2026-01-24 (v0.3.3) — Move `PlainOldData` to `lambda::pod::PlainOldData`. +- 2026-01-24 (v0.3.2) — Add `PlainOldData` requirements for typed buffer data. - 2026-01-16 (v0.3.1) — Update resize handling examples to use `event_mask()` and `on_window_event`. - 2025-12-15 (v0.3.0) — Update builder API calls to use `render_context.gpu()` and add `surface_format`/`depth_format` parameters to `RenderPassBuilder` and `RenderPipelineBuilder`. - 2025-11-23 (v0.2.0) — Filled in the implementation steps for the indexed draws and multiple vertex buffers tutorial and aligned the narrative with the `indexed_multi_vertex_buffers` example. diff --git a/docs/tutorials/instanced-quads.md b/docs/tutorials/instanced-quads.md index b42ee3be..60e29acf 100644 --- a/docs/tutorials/instanced-quads.md +++ b/docs/tutorials/instanced-quads.md @@ -3,13 +3,13 @@ title: "Instanced Rendering: Grid of Colored Quads" document_id: "instanced-quads-tutorial-2025-11-25" status: "draft" created: "2025-11-25T00:00:00Z" -last_updated: "2026-01-16T00:00:00Z" -version: "0.2.1" +last_updated: "2026-01-24T00:00:00Z" +version: "0.2.3" engine_workspace_version: "2023.1.30" wgpu_version: "26.0.1" shader_backend_default: "naga" winit_version: "0.29.10" -repo_commit: "9435ad1491b5930054117406abe08dd1c37f2102" +repo_commit: "df476b77e1f2a17818869c3218cf223ab935c456" owners: ["lambda-sh"] reviewers: ["engine", "rendering"] tags: ["tutorial", "graphics", "instancing", "vertex-buffers", "rust", "wgpu"] @@ -156,6 +156,8 @@ struct QuadVertex { position: [f32; 3], } +unsafe impl lambda::pod::PlainOldData for QuadVertex {} + #[repr(C)] #[derive(Clone, Copy, Debug)] struct InstanceData { @@ -163,6 +165,8 @@ struct InstanceData { color: [f32; 3], } +unsafe impl lambda::pod::PlainOldData for InstanceData {} + pub struct InstancedQuadsExample { vertex_shader: Shader, fragment_shader: Shader, @@ -210,7 +214,7 @@ impl Default for InstancedQuadsExample { } ``` -The `QuadVertex` and `InstanceData` structures mirror the GLSL inputs as arrays of `f32`, and the component tracks resource identifiers and counts that are populated during attachment. The `Default` implementation constructs shader objects from the GLSL source so that the component is ready to build a pipeline when it receives a `RenderContext`. +The `QuadVertex` and `InstanceData` structures mirror the GLSL inputs as arrays of `f32`, and the component tracks resource identifiers and counts that are populated during attachment. The `PlainOldData` implementations mark the types as safe for `BufferBuilder` uploads, which reinterpret values as raw bytes when initializing GPU buffers. The `Default` implementation constructs shader objects from the GLSL source so that the component is ready to build a pipeline when it receives a `RenderContext`. ### Step 3 — Render Pass, Geometry, Instances, and Buffers @@ -510,6 +514,8 @@ This tutorial demonstrates how the `lambda-rs` crate uses per-vertex and per-ins ## Changelog +- 2026-01-24 (v0.2.3) — Move `PlainOldData` to `lambda::pod::PlainOldData`. +- 2026-01-24 (v0.2.2) — Add `PlainOldData` requirements for typed buffer data. - 2026-01-16 (v0.2.1) — Update resize handling examples to use `event_mask()` and `on_window_event`. - 2025-12-15 (v0.2.0) — Update builder API calls to use `render_context.gpu()` and add `surface_format`/`depth_format` parameters to `RenderPassBuilder` and `RenderPipelineBuilder`. - 2025-11-25 (v0.1.1) — Align feature naming with `render-validation-instancing` and update metadata. diff --git a/docs/tutorials/uniform-buffers.md b/docs/tutorials/uniform-buffers.md index 46d55751..47b089a9 100644 --- a/docs/tutorials/uniform-buffers.md +++ b/docs/tutorials/uniform-buffers.md @@ -3,13 +3,13 @@ title: "Uniform Buffers: Build a Spinning Triangle" document_id: "uniform-buffers-tutorial-2025-10-17" status: "draft" created: "2025-10-17T00:00:00Z" -last_updated: "2026-01-16T00:00:00Z" -version: "0.5.1" +last_updated: "2026-01-24T00:00:00Z" +version: "0.5.3" engine_workspace_version: "2023.1.30" wgpu_version: "26.0.1" shader_backend_default: "naga" winit_version: "0.29.10" -repo_commit: "9435ad1491b5930054117406abe08dd1c37f2102" +repo_commit: "df476b77e1f2a17818869c3218cf223ab935c456" owners: ["lambda-sh"] reviewers: ["engine", "rendering"] tags: ["tutorial", "graphics", "uniform-buffers", "rust", "wgpu"] @@ -222,7 +222,7 @@ let mesh: Mesh = mesh_builder ### Step 4 — Uniform Data Layout in Rust -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. +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. `PlainOldData` MUST be implemented so the engine can upload the uniform by reinterpreting its bytes. 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. ```rust #[repr(C)] @@ -230,6 +230,8 @@ Mirror the shader’s uniform block with a Rust structure. Use `#[repr(C)]` so t pub struct GlobalsUniform { pub render_matrix: [[f32; 4]; 4], } + +unsafe impl lambda::pod::PlainOldData for GlobalsUniform {} ``` ### Step 5 — Bind Group Layout at Set 0 @@ -443,6 +445,8 @@ multiple objects and passes. ## Changelog +- 0.5.3 (2026-01-24): Move `PlainOldData` to `lambda::pod::PlainOldData`. +- 0.5.2 (2026-01-24): Add `PlainOldData` requirements for uniform buffer data. - 0.5.1 (2026-01-16): Replace `on_event` resize handling with `event_mask()` and `on_window_event`. - 0.5.0 (2025-12-15): Update builder API calls to use `render_context.gpu()` and add `surface_format`/`depth_format` parameters to `RenderPassBuilder` and `RenderPipelineBuilder`. - 0.4.1 (2025‑11‑10): Add Conclusion section summarizing accomplishments; update