Skip to main content

pebble/graphics/pipeline/
layout.rs

1use crate::graphics::pipeline::binding::{BindGroupLayout, BindingEntry, BindingKind};
2
3/// One bind group slot in a [`Material`](super::material::Material)/[`Compute`](super::compute::Compute)'s
4/// `.with_entries(...)` list — its own entries ([`Own`](Self::Own), built via
5/// [`OwnEntriesBuilder`]), a pre-built [`BindGroupLayout`], or a name looked
6/// up in the [`GlobalLayoutPool`] (for layouts shared across pipelines).
7pub enum GroupEntry {
8    Own(Vec<BindingEntry>),
9    Layout(BindGroupLayout),
10    Global(&'static str),
11}
12
13/// Builds the [`GroupEntry::Own`] list for a pipeline's own bind group —
14/// auto-increments binding indices unless you use
15/// [`with_entry_at`](Self::with_entry_at) to pin one explicitly.
16#[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/// A registry of named bind group layouts, inserted as a resource by
44/// [`BuiltinAssetsPlugin`](crate::graphics::BuiltinAssetsPlugin) — lets
45/// unrelated materials/computes share one layout via [`GroupEntry::Global`]
46/// instead of each declaring their own.
47#[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    /// Registers a layout under `name`. Panics if `name` is already registered.
58    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}