pebble/graphics/pipeline/
layout.rs1use crate::graphics::pipeline::binding::{BindGroupLayout, BindingEntry, BindingKind};
2
3pub enum GroupEntry {
8 Own(Vec<BindingEntry>),
9 Layout(BindGroupLayout),
10 Global(&'static str),
11}
12
13#[derive(Default)]
17pub struct OwnEntriesBuilder {
18 entries: Vec<BindingEntry>,
19 next_binding: u32,
20}
21
22impl OwnEntriesBuilder {
23 pub fn new() -> Self {
24 Self::default()
25 }
26
27 pub fn with_entry(self, name: &'static str, kind: BindingKind) -> Self {
28 let binding = self.next_binding;
29 self.with_entry_at(name, binding, kind)
30 }
31
32 pub fn with_entry_at(mut self, name: &'static str, binding: u32, kind: BindingKind) -> Self {
33 self.entries.push(BindingEntry { name, binding, kind });
34 self.next_binding = self.next_binding.max(binding + 1);
35 self
36 }
37
38 pub fn build(self) -> GroupEntry {
39 GroupEntry::Own(self.entries)
40 }
41}
42
43#[derive(Default)]
48pub struct GlobalLayoutPool {
49 entries: std::collections::HashMap<&'static str, BindGroupLayout>,
50}
51
52impl GlobalLayoutPool {
53 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn register(&mut self, name: &'static str, layout: BindGroupLayout) {
59 if self.entries.insert(name, layout).is_some() {
60 panic!("global layout pool: '{name}' is already registered");
61 }
62 }
63
64 pub fn get(&self, name: &str) -> Option<BindGroupLayout> {
65 self.entries.get(name).cloned()
66 }
67
68 pub(crate) fn get_ref(&self, name: &str) -> Option<&BindGroupLayout> {
69 self.entries.get(name)
70 }
71}
72
73#[derive(Clone, Copy)]
74pub(crate) enum PipelineKind {
75 Material,
76 Compute,
77}
78
79impl std::fmt::Display for PipelineKind {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.write_str(match self {
82 PipelineKind::Material => "material",
83 PipelineKind::Compute => "compute pass",
84 })
85 }
86}
87
88pub(crate) fn find_own_entries<'a>(
89 label: Option<&str>,
90 kind: PipelineKind,
91 groups: &'a [GroupEntry],
92) -> &'a [BindingEntry] {
93 let mut found: Option<&[BindingEntry]> = None;
94 for g in groups {
95 if let GroupEntry::Own(entries) = g {
96 if found.is_some() {
97 panic!(
98 "{kind}{}: more than one GroupEntry::Own(...) in .entries(...) — a {kind} \
99 can only have one group of its own bind group entries",
100 label.map(|l| format!(" '{l}'")).unwrap_or_default()
101 );
102 }
103 found = Some(entries);
104 }
105 }
106 found.unwrap_or(&[])
107}
108
109pub(crate) fn assemble_group_layouts<'a>(
110 label: Option<&str>,
111 groups: &'a [GroupEntry],
112 own_layout: &'a BindGroupLayout,
113 pool: &'a GlobalLayoutPool,
114 max_bind_groups: u32,
115) -> Option<Vec<Option<&'a wgpu::BindGroupLayout>>> {
116 if groups.len() as u32 > max_bind_groups {
117 panic!(
118 "pipeline layout{} needs {} bind groups, but this device only supports \
119 {max_bind_groups} — trim .entries(...) to only the groups actually used",
120 label.map(|l| format!(" '{l}'")).unwrap_or_default(),
121 groups.len(),
122 );
123 }
124
125 groups
126 .iter()
127 .map(|g| {
128 let layout = match g {
129 GroupEntry::Own(_) => own_layout,
130 GroupEntry::Layout(l) => l,
131 GroupEntry::Global(name) => pool.get_ref(name)?,
132 };
133 Some(Some(layout.raw()))
134 })
135 .collect()
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn find_own_entries_returns_empty_slice_when_there_is_no_own_group() {
144 let entries = find_own_entries(None, PipelineKind::Material, &[]);
145 assert!(entries.is_empty());
146 }
147}