pebble/graphics/pipeline/
layout.rs1use std::cell::RefCell;
2use std::collections::HashMap;
3
4use crate::graphics::{
5 pipeline::{
6 binding::{BindGroupLayout, BindingEntry, BindingKind},
7 compute::ComputePipeline,
8 material::RenderPipeline,
9 },
10 types::{
11 Face, PolygonMode,
12 pipeline_state::{ColorTargetState, DepthStencilState, VertexBufferLayout},
13 },
14};
15
16#[derive(Clone)]
23pub enum GroupEntry {
24 Own(Vec<BindingEntry>),
25 Layout(BindGroupLayout),
26 Global(&'static str),
27}
28
29#[derive(Default)]
33pub struct OwnEntriesBuilder {
34 entries: Vec<BindingEntry>,
35 next_binding: u32,
36}
37
38impl OwnEntriesBuilder {
39 pub fn new() -> Self {
40 Self::default()
41 }
42
43 pub fn with_entry(self, name: &'static str, kind: BindingKind) -> Self {
44 let binding = self.next_binding;
45 self.with_entry_at(name, binding, kind)
46 }
47
48 pub fn with_entry_at(mut self, name: &'static str, binding: u32, kind: BindingKind) -> Self {
49 self.entries.push(BindingEntry { name, binding, kind });
50 self.next_binding = self.next_binding.max(binding + 1);
51 self
52 }
53
54 pub fn build(self) -> GroupEntry {
55 GroupEntry::Own(self.entries)
56 }
57
58 pub(crate) fn entries(&self) -> &[BindingEntry] {
63 &self.entries
64 }
65}
66
67#[derive(Default)]
72pub struct GlobalLayoutPool {
73 entries: std::collections::HashMap<&'static str, BindGroupLayout>,
74}
75
76impl GlobalLayoutPool {
77 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn register(&mut self, name: &'static str, layout: BindGroupLayout) {
83 if self.entries.insert(name, layout).is_some() {
84 panic!("global layout pool: '{name}' is already registered");
85 }
86 }
87
88 pub fn get(&self, name: &str) -> Option<BindGroupLayout> {
89 self.entries.get(name).cloned()
90 }
91
92 pub(crate) fn get_ref(&self, name: &str) -> Option<&BindGroupLayout> {
93 self.entries.get(name)
94 }
95}
96
97#[derive(Clone, Copy)]
98pub(crate) enum PipelineKind {
99 Material,
100 Compute,
101}
102
103impl std::fmt::Display for PipelineKind {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 f.write_str(match self {
106 PipelineKind::Material => "material",
107 PipelineKind::Compute => "compute pass",
108 })
109 }
110}
111
112pub(crate) fn find_own_entries<'a>(
113 label: Option<&str>,
114 kind: PipelineKind,
115 groups: &'a [GroupEntry],
116) -> &'a [BindingEntry] {
117 let mut found: Option<&[BindingEntry]> = None;
118 for g in groups {
119 if let GroupEntry::Own(entries) = g {
120 if found.is_some() {
121 panic!(
122 "{kind}{}: more than one GroupEntry::Own(...) in .entries(...) — a {kind} \
123 can only have one group of its own bind group entries",
124 label.map(|l| format!(" '{l}'")).unwrap_or_default()
125 );
126 }
127 found = Some(entries);
128 }
129 }
130 found.unwrap_or(&[])
131}
132
133pub(crate) fn assemble_group_layouts<'a>(
134 label: Option<&str>,
135 groups: &'a [GroupEntry],
136 own_layout: &'a BindGroupLayout,
137 pool: &'a GlobalLayoutPool,
138 max_bind_groups: u32,
139) -> Option<Vec<Option<&'a wgpu::BindGroupLayout>>> {
140 if groups.len() as u32 > max_bind_groups {
141 panic!(
142 "pipeline layout{} needs {} bind groups, but this device only supports \
143 {max_bind_groups} — trim .entries(...) to only the groups actually used",
144 label.map(|l| format!(" '{l}'")).unwrap_or_default(),
145 groups.len(),
146 );
147 }
148
149 groups
150 .iter()
151 .map(|g| {
152 let layout = match g {
153 GroupEntry::Own(_) => own_layout,
154 GroupEntry::Layout(l) => l,
155 GroupEntry::Global(name) => pool.get_ref(name)?,
156 };
157 Some(Some(layout.raw()))
158 })
159 .collect()
160}
161
162#[derive(PartialEq, Eq, Hash)]
165enum GroupKey {
166 Own(Vec<BindingEntry>),
167 Global(&'static str),
168}
169
170fn group_keys(groups: &[GroupEntry]) -> Option<Vec<GroupKey>> {
178 groups
179 .iter()
180 .map(|g| match g {
181 GroupEntry::Own(entries) => Some(GroupKey::Own(entries.clone())),
182 GroupEntry::Global(name) => Some(GroupKey::Global(name)),
183 GroupEntry::Layout(_) => None,
184 })
185 .collect()
186}
187
188#[derive(PartialEq, Eq, Hash)]
195pub(crate) struct MaterialPipelineKey {
196 pub shader_source: &'static str,
197 pub vertex_entry: Option<&'static str>,
198 pub fragment_entry: Option<&'static str>,
199 pub vertex_layouts: Vec<VertexBufferLayout>,
200 pub cull_mode: Option<Face>,
201 pub depth: Option<DepthStencilState>,
202 pub targets: Vec<ColorTargetState>,
203 pub polygon_mode: PolygonMode,
204 pub sample_count: u32,
205 groups: Vec<GroupKey>,
206}
207
208impl MaterialPipelineKey {
209 #[allow(clippy::too_many_arguments)]
211 pub fn new(
212 shader_source: &'static str,
213 vertex_entry: Option<&'static str>,
214 fragment_entry: Option<&'static str>,
215 vertex_layouts: Vec<VertexBufferLayout>,
216 cull_mode: Option<Face>,
217 depth: Option<DepthStencilState>,
218 targets: Vec<ColorTargetState>,
219 polygon_mode: PolygonMode,
220 sample_count: u32,
221 groups: &[GroupEntry],
222 ) -> Option<Self> {
223 Some(Self {
224 shader_source,
225 vertex_entry,
226 fragment_entry,
227 vertex_layouts,
228 cull_mode,
229 depth,
230 targets,
231 polygon_mode,
232 sample_count,
233 groups: group_keys(groups)?,
234 })
235 }
236}
237
238#[derive(Default)]
249pub struct MaterialPipelineCache {
250 entries: RefCell<HashMap<MaterialPipelineKey, (RenderPipeline, BindGroupLayout)>>,
251}
252
253impl MaterialPipelineCache {
254 pub fn new() -> Self {
255 Self::default()
256 }
257
258 pub(crate) fn get_or_compile(
262 &self,
263 key: Option<MaterialPipelineKey>,
264 compile: impl FnOnce() -> Option<(RenderPipeline, BindGroupLayout)>,
265 ) -> Option<(RenderPipeline, BindGroupLayout)> {
266 let Some(key) = key else { return compile() };
267 if let Some(cached) = self.entries.borrow().get(&key) {
268 return Some(cached.clone());
269 }
270 let built = compile()?;
271 self.entries.borrow_mut().insert(key, built.clone());
272 Some(built)
273 }
274}
275
276#[derive(PartialEq, Eq, Hash)]
280pub(crate) struct ComputePipelineKey {
281 pub shader_source: &'static str,
282 pub entry_point: Option<&'static str>,
283 groups: Vec<GroupKey>,
284}
285
286impl ComputePipelineKey {
287 pub fn new(shader_source: &'static str, entry_point: Option<&'static str>, groups: &[GroupEntry]) -> Option<Self> {
289 Some(Self { shader_source, entry_point, groups: group_keys(groups)? })
290 }
291}
292
293#[derive(Default)]
295pub struct ComputePipelineCache {
296 entries: RefCell<HashMap<ComputePipelineKey, (ComputePipeline, BindGroupLayout)>>,
297}
298
299impl ComputePipelineCache {
300 pub fn new() -> Self {
301 Self::default()
302 }
303
304 pub(crate) fn get_or_compile(
305 &self,
306 key: Option<ComputePipelineKey>,
307 compile: impl FnOnce() -> Option<(ComputePipeline, BindGroupLayout)>,
308 ) -> Option<(ComputePipeline, BindGroupLayout)> {
309 let Some(key) = key else { return compile() };
310 if let Some(cached) = self.entries.borrow().get(&key) {
311 return Some(cached.clone());
312 }
313 let built = compile()?;
314 self.entries.borrow_mut().insert(key, built.clone());
315 Some(built)
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn find_own_entries_returns_empty_slice_when_there_is_no_own_group() {
325 let entries = find_own_entries(None, PipelineKind::Material, &[]);
326 assert!(entries.is_empty());
327 }
328}