Skip to content

Commit b1f0509

Browse files
committed
[add] vertex step mode and layout mappings.
1 parent 84c73fb commit b1f0509

5 files changed

Lines changed: 228 additions & 45 deletions

File tree

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

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::ops::Range;
44

55
use wgpu;
66

7+
pub use crate::wgpu::vertex::VertexStepMode;
78
use crate::wgpu::{
89
bind,
910
gpu::Gpu,
@@ -81,6 +82,14 @@ pub struct VertexAttributeDesc {
8182
pub format: ColorFormat,
8283
}
8384

85+
/// Description of a single vertex buffer layout used by a pipeline.
86+
#[derive(Clone, Debug)]
87+
struct VertexBufferLayoutDesc {
88+
array_stride: u64,
89+
step_mode: VertexStepMode,
90+
attributes: Vec<VertexAttributeDesc>,
91+
}
92+
8493
/// Compare function used for depth and stencil tests.
8594
#[derive(Clone, Copy, Debug)]
8695
pub enum CompareFunction {
@@ -301,7 +310,7 @@ impl RenderPipeline {
301310
pub struct RenderPipelineBuilder<'a> {
302311
label: Option<String>,
303312
layout: Option<&'a wgpu::PipelineLayout>,
304-
vertex_buffers: Vec<(u64, Vec<VertexAttributeDesc>)>,
313+
vertex_buffers: Vec<VertexBufferLayoutDesc>,
305314
cull_mode: CullingMode,
306315
color_target_format: Option<wgpu::TextureFormat>,
307316
depth_stencil: Option<wgpu::DepthStencilState>,
@@ -340,7 +349,26 @@ impl<'a> RenderPipelineBuilder<'a> {
340349
array_stride: u64,
341350
attributes: Vec<VertexAttributeDesc>,
342351
) -> Self {
343-
self.vertex_buffers.push((array_stride, attributes));
352+
self = self.with_vertex_buffer_step_mode(
353+
array_stride,
354+
VertexStepMode::Vertex,
355+
attributes,
356+
);
357+
return self;
358+
}
359+
360+
/// Add a vertex buffer layout with attributes and an explicit step mode.
361+
pub fn with_vertex_buffer_step_mode(
362+
mut self,
363+
array_stride: u64,
364+
step_mode: VertexStepMode,
365+
attributes: Vec<VertexAttributeDesc>,
366+
) -> Self {
367+
self.vertex_buffers.push(VertexBufferLayoutDesc {
368+
array_stride,
369+
step_mode,
370+
attributes,
371+
});
344372
return self;
345373
}
346374

@@ -431,11 +459,12 @@ impl<'a> RenderPipelineBuilder<'a> {
431459
// storage stable for layout lifetimes.
432460
let mut attr_storage: Vec<Box<[wgpu::VertexAttribute]>> = Vec::new();
433461
let mut strides: Vec<u64> = Vec::new();
434-
for (stride, attrs) in &self.vertex_buffers {
462+
let mut step_modes: Vec<VertexStepMode> = Vec::new();
463+
for buffer_desc in &self.vertex_buffers {
435464
let mut raw_attrs: Vec<wgpu::VertexAttribute> =
436-
Vec::with_capacity(attrs.len());
465+
Vec::with_capacity(buffer_desc.attributes.len());
437466

438-
for attribute in attrs.iter() {
467+
for attribute in buffer_desc.attributes.iter() {
439468
raw_attrs.push(wgpu::VertexAttribute {
440469
shader_location: attribute.shader_location,
441470
offset: attribute.offset,
@@ -444,15 +473,16 @@ impl<'a> RenderPipelineBuilder<'a> {
444473
}
445474
let boxed: Box<[wgpu::VertexAttribute]> = raw_attrs.into_boxed_slice();
446475
attr_storage.push(boxed);
447-
strides.push(*stride);
476+
strides.push(buffer_desc.array_stride);
477+
step_modes.push(buffer_desc.step_mode);
448478
}
449479
// Now build layouts referencing the stable storage in `attr_storage`.
450480
let mut vbl: Vec<wgpu::VertexBufferLayout<'_>> = Vec::new();
451481
for (i, boxed) in attr_storage.iter().enumerate() {
452482
let slice = boxed.as_ref();
453483
vbl.push(wgpu::VertexBufferLayout {
454484
array_stride: strides[i],
455-
step_mode: wgpu::VertexStepMode::Vertex,
485+
step_mode: step_modes[i].to_wgpu(),
456486
attributes: slice,
457487
});
458488
}
@@ -511,3 +541,37 @@ impl<'a> RenderPipelineBuilder<'a> {
511541
};
512542
}
513543
}
544+
545+
#[cfg(test)]
546+
mod tests {
547+
use super::*;
548+
549+
#[test]
550+
fn vertex_step_mode_maps_to_wgpu() {
551+
let vertex_mode = VertexStepMode::Vertex.to_wgpu();
552+
let instance_mode = VertexStepMode::Instance.to_wgpu();
553+
554+
assert_eq!(vertex_mode, wgpu::VertexStepMode::Vertex);
555+
assert_eq!(instance_mode, wgpu::VertexStepMode::Instance);
556+
}
557+
558+
#[test]
559+
fn with_vertex_buffer_defaults_to_per_vertex_step_mode() {
560+
let builder = RenderPipelineBuilder::new().with_vertex_buffer(
561+
16,
562+
vec![VertexAttributeDesc {
563+
shader_location: 0,
564+
offset: 0,
565+
format: ColorFormat::Rgb32Sfloat,
566+
}],
567+
);
568+
569+
let vertex_buffers = &builder.vertex_buffers;
570+
571+
assert_eq!(vertex_buffers.len(), 1);
572+
assert!(matches!(
573+
vertex_buffers[0].step_mode,
574+
VertexStepMode::Vertex
575+
));
576+
}
577+
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,24 @@ impl ColorFormat {
2626
};
2727
}
2828
}
29+
30+
/// Step mode applied to a vertex buffer layout.
31+
///
32+
/// `Vertex` advances attributes per vertex; `Instance` advances attributes per
33+
/// instance. This mirrors `wgpu::VertexStepMode` without exposing the raw
34+
/// dependency to higher layers.
35+
#[derive(Clone, Copy, Debug)]
36+
pub enum VertexStepMode {
37+
Vertex,
38+
Instance,
39+
}
40+
41+
impl VertexStepMode {
42+
/// Map the engine step mode to the underlying graphics API.
43+
pub(crate) fn to_wgpu(self) -> wgpu::VertexStepMode {
44+
return match self {
45+
VertexStepMode::Vertex => wgpu::VertexStepMode::Vertex,
46+
VertexStepMode::Instance => wgpu::VertexStepMode::Instance,
47+
};
48+
}
49+
}

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

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ use super::{
4545
render_pass::RenderPass,
4646
shader::Shader,
4747
texture,
48-
vertex::VertexAttribute,
48+
vertex::{
49+
VertexAttribute,
50+
VertexBufferLayout,
51+
VertexStepMode,
52+
},
4953
RenderContext,
5054
};
5155
use crate::render::validation;
@@ -106,6 +110,7 @@ pub type PushConstantUpload = (PipelineStage, Range<u32>);
106110

107111
struct BufferBinding {
108112
buffer: Rc<Buffer>,
113+
layout: VertexBufferLayout,
109114
attributes: Vec<VertexAttribute>,
110115
}
111116

@@ -159,6 +164,15 @@ impl CullingMode {
159164
}
160165
}
161166

167+
fn to_platform_step_mode(
168+
step_mode: VertexStepMode,
169+
) -> platform_pipeline::VertexStepMode {
170+
return match step_mode {
171+
VertexStepMode::PerVertex => platform_pipeline::VertexStepMode::Vertex,
172+
VertexStepMode::PerInstance => platform_pipeline::VertexStepMode::Instance,
173+
};
174+
}
175+
162176
/// Engine-level stencil operation.
163177
#[derive(Clone, Copy, Debug)]
164178
pub enum StencilOperation {
@@ -265,9 +279,23 @@ impl RenderPipelineBuilder {
265279

266280
/// Declare a vertex buffer and the vertex attributes consumed by the shader.
267281
pub fn with_buffer(
282+
self,
283+
buffer: Buffer,
284+
attributes: Vec<VertexAttribute>,
285+
) -> Self {
286+
return self.with_buffer_step_mode(
287+
buffer,
288+
attributes,
289+
VertexStepMode::PerVertex,
290+
);
291+
}
292+
293+
/// Declare a vertex buffer with an explicit step mode.
294+
pub fn with_buffer_step_mode(
268295
mut self,
269296
buffer: Buffer,
270297
attributes: Vec<VertexAttribute>,
298+
step_mode: VertexStepMode,
271299
) -> Self {
272300
#[cfg(any(debug_assertions, feature = "render-validation-encoder",))]
273301
{
@@ -278,13 +306,32 @@ impl RenderPipelineBuilder {
278306
);
279307
}
280308
}
309+
310+
let layout = VertexBufferLayout {
311+
stride: buffer.stride(),
312+
step_mode,
313+
};
281314
self.bindings.push(BufferBinding {
282315
buffer: Rc::new(buffer),
316+
layout,
283317
attributes,
284318
});
285319
return self;
286320
}
287321

322+
/// Declare a per-instance vertex buffer.
323+
pub fn with_instance_buffer(
324+
self,
325+
buffer: Buffer,
326+
attributes: Vec<VertexAttribute>,
327+
) -> Self {
328+
return self.with_buffer_step_mode(
329+
buffer,
330+
attributes,
331+
VertexStepMode::PerInstance,
332+
);
333+
}
334+
288335
/// Declare a push constant range for a shader stage in bytes.
289336
pub fn with_push_constant(
290337
mut self,
@@ -488,8 +535,11 @@ impl RenderPipelineBuilder {
488535
})
489536
.collect();
490537

491-
rp_builder =
492-
rp_builder.with_vertex_buffer(binding.buffer.stride(), attributes);
538+
rp_builder = rp_builder.with_vertex_buffer_step_mode(
539+
binding.layout.stride,
540+
to_platform_step_mode(binding.layout.step_mode),
541+
attributes,
542+
);
493543
buffers.push(binding.buffer.clone());
494544
}
495545

@@ -594,3 +644,23 @@ impl RenderPipelineBuilder {
594644
};
595645
}
596646
}
647+
648+
#[cfg(test)]
649+
mod tests {
650+
use super::*;
651+
652+
#[test]
653+
fn engine_step_mode_maps_to_platform_step_mode() {
654+
let per_vertex = to_platform_step_mode(VertexStepMode::PerVertex);
655+
let per_instance = to_platform_step_mode(VertexStepMode::PerInstance);
656+
657+
assert!(matches!(
658+
per_vertex,
659+
platform_pipeline::VertexStepMode::Vertex
660+
));
661+
assert!(matches!(
662+
per_instance,
663+
platform_pipeline::VertexStepMode::Instance
664+
));
665+
}
666+
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,28 @@ impl ColorFormat {
2626
}
2727
}
2828

29+
/// Step mode applied to a vertex buffer layout.
30+
///
31+
/// `PerVertex` advances attributes once per vertex; `PerInstance` advances
32+
/// attributes once per instance. This mirrors the platform step mode without
33+
/// exposing backend types.
34+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35+
pub enum VertexStepMode {
36+
PerVertex,
37+
PerInstance,
38+
}
39+
40+
/// Layout for a single vertex buffer slot.
41+
///
42+
/// `stride` describes the size in bytes of one element in the buffer. The
43+
/// `step_mode` field determines whether attributes sourced from this buffer
44+
/// advance per vertex or per instance.
45+
#[derive(Clone, Copy, Debug)]
46+
pub struct VertexBufferLayout {
47+
pub stride: u64,
48+
pub step_mode: VertexStepMode,
49+
}
50+
2951
/// A single vertex element (format + byte offset).
3052
#[derive(Clone, Copy, Debug)]
3153
///

0 commit comments

Comments
 (0)