simple_wgpu/
render_pipeline.rs

1use wgpu::PipelineCompilationOptions;
2
3use crate::{
4    bind_group::BindGroup, context::Context, draw_call::RasteriserState,
5    pipeline_layout::PipelineLayout, shader::EntryPoint,
6};
7
8/// Describes the layout of a vertex buffer
9///
10/// Equivalent to [wgpu::VertexBufferLayout]
11#[derive(Clone, Hash, PartialEq, Eq, Debug)]
12pub struct VertexBufferLayout {
13    pub array_stride: wgpu::BufferAddress,
14    pub step_mode: wgpu::VertexStepMode,
15    pub attributes: Vec<wgpu::VertexAttribute>,
16}
17
18/// Sets blend modes and color masks for a render target
19///
20/// Loosely equivalent to [wgpu::ColorTargetState]
21#[derive(Clone, Default, Hash, PartialEq, Eq, Debug)]
22pub struct ColorTargetState {
23    pub blend: Option<wgpu::BlendState>,
24    pub write_mask: wgpu::ColorWrites,
25}
26
27/// A render pipeline
28///
29/// Loosely equivalent to [wgpu::RenderPipeline],
30/// but minus some state that is easier to handle dynamically
31#[derive(Clone, Debug)]
32pub struct RenderPipeline {
33    vertex: (EntryPoint, Vec<VertexBufferLayout>),
34    fragment: Option<(EntryPoint, Vec<Option<ColorTargetState>>)>,
35    label: Option<String>,
36}
37
38#[derive(Clone, Hash, PartialEq, Eq)]
39pub(crate) struct RenderPipelineCacheKey {
40    layout: PipelineLayout,
41    vertex: (EntryPoint, Vec<VertexBufferLayout>),
42    fragment: Option<(EntryPoint, Vec<Option<ColorTargetState>>)>,
43    rasteriser_state: RasteriserState,
44}
45
46impl RenderPipeline {
47    pub(crate) fn get_or_build(
48        &self,
49        color_formats: &[wgpu::TextureFormat],
50        depth_format: Option<wgpu::TextureFormat>,
51        multisample: &Option<wgpu::MultisampleState>,
52        rasteriser_state: &RasteriserState,
53        bind_groups: &[BindGroup],
54        context: &Context,
55    ) -> wgpu::RenderPipeline {
56        let layout = PipelineLayout {
57            bind_group_layouts: bind_groups.iter().map(|b| b.build_layout()).collect(),
58        };
59
60        let mut pipeline_cache = context.caches.render_pipeline_cache.borrow_mut();
61
62        let key = RenderPipelineCacheKey {
63            layout: layout.clone(),
64            vertex: self.vertex.clone(),
65            fragment: self.fragment.clone(),
66            rasteriser_state: rasteriser_state.clone(),
67        };
68
69        pipeline_cache
70            .get_or_insert_with(key, || {
71                let layout = layout.get_or_build(context);
72
73                let buffers = self
74                    .vertex
75                    .1
76                    .iter()
77                    .map(|b| wgpu::VertexBufferLayout {
78                        array_stride: b.array_stride,
79                        step_mode: b.step_mode,
80                        attributes: &b.attributes,
81                    })
82                    .collect::<Vec<_>>();
83
84                let targets = if let Some((_, targets)) = &self.fragment {
85                    targets
86                        .iter()
87                        .zip(color_formats.iter())
88                        .map(|(t, f)| {
89                            t.as_ref().map(|t| wgpu::ColorTargetState {
90                                format: *f,
91                                blend: t.blend,
92                                write_mask: t.write_mask,
93                            })
94                        })
95                        .collect::<Vec<_>>()
96                } else {
97                    vec![]
98                };
99
100                context
101                    .device()
102                    .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
103                        label: self.label.as_deref(),
104                        layout: Some(&layout),
105                        primitive: wgpu::PrimitiveState {
106                            front_face: rasteriser_state.front_face,
107                            cull_mode: rasteriser_state.cull_mode,
108                            polygon_mode: rasteriser_state.polygon_mode,
109                            ..Default::default()
110                        },
111                        vertex: wgpu::VertexState {
112                            module: &self.vertex.0.shader,
113                            entry_point: Some(&self.vertex.0.entry_point),
114                            buffers: &buffers,
115                            compilation_options: PipelineCompilationOptions::default(),
116                        },
117                        fragment: self.fragment.as_ref().map(|(entry_point, _)| {
118                            wgpu::FragmentState {
119                                module: &entry_point.shader,
120                                entry_point: Some(&entry_point.entry_point),
121                                targets: &targets,
122                                compilation_options: PipelineCompilationOptions::default(),
123                            }
124                        }),
125                        depth_stencil: depth_format.map(|format| wgpu::DepthStencilState {
126                            format,
127                            depth_compare: rasteriser_state.depth_compare,
128                            depth_write_enabled: rasteriser_state.depth_write,
129                            stencil: Default::default(),
130                            bias: Default::default(),
131                        }),
132                        multisample: multisample.unwrap_or_default(),
133                        multiview: None,
134                        cache: None,
135                    })
136            })
137            .clone()
138    }
139}
140
141/// Builds a [RenderPipeline]
142#[derive(Clone)]
143pub struct RenderPipelineBuilder {
144    vertex: (EntryPoint, Vec<VertexBufferLayout>),
145    fragment: Option<(EntryPoint, Vec<Option<ColorTargetState>>)>,
146    label: Option<String>,
147}
148
149impl RenderPipelineBuilder {
150    pub fn with_vertex<I>(entry_point: &EntryPoint, vertex_buffer_layout: I) -> Self
151    where
152        I: Into<Vec<VertexBufferLayout>>,
153    {
154        Self {
155            vertex: (entry_point.clone(), vertex_buffer_layout.into()),
156            fragment: None,
157            label: None,
158        }
159    }
160
161    pub fn vertex<I>(mut self, entry_point: &EntryPoint, vertex_buffer_layout: I) -> Self
162    where
163        I: Into<Vec<VertexBufferLayout>>,
164    {
165        self.vertex = (entry_point.clone(), vertex_buffer_layout.into());
166        self
167    }
168
169    pub fn fragment<I>(mut self, entry_point: &EntryPoint, targets: I) -> Self
170    where
171        I: Into<Vec<Option<ColorTargetState>>>,
172    {
173        self.fragment = Some((entry_point.clone(), targets.into()));
174        self
175    }
176
177    pub fn no_fragment(mut self) -> Self {
178        self.fragment = None;
179        self
180    }
181
182    /// Set the optional debug name. This may appear in error messages and GPU profiler traces
183    pub fn label(mut self, label: &str) -> Self {
184        self.label = Some(label.into());
185        self
186    }
187
188    pub fn build(self) -> RenderPipeline {
189        RenderPipeline {
190            vertex: self.vertex,
191            fragment: self.fragment,
192            label: self.label,
193        }
194    }
195}