wgpu_core/track/mod.rs
1/*! Resource State and Lifetime Trackers
2
3These structures are responsible for keeping track of resource state,
4generating barriers where needednd making sure resources are kept
5alive until the trackers die.
6
7## General Architecture
8
9Tracking is some of the hottest code in the entire codebase, so the trackers
10are designed to be as cache efficient as possible. They store resource state
11in flat vectors, storing metadata SOA style, one vector per type of metadata.
12
13A lot of the tracker code is deeply unsafe, using unchecked accesses all over
14to make performance as good as possible. However, for all unsafe accesses, there
15is a corresponding debug assert the checks if that access is valid. This helps
16get bugs caught fast, while still letting users not need to pay for the bounds
17checks.
18
19In wgpu, each resource ID includes a bitfield holding an index.
20Indices are allocated and re-used, so they will always be as low as
21reasonably possible. This allows us to use IDs to index into an array
22of tracking information.
23
24## Statefulness
25
26There are two main types of trackers, stateful and stateless.
27
28Stateful trackers are for buffers and textures. They both have
29resource state attached to them which needs to be used to generate
30automatic synchronization. Because of the different requirements of
31buffers and textures, they have two separate tracking structures.
32
33Stateless trackers only store metadata and own the given resource.
34
35## Use Case
36
37Within each type of tracker, the trackers are further split into 3 different
38use cases, Bind Group, Usage Scopend a full Tracker.
39
40Bind Group trackers are just a list of different resources, their refcount,
41and how they are used. Textures are used via a selector and a usage type.
42Buffers by just a usage type. Stateless resources don't have a usage type.
43
44Usage Scope trackers are only for stateful resources. These trackers represent
45a single [`UsageScope`] in the spec. When a use is added to a usage scope,
46it is merged with all other uses of that resource in that scope. If there
47is a usage conflict, merging will fail and an error will be reported.
48
49Full trackers represent a before and after state of a resource. These
50are used for tracking on the device and on command buffers. The before
51state represents the state the resource is first used as in the command buffer,
52the after state is the state the command buffer leaves the resource in.
53These double ended buffers can then be used to generate the needed transitions
54between command buffers.
55
56## Dense Datastructure with Sparse Data
57
58This tracking system is based on having completely dense data, but trackers do
59not always contain every resource. Some resources (or even most resources) go
60unused in any given command buffer. So to help speed up the process of iterating
61through possibly thousands of resources, we use a bit vector to represent if
62a resource is in the buffer or not. This allows us extremely efficient memory
63utilizations well as being able to bail out of whole blocks of 32-64 resources
64with a single usize comparison with zero. In practice this means that merging
65partially resident buffers is extremely quick.
66
67The main advantage of this dense datastructure is that we can do merging
68of trackers in an extremely efficient fashion that results in us doing linear
69scans down a couple of buffers. CPUs and their caches absolutely eat this up.
70
71## Stateful Resource Operations
72
73All operations on stateful trackers boil down to one of four operations:
74- `insert(tracker, new_state)` adds a resource with a given state to the tracker
75 for the first time.
76- `merge(tracker, new_state)` merges this new state with the previous state, checking
77 for usage conflicts.
78- `barrier(tracker, new_state)` compares the given state to the existing state and
79 generates the needed barriers.
80- `update(tracker, new_state)` takes the given new state and overrides the old state.
81
82This allows us to compose the operations to form the various kinds of tracker merges
83that need to happen in the codebase. For each resource in the given merger, the following
84operation applies:
85
86```text
87UsageScope <- Resource = insert(scope, usage) OR merge(scope, usage)
88UsageScope <- UsageScope = insert(scope, scope) OR merge(scope, scope)
89CommandBuffer <- UsageScope = insert(buffer.start, buffer.end, scope)
90 OR barrier(buffer.end, scope) + update(buffer.end, scope)
91Device <- CommandBuffer = insert(device.start, device.end, buffer.start, buffer.end)
92 OR barrier(device.end, buffer.start) + update(device.end, buffer.end)
93```
94
95[`UsageScope`]: https://gpuweb.github.io/gpuweb/#programming-model-synchronization
96*/
97
98mod blas;
99mod buffer;
100mod metadata;
101mod range;
102mod stateless;
103mod texture;
104
105use crate::{
106 binding_model, command,
107 lock::{rank, Mutex},
108 pipeline,
109 resource::{self, Labeled, RawResourceAccess, ResourceErrorIdent},
110 snatch::SnatchGuard,
111 track::blas::BlasTracker,
112};
113
114use alloc::{sync::Arc, vec::Vec};
115use core::{fmt, mem, ops};
116
117use thiserror::Error;
118
119pub(crate) use buffer::{
120 BufferBindGroupState, BufferTracker, BufferUsageScope, DeviceBufferTracker,
121};
122use metadata::{ResourceMetadata, ResourceMetadataProvider};
123pub(crate) use stateless::StatelessTracker;
124pub(crate) use texture::{
125 DeviceTextureTracker, TextureTracker, TextureTrackerSetSingle, TextureUsageScope,
126 TextureViewBindGroupState,
127};
128use wgt::{
129 error::{ErrorType, WebGpuError},
130 strict_assert_ne,
131};
132
133#[repr(transparent)]
134#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
135pub(crate) struct TrackerIndex(u32);
136
137impl TrackerIndex {
138 pub fn as_usize(self) -> usize {
139 self.0 as usize
140 }
141}
142
143/// wgpu-core internally use some array-like storage for tracking resources.
144/// To that end, there needs to be a uniquely assigned index for each live resource
145/// of a certain type. This index is separate from the resource ID for various reasons:
146/// - There can be multiple resource IDs pointing the the same resource.
147/// - IDs of dead handles can be recycled while resources are internally held alive (and tracked).
148/// - The plan is to remove IDs in the long run
149/// ([#5121](https://github.com/gfx-rs/wgpu/issues/5121)).
150///
151/// In order to produce these tracker indices, there is a shared TrackerIndexAllocator
152/// per resource type. Indices have the same lifetime as the internal resource they
153/// are associated to (alloc happens when creating the resource and free is called when
154/// the resource is dropped).
155struct TrackerIndexAllocator {
156 unused: Vec<TrackerIndex>,
157 next_index: TrackerIndex,
158}
159
160impl TrackerIndexAllocator {
161 pub fn new() -> Self {
162 TrackerIndexAllocator {
163 unused: Vec::new(),
164 next_index: TrackerIndex(0),
165 }
166 }
167
168 pub fn alloc(&mut self) -> TrackerIndex {
169 if let Some(index) = self.unused.pop() {
170 return index;
171 }
172
173 let index = self.next_index;
174 self.next_index.0 += 1;
175
176 index
177 }
178
179 pub fn free(&mut self, index: TrackerIndex) {
180 self.unused.push(index);
181 }
182
183 // This is used to pre-allocate the tracker storage.
184 pub fn size(&self) -> usize {
185 self.next_index.0 as usize
186 }
187}
188
189impl fmt::Debug for TrackerIndexAllocator {
190 fn fmt(&self, _: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
191 Ok(())
192 }
193}
194
195/// See TrackerIndexAllocator.
196#[derive(Debug)]
197pub(crate) struct SharedTrackerIndexAllocator {
198 inner: Mutex<TrackerIndexAllocator>,
199}
200
201impl SharedTrackerIndexAllocator {
202 pub fn new() -> Self {
203 SharedTrackerIndexAllocator {
204 inner: Mutex::new(
205 rank::SHARED_TRACKER_INDEX_ALLOCATOR_INNER,
206 TrackerIndexAllocator::new(),
207 ),
208 }
209 }
210
211 pub fn alloc(&self) -> TrackerIndex {
212 self.inner.lock().alloc()
213 }
214
215 pub fn free(&self, index: TrackerIndex) {
216 self.inner.lock().free(index);
217 }
218
219 pub fn size(&self) -> usize {
220 self.inner.lock().size()
221 }
222}
223
224pub(crate) struct TrackerIndexAllocators {
225 pub buffers: Arc<SharedTrackerIndexAllocator>,
226 pub textures: Arc<SharedTrackerIndexAllocator>,
227 pub texture_views: Arc<SharedTrackerIndexAllocator>,
228 pub samplers: Arc<SharedTrackerIndexAllocator>,
229 pub bind_groups: Arc<SharedTrackerIndexAllocator>,
230 pub compute_pipelines: Arc<SharedTrackerIndexAllocator>,
231 pub render_pipelines: Arc<SharedTrackerIndexAllocator>,
232 pub bundles: Arc<SharedTrackerIndexAllocator>,
233 pub query_sets: Arc<SharedTrackerIndexAllocator>,
234 pub blas_s: Arc<SharedTrackerIndexAllocator>,
235 pub tlas_s: Arc<SharedTrackerIndexAllocator>,
236}
237
238impl TrackerIndexAllocators {
239 pub fn new() -> Self {
240 TrackerIndexAllocators {
241 buffers: Arc::new(SharedTrackerIndexAllocator::new()),
242 textures: Arc::new(SharedTrackerIndexAllocator::new()),
243 texture_views: Arc::new(SharedTrackerIndexAllocator::new()),
244 samplers: Arc::new(SharedTrackerIndexAllocator::new()),
245 bind_groups: Arc::new(SharedTrackerIndexAllocator::new()),
246 compute_pipelines: Arc::new(SharedTrackerIndexAllocator::new()),
247 render_pipelines: Arc::new(SharedTrackerIndexAllocator::new()),
248 bundles: Arc::new(SharedTrackerIndexAllocator::new()),
249 query_sets: Arc::new(SharedTrackerIndexAllocator::new()),
250 blas_s: Arc::new(SharedTrackerIndexAllocator::new()),
251 tlas_s: Arc::new(SharedTrackerIndexAllocator::new()),
252 }
253 }
254}
255
256/// A structure containing all the information about a particular resource
257/// transition. User code should be able to generate a pipeline barrier
258/// based on the contents.
259#[derive(Debug, PartialEq)]
260pub(crate) struct PendingTransition<S: ResourceUses> {
261 pub id: u32,
262 pub selector: S::Selector,
263 pub usage: hal::StateTransition<S>,
264}
265
266pub(crate) type PendingTransitionList = Vec<PendingTransition<wgt::TextureUses>>;
267
268impl PendingTransition<wgt::BufferUses> {
269 /// Produce the hal barrier corresponding to the transition.
270 pub fn into_hal<'a>(
271 self,
272 buf: &'a resource::Buffer,
273 snatch_guard: &'a SnatchGuard<'a>,
274 ) -> hal::BufferBarrier<'a, dyn hal::DynBuffer> {
275 let buffer = buf.raw(snatch_guard).expect("Buffer is destroyed");
276 hal::BufferBarrier {
277 buffer,
278 usage: self.usage,
279 }
280 }
281}
282
283impl PendingTransition<wgt::TextureUses> {
284 /// Produce the hal barrier corresponding to the transition.
285 pub fn into_hal(
286 self,
287 texture: &dyn hal::DynTexture,
288 ) -> hal::TextureBarrier<'_, dyn hal::DynTexture> {
289 // These showing up in a barrier is always a bug
290 strict_assert_ne!(self.usage.from, wgt::TextureUses::UNKNOWN);
291 strict_assert_ne!(self.usage.to, wgt::TextureUses::UNKNOWN);
292
293 let mip_count = self.selector.mips.end - self.selector.mips.start;
294 strict_assert_ne!(mip_count, 0);
295 let layer_count = self.selector.layers.end - self.selector.layers.start;
296 strict_assert_ne!(layer_count, 0);
297
298 hal::TextureBarrier {
299 texture,
300 range: wgt::ImageSubresourceRange {
301 aspect: wgt::TextureAspect::All,
302 base_mip_level: self.selector.mips.start,
303 mip_level_count: Some(mip_count),
304 base_array_layer: self.selector.layers.start,
305 array_layer_count: Some(layer_count),
306 },
307 usage: self.usage,
308 }
309 }
310}
311
312/// The uses that a resource or subresource can be in.
313pub(crate) trait ResourceUses:
314 fmt::Debug + ops::BitAnd<Output = Self> + ops::BitOr<Output = Self> + PartialEq + Sized + Copy
315{
316 /// All flags that are exclusive.
317 const EXCLUSIVE: Self;
318
319 /// The selector used by this resource.
320 type Selector: fmt::Debug;
321
322 /// Turn the resource into a pile of bits.
323 fn bits(self) -> u16;
324 /// Returns true if the all the uses are ordered.
325 fn all_ordered(self) -> bool;
326 /// Returns true if any of the uses are exclusive.
327 fn any_exclusive(self) -> bool;
328}
329
330/// Returns true if the given states violates the usage scope rule
331/// of any(inclusive) XOR one(exclusive)
332fn invalid_resource_state<T: ResourceUses>(state: T) -> bool {
333 // Is power of two also means "is one bit set". We check for this as if
334 // we're in any exclusive state, we must only be in a single state.
335 state.any_exclusive() && !state.bits().is_power_of_two()
336}
337
338/// Returns true if the transition from one state to another does not require
339/// a barrier.
340fn skip_barrier<T: ResourceUses>(old_state: T, new_state: T) -> bool {
341 // If the state didn't change and all the usages are ordered, the hardware
342 // will guarantee the order of accesses, so we do not need to issue a barrier at all
343 old_state == new_state && old_state.all_ordered()
344}
345
346#[derive(Clone, Debug, Error)]
347pub enum ResourceUsageCompatibilityError {
348 #[error("Attempted to use {res} with {invalid_use}.")]
349 Buffer {
350 res: ResourceErrorIdent,
351 invalid_use: InvalidUse<wgt::BufferUses>,
352 },
353 #[error(
354 "Attempted to use {res} (mips {mip_levels:?} layers {array_layers:?}) with {invalid_use}."
355 )]
356 Texture {
357 res: ResourceErrorIdent,
358 mip_levels: ops::Range<u32>,
359 array_layers: ops::Range<u32>,
360 invalid_use: InvalidUse<wgt::TextureUses>,
361 },
362}
363
364impl WebGpuError for ResourceUsageCompatibilityError {
365 fn webgpu_error_type(&self) -> ErrorType {
366 ErrorType::Validation
367 }
368}
369
370impl ResourceUsageCompatibilityError {
371 fn from_buffer(
372 buffer: &resource::Buffer,
373 current_state: wgt::BufferUses,
374 new_state: wgt::BufferUses,
375 ) -> Self {
376 Self::Buffer {
377 res: buffer.error_ident(),
378 invalid_use: InvalidUse {
379 current_state,
380 new_state,
381 },
382 }
383 }
384
385 fn from_texture(
386 texture: &resource::Texture,
387 selector: wgt::TextureSelector,
388 current_state: wgt::TextureUses,
389 new_state: wgt::TextureUses,
390 ) -> Self {
391 Self::Texture {
392 res: texture.error_ident(),
393 mip_levels: selector.mips,
394 array_layers: selector.layers,
395 invalid_use: InvalidUse {
396 current_state,
397 new_state,
398 },
399 }
400 }
401}
402
403/// Pretty print helper that shows helpful descriptions of a conflicting usage.
404#[derive(Clone, Debug, Eq, PartialEq)]
405pub struct InvalidUse<T> {
406 current_state: T,
407 new_state: T,
408}
409
410impl<T: ResourceUses> fmt::Display for InvalidUse<T> {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 let current = self.current_state;
413 let new = self.new_state;
414
415 let current_exclusive = current & T::EXCLUSIVE;
416 let new_exclusive = new & T::EXCLUSIVE;
417
418 let exclusive = current_exclusive | new_exclusive;
419
420 // The text starts with "tried to use X resource with {self}"
421 write!(
422 f,
423 "conflicting usages. Current usage {current:?} and new usage {new:?}. \
424 {exclusive:?} is an exclusive usage and cannot be used with any other \
425 usages within the usage scope (renderpass or compute dispatch)"
426 )
427 }
428}
429
430/// All the usages that a bind group contains. The uses are not deduplicated in any way
431/// and may include conflicting uses. This is fully compliant by the WebGPU spec.
432///
433/// All bind group states are sorted by their ID so that when adding to a tracker,
434/// they are added in the most efficient order possible (ascending order).
435#[derive(Debug)]
436pub(crate) struct BindGroupStates {
437 pub buffers: BufferBindGroupState,
438 pub views: TextureViewBindGroupState,
439 pub samplers: StatelessTracker<resource::Sampler>,
440 pub acceleration_structures: StatelessTracker<resource::Tlas>,
441}
442
443impl BindGroupStates {
444 pub fn new() -> Self {
445 Self {
446 buffers: BufferBindGroupState::new(),
447 views: TextureViewBindGroupState::new(),
448 samplers: StatelessTracker::new(),
449 acceleration_structures: StatelessTracker::new(),
450 }
451 }
452
453 /// Optimize the bind group states by sorting them by ID.
454 ///
455 /// When this list of states is merged into a tracker, the memory
456 /// accesses will be in a constant ascending order.
457 pub fn optimize(&mut self) {
458 self.buffers.optimize();
459 // Views are stateless, however, `TextureViewBindGroupState`
460 // is special as it will be merged with other texture trackers.
461 self.views.optimize();
462 // Samplers and Tlas's are stateless and don't need to be optimized
463 // since the tracker is never merged with any other tracker.
464 }
465}
466
467/// This is a render bundle specific usage scope. It includes stateless resources
468/// that are not normally included in a usage scope, but are used by render bundles
469/// and need to be owned by the render bundles.
470#[derive(Debug)]
471pub(crate) struct RenderBundleScope {
472 pub buffers: BufferUsageScope,
473 pub textures: TextureUsageScope,
474 // Don't need to track views and samplers, they are never used directly, only by bind groups.
475 pub bind_groups: StatelessTracker<binding_model::BindGroup>,
476 pub render_pipelines: StatelessTracker<pipeline::RenderPipeline>,
477}
478
479impl RenderBundleScope {
480 /// Create the render bundle scope and pull the maximum IDs from the hubs.
481 pub fn new() -> Self {
482 Self {
483 buffers: BufferUsageScope::default(),
484 textures: TextureUsageScope::default(),
485 bind_groups: StatelessTracker::new(),
486 render_pipelines: StatelessTracker::new(),
487 }
488 }
489
490 /// Merge the inner contents of a bind group into the render bundle tracker.
491 ///
492 /// Only stateful things are merged in herell other resources are owned
493 /// indirectly by the bind group.
494 ///
495 /// # Safety
496 ///
497 /// The maximum ID given by each bind group resource must be less than the
498 /// length of the storage given at the call to `new`.
499 pub unsafe fn merge_bind_group(
500 &mut self,
501 bind_group: &BindGroupStates,
502 ) -> Result<(), ResourceUsageCompatibilityError> {
503 unsafe { self.buffers.merge_bind_group(&bind_group.buffers)? };
504 unsafe { self.textures.merge_bind_group(&bind_group.views)? };
505
506 Ok(())
507 }
508}
509
510/// A pool for storing the memory used by [`UsageScope`]s. We take and store this memory when the
511/// scope is dropped to avoid reallocating. The memory required only grows and allocation cost is
512/// significant when a large number of resources have been used.
513pub(crate) type UsageScopePool = Mutex<Vec<(BufferUsageScope, TextureUsageScope)>>;
514
515/// A usage scope tracker. Only needs to store stateful resources as stateless
516/// resources cannot possibly have a usage conflict.
517#[derive(Debug)]
518pub(crate) struct UsageScope<'a> {
519 pub pool: &'a UsageScopePool,
520 pub buffers: BufferUsageScope,
521 pub textures: TextureUsageScope,
522}
523
524impl<'a> Drop for UsageScope<'a> {
525 fn drop(&mut self) {
526 // clear vecs and push into pool
527 self.buffers.clear();
528 self.textures.clear();
529 self.pool
530 .lock()
531 .push((mem::take(&mut self.buffers), mem::take(&mut self.textures)));
532 }
533}
534
535impl UsageScope<'static> {
536 pub fn new_pooled<'d>(
537 pool: &'d UsageScopePool,
538 tracker_indices: &TrackerIndexAllocators,
539 ) -> UsageScope<'d> {
540 let pooled = pool.lock().pop().unwrap_or_default();
541
542 let mut scope = UsageScope::<'d> {
543 pool,
544 buffers: pooled.0,
545 textures: pooled.1,
546 };
547
548 scope.buffers.set_size(tracker_indices.buffers.size());
549 scope.textures.set_size(tracker_indices.textures.size());
550 scope
551 }
552}
553
554impl<'a> UsageScope<'a> {
555 /// Merge the inner contents of a bind group into the usage scope.
556 ///
557 /// Only stateful things are merged in herell other resources are owned
558 /// indirectly by the bind group.
559 ///
560 /// # Safety
561 ///
562 /// The maximum ID given by each bind group resource must be less than the
563 /// length of the storage given at the call to `new`.
564 pub unsafe fn merge_bind_group(
565 &mut self,
566 bind_group: &BindGroupStates,
567 ) -> Result<(), ResourceUsageCompatibilityError> {
568 unsafe {
569 self.buffers.merge_bind_group(&bind_group.buffers)?;
570 self.textures.merge_bind_group(&bind_group.views)?;
571 }
572
573 Ok(())
574 }
575
576 /// Merge the inner contents of a bind group into the usage scope.
577 ///
578 /// Only stateful things are merged in herell other resources are owned
579 /// indirectly by a bind group or are merged directly into the command buffer tracker.
580 ///
581 /// # Safety
582 ///
583 /// The maximum ID given by each bind group resource must be less than the
584 /// length of the storage given at the call to `new`.
585 pub unsafe fn merge_render_bundle(
586 &mut self,
587 render_bundle: &RenderBundleScope,
588 ) -> Result<(), ResourceUsageCompatibilityError> {
589 self.buffers.merge_usage_scope(&render_bundle.buffers)?;
590 self.textures.merge_usage_scope(&render_bundle.textures)?;
591
592 Ok(())
593 }
594}
595
596/// A tracker used by Device.
597pub(crate) struct DeviceTracker {
598 pub buffers: DeviceBufferTracker,
599 pub textures: DeviceTextureTracker,
600}
601
602impl DeviceTracker {
603 pub fn new() -> Self {
604 Self {
605 buffers: DeviceBufferTracker::new(),
606 textures: DeviceTextureTracker::new(),
607 }
608 }
609}
610
611/// A full double sided tracker used by CommandBuffers.
612pub(crate) struct Tracker {
613 pub buffers: BufferTracker,
614 pub textures: TextureTracker,
615 pub blas_s: BlasTracker,
616 pub tlas_s: StatelessTracker<resource::Tlas>,
617 pub views: StatelessTracker<resource::TextureView>,
618 pub bind_groups: StatelessTracker<binding_model::BindGroup>,
619 pub compute_pipelines: StatelessTracker<pipeline::ComputePipeline>,
620 pub render_pipelines: StatelessTracker<pipeline::RenderPipeline>,
621 pub bundles: StatelessTracker<command::RenderBundle>,
622 pub query_sets: StatelessTracker<resource::QuerySet>,
623}
624
625impl Tracker {
626 pub fn new() -> Self {
627 Self {
628 buffers: BufferTracker::new(),
629 textures: TextureTracker::new(),
630 blas_s: BlasTracker::new(),
631 tlas_s: StatelessTracker::new(),
632 views: StatelessTracker::new(),
633 bind_groups: StatelessTracker::new(),
634 compute_pipelines: StatelessTracker::new(),
635 render_pipelines: StatelessTracker::new(),
636 bundles: StatelessTracker::new(),
637 query_sets: StatelessTracker::new(),
638 }
639 }
640
641 /// Iterates through all resources in the given bind group and adopts
642 /// the state given for those resources in the UsageScope. It also
643 /// removes all touched resources from the usage scope.
644 ///
645 /// If a transition is needed to get the resources into the needed
646 /// state, those transitions are stored within the tracker. A
647 /// subsequent call to [`BufferTracker::drain_transitions`] or
648 /// [`TextureTracker::drain_transitions`] is needed to get those transitions.
649 ///
650 /// This is a really funky method used by Compute Passes to generate
651 /// barriers after a call to dispatch without needing to iterate
652 /// over all elements in the usage scope. We use each the
653 /// bind group as a source of which IDs to look at. The bind groups
654 /// must have first been added to the usage scope.
655 ///
656 /// Only stateful things are merged in here, all other resources are owned
657 /// indirectly by the bind group.
658 ///
659 /// # Safety
660 ///
661 /// The maximum ID given by each bind group resource must be less than the
662 /// value given to `set_size`
663 pub unsafe fn set_and_remove_from_usage_scope_sparse(
664 &mut self,
665 scope: &mut UsageScope,
666 bind_group: &BindGroupStates,
667 ) {
668 unsafe {
669 self.buffers.set_and_remove_from_usage_scope_sparse(
670 &mut scope.buffers,
671 bind_group.buffers.used_tracker_indices(),
672 )
673 };
674 unsafe {
675 self.textures
676 .set_and_remove_from_usage_scope_sparse(&mut scope.textures, &bind_group.views)
677 };
678 }
679}