Skip to content

Commit 5ee2f0e

Browse files
committed
[add] render pipeline abstraction.
1 parent baa4ca8 commit 5ee2f0e

File tree

3 files changed

+281
-79
lines changed

3 files changed

+281
-79
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::winit::WindowHandle;
1717

1818
pub mod bind;
1919
pub mod buffer;
20+
pub mod pipeline;
2021

2122
#[derive(Debug, Clone)]
2223
/// Builder for creating a `wgpu::Instance` with consistent defaults.
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
//! Pipeline and shader module wrappers/builders for the platform layer.
2+
3+
use crate::wgpu::types as wgpu;
4+
5+
#[derive(Debug)]
6+
/// Wrapper around `wgpu::ShaderModule` that preserves a label.
7+
pub struct ShaderModule {
8+
raw: wgpu::ShaderModule,
9+
label: Option<String>,
10+
}
11+
12+
impl ShaderModule {
13+
/// Create a shader module from SPIR-V words.
14+
pub fn from_spirv(
15+
device: &wgpu::Device,
16+
words: &[u32],
17+
label: Option<&str>,
18+
) -> Self {
19+
let raw = device.create_shader_module(wgpu::ShaderModuleDescriptor {
20+
label,
21+
source: wgpu::ShaderSource::SpirV(std::borrow::Cow::Borrowed(words)),
22+
});
23+
return Self {
24+
raw,
25+
label: label.map(|s| s.to_string()),
26+
};
27+
}
28+
29+
/// Borrow the raw shader module.
30+
pub fn raw(&self) -> &wgpu::ShaderModule {
31+
&self.raw
32+
}
33+
}
34+
35+
#[derive(Debug)]
36+
/// Wrapper around `wgpu::PipelineLayout`.
37+
pub struct PipelineLayout {
38+
raw: wgpu::PipelineLayout,
39+
label: Option<String>,
40+
}
41+
42+
impl PipelineLayout {
43+
/// Borrow the raw pipeline layout.
44+
pub fn raw(&self) -> &wgpu::PipelineLayout {
45+
&self.raw
46+
}
47+
}
48+
49+
/// Builder for creating a `PipelineLayout`.
50+
pub struct PipelineLayoutBuilder<'a> {
51+
label: Option<String>,
52+
layouts: Vec<&'a wgpu::BindGroupLayout>,
53+
push_constant_ranges: Vec<wgpu::PushConstantRange>,
54+
}
55+
56+
impl<'a> PipelineLayoutBuilder<'a> {
57+
/// New builder with no layouts or push constants.
58+
pub fn new() -> Self {
59+
return Self {
60+
label: None,
61+
layouts: Vec::new(),
62+
push_constant_ranges: Vec::new(),
63+
};
64+
}
65+
66+
/// Attach a label.
67+
pub fn with_label(mut self, label: &str) -> Self {
68+
self.label = Some(label.to_string());
69+
self
70+
}
71+
72+
/// Provide bind group layouts.
73+
pub fn with_layouts(mut self, layouts: &'a [&wgpu::BindGroupLayout]) -> Self {
74+
self.layouts = layouts.to_vec();
75+
self
76+
}
77+
78+
/// Provide push constant ranges.
79+
pub fn with_push_constants(
80+
mut self,
81+
ranges: Vec<wgpu::PushConstantRange>,
82+
) -> Self {
83+
self.push_constant_ranges = ranges;
84+
self
85+
}
86+
87+
/// Build the layout.
88+
pub fn build(self, device: &wgpu::Device) -> PipelineLayout {
89+
let raw = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
90+
label: self.label.as_deref(),
91+
bind_group_layouts: &self.layouts,
92+
push_constant_ranges: &self.push_constant_ranges,
93+
});
94+
return PipelineLayout {
95+
raw,
96+
label: self.label,
97+
};
98+
}
99+
}
100+
101+
#[derive(Debug)]
102+
/// Wrapper around `wgpu::RenderPipeline`.
103+
pub struct RenderPipeline {
104+
raw: wgpu::RenderPipeline,
105+
label: Option<String>,
106+
}
107+
108+
impl RenderPipeline {
109+
/// Borrow the raw pipeline.
110+
pub fn raw(&self) -> &wgpu::RenderPipeline {
111+
&self.raw
112+
}
113+
/// Consume and return the raw pipeline.
114+
pub fn into_raw(self) -> wgpu::RenderPipeline {
115+
self.raw
116+
}
117+
}
118+
119+
/// Builder for creating a graphics render pipeline.
120+
pub struct RenderPipelineBuilder<'a> {
121+
label: Option<String>,
122+
layout: Option<&'a wgpu::PipelineLayout>,
123+
vertex_buffers: Vec<(u64, Vec<wgpu::VertexAttribute>)>,
124+
cull_mode: Option<wgpu::Face>,
125+
color_target: Option<wgpu::ColorTargetState>,
126+
}
127+
128+
impl<'a> RenderPipelineBuilder<'a> {
129+
/// New builder with defaults.
130+
pub fn new() -> Self {
131+
return Self {
132+
label: None,
133+
layout: None,
134+
vertex_buffers: Vec::new(),
135+
cull_mode: Some(wgpu::Face::Back),
136+
color_target: None,
137+
};
138+
}
139+
140+
/// Attach a label.
141+
pub fn with_label(mut self, label: &str) -> Self {
142+
self.label = Some(label.to_string());
143+
self
144+
}
145+
146+
/// Use the provided pipeline layout.
147+
pub fn with_layout(mut self, layout: &'a PipelineLayout) -> Self {
148+
self.layout = Some(layout.raw());
149+
self
150+
}
151+
152+
/// Add a vertex buffer layout with attributes.
153+
pub fn with_vertex_buffer(
154+
mut self,
155+
array_stride: u64,
156+
attributes: Vec<wgpu::VertexAttribute>,
157+
) -> Self {
158+
self.vertex_buffers.push((array_stride, attributes));
159+
self
160+
}
161+
162+
/// Set cull mode (None disables culling).
163+
pub fn with_cull_mode(mut self, face: Option<wgpu::Face>) -> Self {
164+
self.cull_mode = face;
165+
self
166+
}
167+
168+
/// Set single color target for fragment stage.
169+
pub fn with_color_target(mut self, target: wgpu::ColorTargetState) -> Self {
170+
self.color_target = Some(target);
171+
self
172+
}
173+
174+
/// Build the render pipeline from provided shader modules.
175+
pub fn build(
176+
self,
177+
device: &wgpu::Device,
178+
vertex_shader: &ShaderModule,
179+
fragment_shader: Option<&ShaderModule>,
180+
) -> RenderPipeline {
181+
let mut attr_storage: Vec<Box<[wgpu::VertexAttribute]>> = Vec::new();
182+
let mut strides: Vec<u64> = Vec::new();
183+
for (stride, attrs) in &self.vertex_buffers {
184+
let boxed: Box<[wgpu::VertexAttribute]> =
185+
attrs.clone().into_boxed_slice();
186+
attr_storage.push(boxed);
187+
strides.push(*stride);
188+
}
189+
// Now build layouts referencing the stable storage in `attr_storage`.
190+
let mut vbl: Vec<wgpu::VertexBufferLayout<'_>> = Vec::new();
191+
for (i, boxed) in attr_storage.iter().enumerate() {
192+
let slice = boxed.as_ref();
193+
vbl.push(wgpu::VertexBufferLayout {
194+
array_stride: strides[i],
195+
step_mode: wgpu::VertexStepMode::Vertex,
196+
attributes: slice,
197+
});
198+
}
199+
200+
let color_targets: Vec<Option<wgpu::ColorTargetState>> =
201+
match &self.color_target {
202+
Some(ct) => vec![Some(ct.clone())],
203+
None => Vec::new(),
204+
};
205+
206+
let fragment = fragment_shader.map(|fs| wgpu::FragmentState {
207+
module: fs.raw(),
208+
entry_point: Some("main"),
209+
compilation_options: wgpu::PipelineCompilationOptions::default(),
210+
targets: color_targets.as_slice(),
211+
});
212+
213+
let vertex_state = wgpu::VertexState {
214+
module: vertex_shader.raw(),
215+
entry_point: Some("main"),
216+
compilation_options: wgpu::PipelineCompilationOptions::default(),
217+
buffers: vbl.as_slice(),
218+
};
219+
220+
let primitive_state = wgpu::PrimitiveState {
221+
cull_mode: self.cull_mode,
222+
..wgpu::PrimitiveState::default()
223+
};
224+
225+
let layout_ref = self.layout;
226+
let raw = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
227+
label: self.label.as_deref(),
228+
layout: layout_ref,
229+
vertex: vertex_state,
230+
primitive: primitive_state,
231+
depth_stencil: None,
232+
multisample: wgpu::MultisampleState::default(),
233+
fragment,
234+
multiview: None,
235+
cache: None,
236+
});
237+
238+
return RenderPipeline {
239+
raw,
240+
label: self.label,
241+
};
242+
}
243+
}

0 commit comments

Comments
 (0)