Skip to main content

sable_gpu/
pipeline.rs

1//! Render pipeline builder and management.
2
3use wgpu::{
4    BlendState, ColorTargetState, ColorWrites, Device, Face, FragmentState, FrontFace,
5    MultisampleState, PipelineCompilationOptions, PipelineLayoutDescriptor, PolygonMode,
6    PrimitiveState, PrimitiveTopology, RenderPipelineDescriptor, TextureFormat, VertexState,
7};
8
9use crate::shader::Shader;
10use crate::vertex::Vertex;
11
12/// A render pipeline wrapping wgpu's render pipeline.
13#[derive(Debug)]
14pub struct RenderPipeline {
15    inner: wgpu::RenderPipeline,
16}
17
18impl RenderPipeline {
19    /// Get a reference to the underlying wgpu render pipeline.
20    #[must_use]
21    pub fn inner(&self) -> &wgpu::RenderPipeline {
22        &self.inner
23    }
24}
25
26/// Builder for creating render pipelines.
27pub struct PipelineBuilder<'a> {
28    device: &'a Device,
29    shader: Option<&'a Shader>,
30    vertex_entry: &'a str,
31    fragment_entry: &'a str,
32    vertex_layouts: Vec<wgpu::VertexBufferLayout<'static>>,
33    target_format: Option<TextureFormat>,
34    blend_state: Option<BlendState>,
35    cull_mode: Option<Face>,
36    front_face: FrontFace,
37    polygon_mode: PolygonMode,
38    topology: PrimitiveTopology,
39    depth_format: Option<TextureFormat>,
40    label: Option<&'a str>,
41}
42
43impl<'a> PipelineBuilder<'a> {
44    /// Create a new pipeline builder.
45    #[must_use]
46    pub fn new(device: &'a Device) -> Self {
47        Self {
48            device,
49            shader: None,
50            vertex_entry: "vs_main",
51            fragment_entry: "fs_main",
52            vertex_layouts: Vec::new(),
53            target_format: None,
54            blend_state: None,
55            cull_mode: None,
56            front_face: FrontFace::Ccw,
57            polygon_mode: PolygonMode::Fill,
58            topology: PrimitiveTopology::TriangleList,
59            depth_format: None,
60            label: None,
61        }
62    }
63
64    /// Set the shader module.
65    #[must_use]
66    pub fn shader(mut self, shader: &'a Shader) -> Self {
67        self.shader = Some(shader);
68        self
69    }
70
71    /// Set the vertex shader entry point.
72    #[must_use]
73    pub fn vertex_entry(mut self, entry: &'a str) -> Self {
74        self.vertex_entry = entry;
75        self
76    }
77
78    /// Set the fragment shader entry point.
79    #[must_use]
80    pub fn fragment_entry(mut self, entry: &'a str) -> Self {
81        self.fragment_entry = entry;
82        self
83    }
84
85    /// Add a vertex buffer layout using a vertex type.
86    #[must_use]
87    pub fn vertex<V: Vertex>(mut self) -> Self {
88        self.vertex_layouts.push(V::layout());
89        self
90    }
91
92    /// Set the render target format.
93    #[must_use]
94    pub fn target_format(mut self, format: TextureFormat) -> Self {
95        self.target_format = Some(format);
96        self
97    }
98
99    /// Set the blend state.
100    #[must_use]
101    pub fn blend(mut self, blend: BlendState) -> Self {
102        self.blend_state = Some(blend);
103        self
104    }
105
106    /// Enable alpha blending.
107    #[must_use]
108    pub fn alpha_blend(mut self) -> Self {
109        self.blend_state = Some(BlendState::ALPHA_BLENDING);
110        self
111    }
112
113    /// Set the cull mode.
114    #[must_use]
115    pub fn cull_mode(mut self, mode: Option<Face>) -> Self {
116        self.cull_mode = mode;
117        self
118    }
119
120    /// Set the front face winding order.
121    #[must_use]
122    pub fn front_face(mut self, front_face: FrontFace) -> Self {
123        self.front_face = front_face;
124        self
125    }
126
127    /// Set the polygon mode (fill, line, point).
128    #[must_use]
129    pub fn polygon_mode(mut self, mode: PolygonMode) -> Self {
130        self.polygon_mode = mode;
131        self
132    }
133
134    /// Set the primitive topology.
135    #[must_use]
136    pub fn topology(mut self, topology: PrimitiveTopology) -> Self {
137        self.topology = topology;
138        self
139    }
140
141    /// Set the depth texture format.
142    #[must_use]
143    pub fn depth_format(mut self, format: TextureFormat) -> Self {
144        self.depth_format = Some(format);
145        self
146    }
147
148    /// Set the pipeline label.
149    #[must_use]
150    pub fn label(mut self, label: &'a str) -> Self {
151        self.label = Some(label);
152        self
153    }
154
155    /// Build the render pipeline.
156    ///
157    /// # Panics
158    ///
159    /// Panics if no shader or target format is set.
160    #[must_use]
161    pub fn build(self) -> RenderPipeline {
162        let shader = self.shader.expect("shader is required");
163        let target_format = self.target_format.expect("target format is required");
164
165        let pipeline_layout = self
166            .device
167            .create_pipeline_layout(&PipelineLayoutDescriptor {
168                label: self.label.map(|l| format!("{l} Layout")).as_deref(),
169                bind_group_layouts: &[],
170                push_constant_ranges: &[],
171            });
172
173        let depth_stencil = self.depth_format.map(|format| wgpu::DepthStencilState {
174            format,
175            depth_write_enabled: true,
176            depth_compare: wgpu::CompareFunction::Less,
177            stencil: wgpu::StencilState::default(),
178            bias: wgpu::DepthBiasState::default(),
179        });
180
181        let inner = self
182            .device
183            .create_render_pipeline(&RenderPipelineDescriptor {
184                label: self.label,
185                layout: Some(&pipeline_layout),
186                vertex: VertexState {
187                    module: shader.module(),
188                    entry_point: Some(self.vertex_entry),
189                    compilation_options: PipelineCompilationOptions::default(),
190                    buffers: &self.vertex_layouts,
191                },
192                fragment: Some(FragmentState {
193                    module: shader.module(),
194                    entry_point: Some(self.fragment_entry),
195                    compilation_options: PipelineCompilationOptions::default(),
196                    targets: &[Some(ColorTargetState {
197                        format: target_format,
198                        blend: self.blend_state,
199                        write_mask: ColorWrites::ALL,
200                    })],
201                }),
202                primitive: PrimitiveState {
203                    topology: self.topology,
204                    strip_index_format: None,
205                    front_face: self.front_face,
206                    cull_mode: self.cull_mode,
207                    polygon_mode: self.polygon_mode,
208                    unclipped_depth: false,
209                    conservative: false,
210                },
211                depth_stencil,
212                multisample: MultisampleState {
213                    count: 1,
214                    mask: !0,
215                    alpha_to_coverage_enabled: false,
216                },
217                multiview: None,
218                cache: None,
219            });
220
221        RenderPipeline { inner }
222    }
223}