easy_wgpu/
bind_group.rs

1use smallvec::SmallVec;
2use wgpu;
3
4use crate::texture::Texture;
5
6pub fn align(size: usize, alignment: usize) -> usize {
7    size.div_ceil(alignment) * alignment
8}
9
10/// Since we want the `BindGroupWrapper` to keep a vector of the ids and the ids
11/// are all typed, we use an enum to deal with heterogeneous types
12#[derive(PartialEq, Clone)]
13pub enum BgEntriesId {
14    Tex(wgpu::Id<wgpu::Texture>),
15    Buf(wgpu::Id<wgpu::Buffer>),
16    Sampler(wgpu::Id<wgpu::Sampler>),
17}
18
19/// Wrapper for a bind group that also keeps the ids of the entries. This helps
20/// with keeping track if the textures in the entries have changed and the bind
21/// group needs to be recreated
22pub struct BindGroupWrapper {
23    bind_group: wgpu::BindGroup,
24    ids: SmallVec<[BgEntriesId; 16]>,
25}
26impl BindGroupWrapper {
27    fn new(bind_group: wgpu::BindGroup, ids: SmallVec<[BgEntriesId; 16]>) -> Self {
28        Self { bind_group, ids }
29    }
30    pub fn bg(&self) -> &wgpu::BindGroup {
31        &self.bind_group
32    }
33    pub fn is_stale(&self, entries: &SmallVec<[BindGroupEntry; 16]>) -> bool {
34        if self.ids.len() != entries.len() {
35            return true;
36        }
37
38        for it in entries.iter().zip(self.ids.iter()) {
39            let (entry, id) = it;
40            //if the ids don't match we return true because the binding group needs to be
41            // recreated
42            if entry.id != *id {
43                return true;
44            }
45        }
46
47        false
48    }
49}
50
51/// Stores both an entry and the ``global_id`` of the resources it points to in
52/// order to track if the bind group is stale Required wgpu to enable the
53/// expose-ids feature
54pub struct BindGroupEntry<'a> {
55    pub entry: wgpu::BindGroupEntry<'a>,
56    pub id: BgEntriesId,
57}
58
59/// Describes a bind group a series of entries
60pub struct BindGroupDesc<'a> {
61    pub label: Option<String>,
62    pub bind_group_entries: SmallVec<[BindGroupEntry<'a>; 16]>,
63    last_binding_number: u32,
64}
65impl Default for BindGroupDesc<'_> {
66    fn default() -> Self {
67        Self {
68            label: None,
69            bind_group_entries: SmallVec::new(),
70            last_binding_number: 0,
71        }
72    }
73}
74impl<'a> BindGroupDesc<'a> {
75    pub fn new(label: &str, entries: SmallVec<[BindGroupEntry<'a>; 16]>) -> Self {
76        Self {
77            label: Some(String::from(label)),
78            bind_group_entries: entries,
79            last_binding_number: 0,
80        }
81    }
82    pub fn into_bind_group_wrapper(self, device: &wgpu::Device, layout: &wgpu::BindGroupLayout) -> BindGroupWrapper {
83        //make the vector of bg entries
84        let mut vec_entries: SmallVec<[wgpu::BindGroupEntry; 16]> = SmallVec::new();
85        let mut ids: SmallVec<[BgEntriesId; 16]> = SmallVec::new();
86        for bg_entry in self.bind_group_entries {
87            //moves self.bg_entries and invalidates them
88            vec_entries.push(bg_entry.entry);
89            ids.push(bg_entry.id);
90        }
91        //create bg
92        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
93            layout,
94            // entries: self.bind_group_entries.as_slice(),
95            entries: vec_entries.as_slice(),
96            label: self.label.as_deref(),
97        });
98        BindGroupWrapper::new(bind_group, ids) //TODO add the ids of the
99                                               // texture entries
100    }
101}
102
103/// Builder for bind groups
104/// IMPORTANT: order of adding entries sets also the binding index that should
105/// correspond with the layout and the shader
106pub struct BindGroupBuilder<'a> {
107    bind_group_desc: Option<BindGroupDesc<'a>>,
108}
109impl Default for BindGroupBuilder<'_> {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114impl<'a> BindGroupBuilder<'a> {
115    pub fn new() -> Self {
116        Self {
117            bind_group_desc: Some(BindGroupDesc::default()),
118        }
119    }
120
121    /// # Panics
122    /// Will panic if the builder was not constructed with ``new()``
123    #[must_use]
124    pub fn label(mut self, label: &str) -> Self {
125        self.bind_group_desc.as_mut().unwrap().label = Some(String::from(label));
126        self
127    }
128
129    /// # Panics
130    /// Will panic if the builder was not constructed with ``new()``
131    #[must_use]
132    pub fn add_entry_empty(mut self) -> Self {
133        self.bind_group_desc.as_mut().unwrap().last_binding_number += 1;
134        self
135    }
136
137    /// # Panics
138    /// Will panic if the builder was not constructed with ``new()``
139    #[must_use]
140    pub fn add_entry_tex(mut self, tex: &'a Texture) -> Self {
141        //each entry we add will have sequential binding_indices
142        //this should correspond with the binding in the shader
143        let binding_number = self.bind_group_desc.as_ref().unwrap().last_binding_number;
144        //entry and id
145        let entry = BindGroupEntry {
146            entry: wgpu::BindGroupEntry {
147                binding: binding_number,
148                resource: wgpu::BindingResource::TextureView(&tex.view),
149            },
150            id: BgEntriesId::Tex(tex.texture.global_id()),
151        };
152        //add
153        self.bind_group_desc.as_mut().unwrap().bind_group_entries.push(entry);
154        self.bind_group_desc.as_mut().unwrap().last_binding_number += 1;
155        self
156    }
157
158    /// # Panics
159    /// Will panic if the builder was not constructed with ``new()``
160    #[must_use]
161    pub fn add_entry_buf(mut self, buffer: &'a wgpu::Buffer) -> Self {
162        //each entry we add will have sequential binding_indices
163        //this should correspond with the binding in the shader
164        let binding_number = self.bind_group_desc.as_ref().unwrap().last_binding_number;
165        //entry and id
166        let entry = BindGroupEntry {
167            entry: wgpu::BindGroupEntry {
168                binding: binding_number,
169                resource: buffer.as_entire_binding(),
170            },
171            id: BgEntriesId::Buf(buffer.global_id()),
172        };
173        //add
174        self.bind_group_desc.as_mut().unwrap().bind_group_entries.push(entry);
175        self.bind_group_desc.as_mut().unwrap().last_binding_number += 1;
176        self
177    }
178
179    /// # Panics
180    /// Will panic if the builder was not constructed with ``new()``
181    #[must_use]
182    pub fn add_entry_buf_chunk<T>(mut self, buffer: &'a wgpu::Buffer) -> Self {
183        //each entry we add will have sequential binding_indices
184        //this should correspond with the binding in the shader
185        let binding_number = self.bind_group_desc.as_ref().unwrap().last_binding_number;
186        //chunk of the buffer
187        let binding = wgpu::BufferBinding {
188            buffer,
189            offset: 0,
190            size: wgpu::BufferSize::new(u64::try_from(align(std::mem::size_of::<T>(), 256)).unwrap()),
191        };
192        //entry and id
193        let entry = BindGroupEntry {
194            entry: wgpu::BindGroupEntry {
195                binding: binding_number,
196                resource: wgpu::BindingResource::Buffer(binding),
197            },
198            id: BgEntriesId::Buf(buffer.global_id()),
199        };
200        //add
201        self.bind_group_desc.as_mut().unwrap().bind_group_entries.push(entry);
202        self.bind_group_desc.as_mut().unwrap().last_binding_number += 1;
203        self
204    }
205
206    /// # Panics
207    /// Will panic if the builder was not constructed with ``new()``
208    #[must_use]
209    pub fn add_entry_sampler(mut self, sampler: &'a wgpu::Sampler) -> Self {
210        //each entry we add will have sequential binding_indices
211        //this should correspond with the binding in the shader
212        let binding_number = self.bind_group_desc.as_ref().unwrap().last_binding_number;
213        //entry and id
214        let entry = BindGroupEntry {
215            entry: wgpu::BindGroupEntry {
216                binding: binding_number,
217                resource: wgpu::BindingResource::Sampler(sampler),
218            },
219            id: BgEntriesId::Sampler(sampler.global_id()),
220        };
221        // add
222        self.bind_group_desc.as_mut().unwrap().bind_group_entries.push(entry);
223        self.bind_group_desc.as_mut().unwrap().last_binding_number += 1;
224        self
225    }
226
227    // pub fn get_or_build(
228    //     &mut self,
229    //     device: &wgpu::Device,
230    //     layout: &wgpu::BindGroupLayout,
231    //     old_bind_group: Option<BindGroupWrapper>,
232    // ) -> BindGroupWrapper {
233    //     //check if it's stale by comparing if the id's match
234    //     let stale = true;
235    //     let ret = if stale {
236    //         let desc = self.bind_group_desc.take().unwrap();
237    //         desc.into_bind_group_wrapper(device, layout)
238    //     } else {
239    //         old_bind_group.unwrap()
240    //     };
241    //     ret
242    // }
243
244    /// # Panics
245    /// Will panic if the builder was not constructed with ``new()``
246    pub fn build(&mut self, device: &wgpu::Device, layout: &wgpu::BindGroupLayout) -> BindGroupWrapper {
247        let desc = self.bind_group_desc.take().unwrap();
248        desc.into_bind_group_wrapper(device, layout)
249    }
250
251    /// # Panics
252    /// Will panic if the builder was not constructed with ``new()``
253    pub fn build_bind_group(&mut self, device: &wgpu::Device, layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup {
254        let desc = self.bind_group_desc.take().unwrap();
255        desc.into_bind_group_wrapper(device, layout).bind_group
256    }
257
258    /// # Panics
259    /// Will panic if the builder was not constructed with ``new()``
260    pub fn build_entries(&mut self) -> SmallVec<[BindGroupEntry<'a>; 16]> {
261        self.bind_group_desc.take().unwrap().bind_group_entries
262    }
263}