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