Skip to main content

bevy_render/
combined_bind_group.rs

1//! Utilities for combining [`AsBindGroup`] implementations
2
3use alloc::borrow::Cow;
4
5use bevy_ecs::system::SystemParamItem;
6use bevy_platform::{collections::HashSet, hash::FixedHasher};
7use wgpu::BindGroupLayoutEntry;
8
9use crate::{
10    render_resource::{
11        AsBindGroup, AsBindGroupError, BindGroupBuilder, BindGroupLayout, BindlessDescriptor,
12        BindlessResourceType, BindlessSlabResourceLimit,
13    },
14    renderer::RenderDevice,
15};
16
17/// Combines [`AsBindGroup::bindless_slot_count`] implementations from base `B` and extension `E` by choosing the smallest count
18pub fn bindless_slot_count<B: AsBindGroup, E: AsBindGroup>() -> Option<BindlessSlabResourceLimit> {
19    // We only enable bindless if both the base material and its extension
20    // are bindless. If we do enable bindless, we choose the smaller of the
21    // two slab size limits.
22    Some(combine_bindless_slot_count(
23        B::bindless_slot_count()?,
24        E::bindless_slot_count()?,
25    ))
26}
27
28/// Combines two [`BindlessSlabResourceLimit`]s, choosing the smallest limit
29fn combine_bindless_slot_count(
30    base: BindlessSlabResourceLimit,
31    extension: BindlessSlabResourceLimit,
32) -> BindlessSlabResourceLimit {
33    match (base, extension) {
34        (BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Auto) => {
35            BindlessSlabResourceLimit::Auto
36        }
37        (BindlessSlabResourceLimit::Auto, BindlessSlabResourceLimit::Custom(limit))
38        | (BindlessSlabResourceLimit::Custom(limit), BindlessSlabResourceLimit::Auto) => {
39            BindlessSlabResourceLimit::Custom(limit)
40        }
41        (
42            BindlessSlabResourceLimit::Custom(base_limit),
43            BindlessSlabResourceLimit::Custom(extended_limit),
44        ) => BindlessSlabResourceLimit::Custom(base_limit.min(extended_limit)),
45    }
46}
47
48/// Combines [`AsBindGroup::bind_group_data`] implementations from base `B` and extension `E`
49pub fn bind_group_data<B: AsBindGroup, E: AsBindGroup>(
50    base: &B,
51    extension: &E,
52) -> CombinedBindGroupData<B::Data, E::Data> {
53    CombinedBindGroupData {
54        base: base.bind_group_data(),
55        extension: extension.bind_group_data(),
56    }
57}
58
59/// Combines [`AsBindGroup::build_bind_group`] implementations from base `B` and extension `E` by concatenating their bindings
60pub fn build_bind_group<B: AsBindGroup, E: AsBindGroup>(
61    base: &B,
62    extension: &E,
63    layout: &BindGroupLayout,
64    render_device: &RenderDevice,
65    (base_param, extended_param): &mut SystemParamItem<'_, '_, (B::Param, E::Param)>,
66    mut force_no_bindless: bool,
67    output: &mut BindGroupBuilder,
68) -> Result<(), AsBindGroupError> {
69    force_no_bindless = force_no_bindless || bindless_slot_count::<B, E>().is_none();
70
71    B::build_bind_group(
72        base,
73        layout,
74        render_device,
75        base_param,
76        force_no_bindless,
77        output,
78    )?;
79    E::build_bind_group(
80        extension,
81        layout,
82        render_device,
83        extended_param,
84        force_no_bindless,
85        output,
86    )?;
87
88    Ok(())
89}
90
91/// Combines [`AsBindGroup::bind_group_layout_entries`] implementations from base `B` and extension `E` by deduplicatinig identical bindings
92pub fn bind_group_layout_entries<B: AsBindGroup, E: AsBindGroup>(
93    render_device: &RenderDevice,
94    mut force_no_bindless: bool,
95) -> Vec<BindGroupLayoutEntry> {
96    force_no_bindless = force_no_bindless || bindless_slot_count::<B, E>().is_none();
97
98    let base_entries = B::bind_group_layout_entries(render_device, force_no_bindless);
99    let extension_entries = E::bind_group_layout_entries(render_device, force_no_bindless);
100
101    combine_bind_group_layout_entries(base_entries, extension_entries)
102}
103
104/// Combines two sets of [`BindGroupLayoutEntry`]s, deduplicating identical bindings
105pub fn combine_bind_group_layout_entries(
106    base_entries: Vec<BindGroupLayoutEntry>,
107    extension_entries: Vec<BindGroupLayoutEntry>,
108) -> Vec<BindGroupLayoutEntry> {
109    // Add together the bindings of the base material and the extension
110    // material, skipping duplicate bindings. Duplicate bindings will occur
111    // when bindless mode is on, because of the common bindless resource
112    // arrays, and we need to eliminate the duplicates or `wgpu` will
113    // complain.
114    let mut seen_bindings = HashSet::<u32>::with_hasher(FixedHasher);
115
116    base_entries
117        .into_iter()
118        .chain(extension_entries)
119        .filter(|entry| seen_bindings.insert(entry.binding))
120        .collect()
121}
122
123/// Combines [`AsBindGroup::bindless_descriptor`] implementations from base `B` and extension `E` by merging their contents together
124pub fn bindless_descriptor<B: AsBindGroup, E: AsBindGroup>() -> Option<BindlessDescriptor> {
125    let base_bindless_descriptor = B::bindless_descriptor()?;
126    let extended_bindless_descriptor = E::bindless_descriptor()?;
127
128    Some(combine_bindless_descriptors(
129        base_bindless_descriptor,
130        extended_bindless_descriptor,
131    ))
132}
133
134/// Combines two [`BindlessDescriptor`]s by merging their contents together
135pub fn combine_bindless_descriptors(
136    base_bindless_descriptor: BindlessDescriptor,
137    extended_bindless_descriptor: BindlessDescriptor,
138) -> BindlessDescriptor {
139    // Combining the buffers and index tables is straightforward.
140    let mut buffers = base_bindless_descriptor.buffers.to_vec();
141    let mut index_tables = base_bindless_descriptor.index_tables.to_vec();
142
143    buffers.extend(extended_bindless_descriptor.buffers.iter().cloned());
144    index_tables.extend(extended_bindless_descriptor.index_tables.iter().cloned());
145
146    // Combining the resources is a little trickier because the resource
147    // array is indexed by bindless index, so we have to merge the two
148    // arrays, not just concatenate them.
149    let max_bindless_index = base_bindless_descriptor
150        .resources
151        .len()
152        .max(extended_bindless_descriptor.resources.len());
153    let mut resources = Vec::with_capacity(max_bindless_index);
154    for bindless_index in 0..max_bindless_index {
155        // In the event of a conflicting bindless index, we choose the
156        // base's binding.
157        match base_bindless_descriptor.resources.get(bindless_index) {
158            None | Some(&BindlessResourceType::None) => resources.push(
159                extended_bindless_descriptor
160                    .resources
161                    .get(bindless_index)
162                    .copied()
163                    .unwrap_or(BindlessResourceType::None),
164            ),
165            Some(&resource_type) => resources.push(resource_type),
166        }
167    }
168
169    BindlessDescriptor {
170        resources: Cow::Owned(resources),
171        buffers: Cow::Owned(buffers),
172        index_tables: Cow::Owned(index_tables),
173    }
174}
175
176/// The [`AsBindGroup::Data`] used for combining two [`AsBindGroup`] implementations
177#[derive(Copy, Clone, PartialEq, Eq, Hash)]
178#[repr(C, packed)]
179pub struct CombinedBindGroupData<B, E> {
180    pub base: B,
181    pub extension: E,
182}