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()
}
}