Skip to content

Commit df476b7

Browse files
committed
[add] plain old data type for making the semantics around writing data more clear and update examples.
1 parent 51e91af commit df476b7

File tree

4 files changed

+63
-14
lines changed

4 files changed

+63
-14
lines changed

crates/lambda-rs/examples/indexed_multi_vertex_buffers.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,16 @@ struct PositionVertex {
8989
position: [f32; 3],
9090
}
9191

92+
unsafe impl lambda::render::buffer::PlainOldData for PositionVertex {}
93+
9294
#[repr(C)]
9395
#[derive(Clone, Copy, Debug)]
9496
struct ColorVertex {
9597
color: [f32; 3],
9698
}
9799

100+
unsafe impl lambda::render::buffer::PlainOldData for ColorVertex {}
101+
98102
// --------------------------------- COMPONENT ---------------------------------
99103

100104
pub struct IndexedMultiBufferExample {

crates/lambda-rs/examples/instanced_quads.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,17 @@ struct QuadVertex {
9292
position: [f32; 3],
9393
}
9494

95+
unsafe impl lambda::render::buffer::PlainOldData for QuadVertex {}
96+
9597
#[repr(C)]
9698
#[derive(Clone, Copy, Debug)]
9799
struct InstanceData {
98100
offset: [f32; 3],
99101
color: [f32; 3],
100102
}
101103

104+
unsafe impl lambda::render::buffer::PlainOldData for InstanceData {}
105+
102106
// --------------------------------- COMPONENT ---------------------------------
103107

104108
/// Component that renders a grid of instanced quads.

crates/lambda-rs/examples/uniform_buffer_triangle.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ pub struct GlobalsUniform {
101101
pub render_matrix: [[f32; 4]; 4],
102102
}
103103

104+
unsafe impl lambda::render::buffer::PlainOldData for GlobalsUniform {}
105+
104106
// --------------------------------- COMPONENT ---------------------------------
105107

106108
pub struct UniformBufferExample {

crates/lambda-rs/src/render/buffer.rs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,43 @@ use super::{
2828
RenderContext,
2929
};
3030

31+
/// Marker trait for types that are safe to reinterpret as raw bytes.
32+
///
33+
/// This trait is required by `Buffer::write_value`, `Buffer::write_slice`, and
34+
/// `BufferBuilder::build` because those APIs upload the in-memory representation
35+
/// of a value to the GPU.
36+
///
37+
/// # Safety
38+
/// Types implementing `PlainOldData` MUST satisfy all of the following:
39+
/// - Every byte of the value is initialized (including any padding bytes).
40+
/// - The type has no pointers or references that would be invalidated by a
41+
/// raw byte copy.
42+
/// - The type's byte representation is stable for GPU consumption. Prefer
43+
/// `#[repr(C)]` or `#[repr(transparent)]`.
44+
///
45+
/// Implementing this trait incorrectly can cause undefined behavior.
46+
pub unsafe trait PlainOldData: Copy {}
47+
48+
unsafe impl PlainOldData for u8 {}
49+
unsafe impl PlainOldData for i8 {}
50+
unsafe impl PlainOldData for u16 {}
51+
unsafe impl PlainOldData for i16 {}
52+
unsafe impl PlainOldData for u32 {}
53+
unsafe impl PlainOldData for i32 {}
54+
unsafe impl PlainOldData for u64 {}
55+
unsafe impl PlainOldData for i64 {}
56+
unsafe impl PlainOldData for u128 {}
57+
unsafe impl PlainOldData for i128 {}
58+
unsafe impl PlainOldData for usize {}
59+
unsafe impl PlainOldData for isize {}
60+
unsafe impl PlainOldData for f32 {}
61+
unsafe impl PlainOldData for f64 {}
62+
unsafe impl PlainOldData for bool {}
63+
unsafe impl PlainOldData for char {}
64+
unsafe impl<T: PlainOldData, const N: usize> PlainOldData for [T; N] {}
65+
66+
unsafe impl PlainOldData for super::vertex::Vertex {}
67+
3168
/// High‑level classification for buffers created by the engine.
3269
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3370
///
@@ -140,8 +177,8 @@ impl Buffer {
140177

141178
/// Write a single plain-old-data value into this buffer at the specified
142179
/// byte offset. This is intended for updating uniform buffer contents from
143-
/// the CPU. The `data` type must be trivially copyable.
144-
pub fn write_value<T: Copy>(&self, gpu: &Gpu, offset: u64, data: &T) {
180+
/// the CPU. The `data` type must implement `PlainOldData`.
181+
pub fn write_value<T: PlainOldData>(&self, gpu: &Gpu, offset: u64, data: &T) {
145182
let bytes = value_as_bytes(data);
146183
self.write_bytes(gpu, offset, bytes);
147184
}
@@ -165,7 +202,8 @@ impl Buffer {
165202
///
166203
/// This is intended for uploading arrays of vertices, indices, instance
167204
/// data, or uniform blocks. The `T` type MUST be plain-old-data (POD) and
168-
/// safely representable as bytes.
205+
/// safely representable as bytes. This is enforced by requiring `T` to
206+
/// implement `PlainOldData`.
169207
///
170208
/// Example
171209
/// ```rust,ignore
@@ -174,7 +212,7 @@ impl Buffer {
174212
/// .write_slice(render_context.gpu(), 0, &transforms)
175213
/// .unwrap();
176214
/// ```
177-
pub fn write_slice<T: Copy>(
215+
pub fn write_slice<T: PlainOldData>(
178216
&self,
179217
gpu: &Gpu,
180218
offset: u64,
@@ -186,7 +224,7 @@ impl Buffer {
186224
}
187225
}
188226

189-
fn value_as_bytes<T: Copy>(data: &T) -> &[u8] {
227+
fn value_as_bytes<T: PlainOldData>(data: &T) -> &[u8] {
190228
let bytes = unsafe {
191229
std::slice::from_raw_parts(
192230
(data as *const T) as *const u8,
@@ -206,7 +244,7 @@ fn checked_byte_len(
206244
return Ok(byte_len);
207245
}
208246

209-
fn slice_as_bytes<T: Copy>(data: &[T]) -> Result<&[u8], &'static str> {
247+
fn slice_as_bytes<T: PlainOldData>(data: &[T]) -> Result<&[u8], &'static str> {
210248
let element_size = std::mem::size_of::<T>();
211249
let byte_len = checked_byte_len(element_size, data.len())?;
212250

@@ -238,7 +276,7 @@ pub struct UniformBuffer<T> {
238276
_phantom: core::marker::PhantomData<T>,
239277
}
240278

241-
impl<T: Copy> UniformBuffer<T> {
279+
impl<T: PlainOldData> UniformBuffer<T> {
242280
/// Create a new uniform buffer initialized with `initial`.
243281
pub fn new(
244282
gpu: &Gpu,
@@ -349,22 +387,23 @@ impl BufferBuilder {
349387
/// Create a buffer initialized with the provided `data`.
350388
///
351389
/// Returns an error if the resolved length would be zero.
352-
pub fn build<Data: Copy>(
390+
///
391+
/// The element type MUST implement `PlainOldData` because the engine uploads
392+
/// the in-memory representation to the GPU.
393+
pub fn build<Data: PlainOldData>(
353394
&self,
354395
gpu: &Gpu,
355396
data: Vec<Data>,
356397
) -> Result<Buffer, &'static str> {
357398
let element_size = std::mem::size_of::<Data>();
358399
let buffer_length = self.resolve_length(element_size, data.len())?;
400+
let byte_len = checked_byte_len(element_size, data.len())?;
359401

360402
// SAFETY: Converting data to bytes is safe because its underlying
361-
// type, Data, is constrained to Copy and the lifetime of the slice does
362-
// not outlive data.
403+
// type, Data, is constrained to PlainOldData and the lifetime of the slice
404+
// does not outlive data.
363405
let bytes = unsafe {
364-
std::slice::from_raw_parts(
365-
data.as_ptr() as *const u8,
366-
element_size * data.len(),
367-
)
406+
std::slice::from_raw_parts(data.as_ptr() as *const u8, byte_len)
368407
};
369408

370409
let mut builder = platform_buffer::BufferBuilder::new()

0 commit comments

Comments
 (0)