use super::binding::{BindGroupLayout, BindingEntry};
pub enum GroupEntry {
Own(Vec<BindingEntry>),
Layout(BindGroupLayout),
Global(&'static str),
}
#[derive(Default)]
pub struct GlobalLayoutPool {
entries: std::collections::HashMap<&'static str, BindGroupLayout>,
}
impl GlobalLayoutPool {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, name: &'static str, layout: BindGroupLayout) {
if self.entries.insert(name, layout).is_some() {
panic!("global layout pool: '{name}' is already registered");
}
}
pub fn get(&self, name: &str) -> Option<BindGroupLayout> {
self.entries.get(name).cloned()
}
pub(crate) fn get_ref(&self, name: &str) -> Option<&BindGroupLayout> {
self.entries.get(name)
}
}
#[derive(Clone, Copy)]
pub(crate) enum PipelineKind {
Material,
Compute,
}
impl std::fmt::Display for PipelineKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
PipelineKind::Material => "material",
PipelineKind::Compute => "compute pass",
})
}
}
pub(crate) fn find_own_entries<'a>(
label: Option<&str>,
kind: PipelineKind,
groups: &'a [GroupEntry],
) -> &'a [BindingEntry] {
let mut found: Option<&[BindingEntry]> = None;
for g in groups {
if let GroupEntry::Own(entries) = g {
if found.is_some() {
panic!(
"{kind}{}: more than one GroupEntry::Own(...) in .entries(...) — a {kind} \
can only have one group of its own bind group entries",
label.map(|l| format!(" '{l}'")).unwrap_or_default()
);
}
found = Some(entries);
}
}
found.unwrap_or(&[])
}
pub(crate) fn assemble_group_layouts<'a>(
label: Option<&str>,
groups: &'a [GroupEntry],
own_layout: &'a BindGroupLayout,
pool: &'a GlobalLayoutPool,
max_bind_groups: u32,
) -> Option<Vec<Option<&'a wgpu::BindGroupLayout>>> {
if groups.len() as u32 > max_bind_groups {
panic!(
"pipeline layout{} needs {} bind groups, but this device only supports \
{max_bind_groups} — trim .entries(...) to only the groups actually used",
label.map(|l| format!(" '{l}'")).unwrap_or_default(),
groups.len(),
);
}
groups
.iter()
.map(|g| {
let layout = match g {
GroupEntry::Own(_) => own_layout,
GroupEntry::Layout(l) => l,
GroupEntry::Global(name) => pool.get_ref(name)?,
};
Some(Some(layout.raw()))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wgpu::binding::BindGroupLayoutBuilder;
use crate::wgpu::test_util::with_device;
fn empty_layout(device: &wgpu::Device) -> BindGroupLayout {
BindGroupLayoutBuilder::new().build_raw(device)
}
#[test]
fn global_layout_pool_get_round_trips_through_register() {
with_device!(device, _queue, {
let mut pool = GlobalLayoutPool::new();
pool.register("camera", empty_layout(&device));
assert!(pool.get("camera").is_some());
assert!(pool.get("missing").is_none());
});
}
#[test]
fn global_layout_pool_panics_on_duplicate_name() {
with_device!(device, _queue, {
let mut pool = GlobalLayoutPool::new();
pool.register("camera", empty_layout(&device));
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
pool.register("camera", empty_layout(&device));
}));
assert!(result.is_err(), "expected a panic for a duplicate name registered in the pool");
});
}
#[test]
fn find_own_entries_returns_empty_slice_when_there_is_no_own_group() {
let entries = find_own_entries(None, PipelineKind::Material, &[]);
assert!(entries.is_empty());
}
#[test]
fn find_own_entries_panics_on_more_than_one_own_group() {
with_device!(device, _queue, {
let groups =
vec![GroupEntry::Own(vec![]), GroupEntry::Layout(empty_layout(&device)), GroupEntry::Own(vec![])];
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
find_own_entries(None, PipelineKind::Material, &groups);
}));
assert!(result.is_err(), "expected a panic for more than one GroupEntry::Own");
});
}
#[test]
fn assemble_group_layouts_orders_slots_by_position() {
with_device!(device, _queue, {
let own = empty_layout(&device);
let a = empty_layout(&device);
let b = empty_layout(&device);
let groups =
vec![GroupEntry::Layout(a), GroupEntry::Own(vec![]), GroupEntry::Layout(b)];
let pool = GlobalLayoutPool::new();
let assembled = assemble_group_layouts(None, &groups, &own, &pool, 4).unwrap();
assert_eq!(assembled.len(), 3);
let GroupEntry::Layout(a) = &groups[0] else { unreachable!() };
let GroupEntry::Layout(b) = &groups[2] else { unreachable!() };
assert!(std::ptr::eq(assembled[0].unwrap(), a.raw()));
assert!(std::ptr::eq(assembled[1].unwrap(), own.raw()));
assert!(std::ptr::eq(assembled[2].unwrap(), b.raw()));
});
}
#[test]
fn assemble_group_layouts_resolves_global_entries_from_the_pool() {
with_device!(device, _queue, {
let own = empty_layout(&device);
let mut pool = GlobalLayoutPool::new();
pool.register("camera", empty_layout(&device));
let groups = vec![GroupEntry::Own(vec![]), GroupEntry::Global("camera")];
let assembled = assemble_group_layouts(None, &groups, &own, &pool, 4).unwrap();
assert_eq!(assembled.len(), 2);
assert!(std::ptr::eq(assembled[1].unwrap(), pool.get_ref("camera").unwrap().raw()));
});
}
#[test]
fn assemble_group_layouts_returns_none_for_an_unregistered_global() {
with_device!(device, _queue, {
let own = empty_layout(&device);
let pool = GlobalLayoutPool::new(); let groups = vec![GroupEntry::Global("camera")];
let assembled = assemble_group_layouts(None, &groups, &own, &pool, 4);
assert!(assembled.is_none(), "expected None (not ready), not a panic, for an unresolved global");
});
}
#[test]
fn exceeding_max_bind_groups_panics() {
with_device!(device, _queue, {
let own = empty_layout(&device);
let groups = vec![GroupEntry::Layout(empty_layout(&device)), GroupEntry::Layout(empty_layout(&device))];
let pool = GlobalLayoutPool::new();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
assemble_group_layouts(None, &groups, &own, &pool, 1);
}));
assert!(result.is_err(), "expected a panic for exceeding max_bind_groups");
});
}
}