1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
use std::sync::Arc;

use smallvec::SmallVec;

use crate::debug_label::DebugLabel;

use super::{
    bind_group_layout_pool::GpuBindGroupLayoutHandle,
    buffer_pool::{GpuBuffer, GpuBufferHandle, GpuBufferPool},
    dynamic_resource_pool::{DynamicResource, DynamicResourcePool, DynamicResourcesDesc},
    sampler_pool::{GpuSamplerHandle, GpuSamplerPool},
    texture_pool::{GpuTexture, GpuTextureHandle, GpuTexturePool},
    WgpuResourcePools,
};

slotmap::new_key_type! { pub struct GpuBindGroupHandle; }

/// A reference-counter baked bind group.
///
/// Once instances handles are dropped, the bind group will be marked for reclamation in the following frame.
/// Tracks use of dependent resources as well!
#[derive(Clone)]
pub struct GpuBindGroup {
    resource: Arc<DynamicResource<GpuBindGroupHandle, BindGroupDesc, wgpu::BindGroup>>,
    _owned_buffers: SmallVec<[GpuBuffer; 4]>,
    _owned_textures: SmallVec<[GpuTexture; 4]>,
}

impl std::ops::Deref for GpuBindGroup {
    type Target = wgpu::BindGroup;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.resource.inner
    }
}

#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub enum BindGroupEntry {
    DefaultTextureView(GpuTextureHandle), // TODO(andreas) what about non-default views?
    Buffer {
        handle: GpuBufferHandle,

        /// Base offset of the buffer. For bindings with `dynamic == true`, this offset
        /// will be added to the dynamic offset provided in [`wgpu::RenderPass::set_bind_group`].
        ///
        /// The offset has to be aligned to [`wgpu::Limits::min_uniform_buffer_offset_alignment`]
        /// or [`wgpu::Limits::min_storage_buffer_offset_alignment`] appropriately.
        offset: wgpu::BufferAddress,

        /// Size of the binding, or `None` for using the rest of the buffer.
        size: Option<wgpu::BufferSize>,
    },
    Sampler(GpuSamplerHandle),
}

#[derive(Clone, Hash, PartialEq, Eq, Debug)]
pub struct BindGroupDesc {
    /// Debug label of the bind group. This will show up in graphics debuggers for easy identification.
    pub label: DebugLabel,
    pub entries: SmallVec<[BindGroupEntry; 4]>,
    pub layout: GpuBindGroupLayoutHandle,
}

impl DynamicResourcesDesc for BindGroupDesc {
    fn resource_size_in_bytes(&self) -> u64 {
        // Size depends on gpu/driver (like with all resources).
        // We could guess something like a pointer per descriptor, but let's not pretend we know!
        0
    }

    fn allow_reuse(&self) -> bool {
        true
    }
}

/// Resource pool for bind groups.
///
/// Implementation notes:
/// Requirements regarding ownership & resource lifetime:
/// * owned [`wgpu::BindGroup`] should keep buffer/texture alive
///   (user should not need to hold buffer/texture manually)
/// * [`GpuBindGroupPool`] should *try* to re-use previously created bind groups if they happen to match
/// * mustn't prevent buffer/texture re-use on next frame
///   i.e. a internally cached [`GpuBindGroupPool`]s without owner shouldn't keep textures/buffers alive
///
/// We satisfy these by retrieving the "weak" buffer/texture handles and make them part of the [`GpuBindGroup`].
/// Internally, the [`GpuBindGroupPool`] does *not* hold any strong reference to any resource,
/// i.e. it does not interfere with the ownership tracking of buffer/texture pools.
/// The question whether a bind groups happen to be re-usable becomes again a simple question of matching
/// bind group descs which itself does not contain any ref counted objects!
#[derive(Default)]
pub struct GpuBindGroupPool {
    // Use a DynamicResourcePool because it gives out reference counted handles
    // which makes interacting with buffer/textures easier.
    //
    // On the flipside if someone requests the exact same bind group again as before,
    // they'll get a new one which is unnecessary. But this is *very* unlikely to ever happen.
    pool: DynamicResourcePool<GpuBindGroupHandle, BindGroupDesc, wgpu::BindGroup>,
}

impl GpuBindGroupPool {
    /// Returns a reference-counted, currently unused bind-group.
    /// Once ownership to the handle is given up, the bind group may be reclaimed in future frames.
    /// The handle also keeps alive any dependent resources.
    pub fn alloc(
        &self,
        device: &wgpu::Device,
        pools: &WgpuResourcePools,
        desc: &BindGroupDesc,
    ) -> GpuBindGroup {
        re_tracing::profile_function!();

        // Retrieve strong handles to buffers and textures.
        // This way, an owner of a bind group handle keeps buffers & textures alive!.
        let owned_buffers: SmallVec<[GpuBuffer; 4]> = desc
            .entries
            .iter()
            .filter_map(|e| {
                if let BindGroupEntry::Buffer { handle, .. } = e {
                    Some(
                        pools
                            .buffers
                            .get_from_handle(*handle)
                            .expect("BindGroupDesc had an invalid buffer handle"),
                    )
                } else {
                    None
                }
            })
            .collect();

        let owned_textures: SmallVec<[GpuTexture; 4]> = desc
            .entries
            .iter()
            .filter_map(|e| {
                if let BindGroupEntry::DefaultTextureView(handle) = e {
                    Some(
                        pools
                            .textures
                            .get_from_handle(*handle)
                            .expect("BindGroupDesc had an invalid texture handle"),
                    )
                } else {
                    None
                }
            })
            .collect();

        let resource = self.pool.alloc(desc, |desc| {
            let mut buffer_index = 0;
            let mut texture_index = 0;

            let samplers = pools.samplers.resources();

            device.create_bind_group(&wgpu::BindGroupDescriptor {
                label: desc.label.get(),
                entries: &desc
                    .entries
                    .iter()
                    .enumerate()
                    .map(|(index, entry)| wgpu::BindGroupEntry {
                        binding: index as _,
                        resource: match entry {
                            BindGroupEntry::DefaultTextureView(_) => {
                                let res = wgpu::BindingResource::TextureView(
                                    &owned_textures[texture_index].default_view,
                                );
                                texture_index += 1;
                                res
                            }
                            BindGroupEntry::Buffer {
                                handle: _,
                                offset,
                                size,
                            } => {
                                let res = wgpu::BindingResource::Buffer(wgpu::BufferBinding {
                                    buffer: &owned_buffers[buffer_index],
                                    offset: *offset,
                                    size: *size,
                                });
                                buffer_index += 1;
                                res
                            }
                            BindGroupEntry::Sampler(handle) => wgpu::BindingResource::Sampler(
                                samplers
                                    .get(*handle)
                                    .expect("BindGroupDesc had an sampler handle"),
                            ),
                        },
                    })
                    .collect::<Vec<_>>(),
                layout: pools
                    .bind_group_layouts
                    .resources()
                    .get(desc.layout)
                    .unwrap(),
            })
        });

        GpuBindGroup {
            resource,
            _owned_buffers: owned_buffers,
            _owned_textures: owned_textures,
        }
    }

    pub fn begin_frame(
        &mut self,
        frame_index: u64,
        _textures: &mut GpuTexturePool,
        _buffers: &mut GpuBufferPool,
        _samplers: &mut GpuSamplerPool,
    ) {
        self.pool.begin_frame(frame_index, |_res| {});
    }

    pub fn num_resources(&self) -> usize {
        self.pool.num_resources()
    }
}