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 external_textures: Arc<SharedTrackerIndexAllocator>,
229    pub samplers: Arc<SharedTrackerIndexAllocator>,
230    pub bind_groups: Arc<SharedTrackerIndexAllocator>,
231    pub compute_pipelines: Arc<SharedTrackerIndexAllocator>,
232    pub render_pipelines: Arc<SharedTrackerIndexAllocator>,
233    pub bundles: Arc<SharedTrackerIndexAllocator>,
234    pub query_sets: Arc<SharedTrackerIndexAllocator>,
235    pub blas_s: Arc<SharedTrackerIndexAllocator>,
236    pub tlas_s: Arc<SharedTrackerIndexAllocator>,
237}
238
239impl TrackerIndexAllocators {
240    pub fn new() -> Self {
241        TrackerIndexAllocators {
242            buffers: Arc::new(SharedTrackerIndexAllocator::new()),
243            textures: Arc::new(SharedTrackerIndexAllocator::new()),
244            texture_views: Arc::new(SharedTrackerIndexAllocator::new()),
245            external_textures: Arc::new(SharedTrackerIndexAllocator::new()),
246            samplers: Arc::new(SharedTrackerIndexAllocator::new()),
247            bind_groups: Arc::new(SharedTrackerIndexAllocator::new()),
248            compute_pipelines: Arc::new(SharedTrackerIndexAllocator::new()),
249            render_pipelines: Arc::new(SharedTrackerIndexAllocator::new()),
250            bundles: Arc::new(SharedTrackerIndexAllocator::new()),
251            query_sets: Arc::new(SharedTrackerIndexAllocator::new()),
252            blas_s: Arc::new(SharedTrackerIndexAllocator::new()),
253            tlas_s: Arc::new(SharedTrackerIndexAllocator::new()),
254        }
255    }
256}
257
258/// A structure containing all the information about a particular resource
259/// transition. User code should be able to generate a pipeline barrier
260/// based on the contents.
261#[derive(Debug, PartialEq)]
262pub(crate) struct PendingTransition<S: ResourceUses> {
263    pub id: u32,
264    pub selector: S::Selector,
265    pub usage: hal::StateTransition<S>,
266}
267
268pub(crate) type PendingTransitionList = Vec<PendingTransition<wgt::TextureUses>>;
269
270impl PendingTransition<wgt::BufferUses> {
271    /// Produce the hal barrier corresponding to the transition.
272    pub fn into_hal<'a>(
273        self,
274        buf: &'a resource::Buffer,
275        snatch_guard: &'a SnatchGuard<'a>,
276    ) -> hal::BufferBarrier<'a, dyn hal::DynBuffer> {
277        let buffer = buf.raw(snatch_guard).expect("Buffer is destroyed");
278        hal::BufferBarrier {
279            buffer,
280            usage: self.usage,
281        }
282    }
283}
284
285impl PendingTransition<wgt::TextureUses> {
286    /// Produce the hal barrier corresponding to the transition.
287    pub fn into_hal(
288        self,
289        texture: &dyn hal::DynTexture,
290    ) -> hal::TextureBarrier<'_, dyn hal::DynTexture> {
291        // These showing up in a barrier is always a bug
292        strict_assert_ne!(self.usage.from, wgt::TextureUses::UNKNOWN);
293        strict_assert_ne!(self.usage.to, wgt::TextureUses::UNKNOWN);
294
295        let mip_count = self.selector.mips.end - self.selector.mips.start;
296        strict_assert_ne!(mip_count, 0);
297        let layer_count = self.selector.layers.end - self.selector.layers.start;
298        strict_assert_ne!(layer_count, 0);
299
300        hal::TextureBarrier {
301            texture,
302            range: wgt::ImageSubresourceRange {
303                aspect: wgt::TextureAspect::All,
304                base_mip_level: self.selector.mips.start,
305                mip_level_count: Some(mip_count),
306                base_array_layer: self.selector.layers.start,
307                array_layer_count: Some(layer_count),
308            },
309            usage: self.usage,
310        }
311    }
312}
313
314/// The uses that a resource or subresource can be in.
315pub(crate) trait ResourceUses:
316    fmt::Debug + ops::BitAnd<Output = Self> + ops::BitOr<Output = Self> + PartialEq + Sized + Copy
317{
318    /// All flags that are exclusive.
319    const EXCLUSIVE: Self;
320
321    /// The selector used by this resource.
322    type Selector: fmt::Debug;
323
324    /// Turn the resource into a pile of bits.
325    fn bits(self) -> u16;
326    /// Returns true if the all the uses are ordered.
327    fn all_ordered(self) -> bool;
328    /// Returns true if any of the uses are exclusive.
329    fn any_exclusive(self) -> bool;
330}
331
332/// Returns true if the given states violates the usage scope rule
333/// of any(inclusive) XOR one(exclusive)
334fn invalid_resource_state<T: ResourceUses>(state: T) -> bool {
335    // Is power of two also means "is one bit set". We check for this as if
336    // we're in any exclusive state, we must only be in a single state.
337    state.any_exclusive() && !state.bits().is_power_of_two()
338}
339
340/// Returns true if the transition from one state to another does not require
341/// a barrier.
342fn skip_barrier<T: ResourceUses>(old_state: T, new_state: T) -> bool {
343    // If the state didn't change and all the usages are ordered, the hardware
344    // will guarantee the order of accesses, so we do not need to issue a barrier at all
345    old_state == new_state && old_state.all_ordered()
346}
347
348#[derive(Clone, Debug, Error)]
349pub enum ResourceUsageCompatibilityError {
350    #[error("Attempted to use {res} with {invalid_use}.")]
351    Buffer {
352        res: ResourceErrorIdent,
353        invalid_use: InvalidUse<wgt::BufferUses>,
354    },
355    #[error(
356        "Attempted to use {res} (mips {mip_levels:?} layers {array_layers:?}) with {invalid_use}."
357    )]
358    Texture {
359        res: ResourceErrorIdent,
360        mip_levels: ops::Range<u32>,
361        array_layers: ops::Range<u32>,
362        invalid_use: InvalidUse<wgt::TextureUses>,
363    },
364}
365
366impl WebGpuError for ResourceUsageCompatibilityError {
367    fn webgpu_error_type(&self) -> ErrorType {
368        ErrorType::Validation
369    }
370}
371
372impl ResourceUsageCompatibilityError {
373    fn from_buffer(
374        buffer: &resource::Buffer,
375        current_state: wgt::BufferUses,
376        new_state: wgt::BufferUses,
377    ) -> Self {
378        Self::Buffer {
379            res: buffer.error_ident(),
380            invalid_use: InvalidUse {
381                current_state,
382                new_state,
383            },
384        }
385    }
386
387    fn from_texture(
388        texture: &resource::Texture,
389        selector: wgt::TextureSelector,
390        current_state: wgt::TextureUses,
391        new_state: wgt::TextureUses,
392    ) -> Self {
393        Self::Texture {
394            res: texture.error_ident(),
395            mip_levels: selector.mips,
396            array_layers: selector.layers,
397            invalid_use: InvalidUse {
398                current_state,
399                new_state,
400            },
401        }
402    }
403}
404
405/// Pretty print helper that shows helpful descriptions of a conflicting usage.
406#[derive(Clone, Debug, Eq, PartialEq)]
407pub struct InvalidUse<T> {
408    current_state: T,
409    new_state: T,
410}
411
412impl<T: ResourceUses> fmt::Display for InvalidUse<T> {
413    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414        let current = self.current_state;
415        let new = self.new_state;
416
417        let current_exclusive = current & T::EXCLUSIVE;
418        let new_exclusive = new & T::EXCLUSIVE;
419
420        let exclusive = current_exclusive | new_exclusive;
421
422        // The text starts with "tried to use X resource with {self}"
423        write!(
424            f,
425            "conflicting usages. Current usage {current:?} and new usage {new:?}. \
426            {exclusive:?} is an exclusive usage and cannot be used with any other \
427            usages within the usage scope (renderpass or compute dispatch)"
428        )
429    }
430}
431
432/// All the usages that a bind group contains. The uses are not deduplicated in any way
433/// and may include conflicting uses. This is fully compliant by the WebGPU spec.
434///
435/// All bind group states are sorted by their ID so that when adding to a tracker,
436/// they are added in the most efficient order possible (ascending order).
437#[derive(Debug)]
438pub(crate) struct BindGroupStates {
439    pub buffers: BufferBindGroupState,
440    pub views: TextureViewBindGroupState,
441    pub external_textures: StatelessTracker<resource::ExternalTexture>,
442    pub samplers: StatelessTracker<resource::Sampler>,
443    pub acceleration_structures: StatelessTracker<resource::Tlas>,
444}
445
446impl BindGroupStates {
447    pub fn new() -> Self {
448        Self {
449            buffers: BufferBindGroupState::new(),
450            views: TextureViewBindGroupState::new(),
451            external_textures: StatelessTracker::new(),
452            samplers: StatelessTracker::new(),
453            acceleration_structures: StatelessTracker::new(),
454        }
455    }
456
457    /// Optimize the bind group states by sorting them by ID.
458    ///
459    /// When this list of states is merged into a tracker, the memory
460    /// accesses will be in a constant ascending order.
461    pub fn optimize(&mut self) {
462        self.buffers.optimize();
463        // Views are stateless, however, `TextureViewBindGroupState`
464        // is special as it will be merged with other texture trackers.
465        self.views.optimize();
466        // Samplers and Tlas's are stateless and don't need to be optimized
467        // since the tracker is never merged with any other tracker.
468    }
469}
470
471/// This is a render bundle specific usage scope. It includes stateless resources
472/// that are not normally included in a usage scope, but are used by render bundles
473/// and need to be owned by the render bundles.
474#[derive(Debug)]
475pub(crate) struct RenderBundleScope {
476    pub buffers: BufferUsageScope,
477    pub textures: TextureUsageScope,
478    // Don't need to track views and samplers, they are never used directly, only by bind groups.
479    pub bind_groups: StatelessTracker<binding_model::BindGroup>,
480    pub render_pipelines: StatelessTracker<pipeline::RenderPipeline>,
481}
482
483impl RenderBundleScope {
484    /// Create the render bundle scope and pull the maximum IDs from the hubs.
485    pub fn new() -> Self {
486        Self {
487            buffers: BufferUsageScope::default(),
488            textures: TextureUsageScope::default(),
489            bind_groups: StatelessTracker::new(),
490            render_pipelines: StatelessTracker::new(),
491        }
492    }
493
494    /// Merge the inner contents of a bind group into the render bundle tracker.
495    ///
496    /// Only stateful things are merged in herell other resources are owned
497    /// indirectly by the bind group.
498    ///
499    /// # Safety
500    ///
501    /// The maximum ID given by each bind group resource must be less than the
502    /// length of the storage given at the call to `new`.
503    pub unsafe fn merge_bind_group(
504        &mut self,
505        bind_group: &BindGroupStates,
506    ) -> Result<(), ResourceUsageCompatibilityError> {
507        unsafe { self.buffers.merge_bind_group(&bind_group.buffers)? };
508        unsafe { self.textures.merge_bind_group(&bind_group.views)? };
509
510        Ok(())
511    }
512}
513
514/// A pool for storing the memory used by [`UsageScope`]s. We take and store this memory when the
515/// scope is dropped to avoid reallocating. The memory required only grows and allocation cost is
516/// significant when a large number of resources have been used.
517pub(crate) type UsageScopePool = Mutex<Vec<(BufferUsageScope, TextureUsageScope)>>;
518
519/// A usage scope tracker. Only needs to store stateful resources as stateless
520/// resources cannot possibly have a usage conflict.
521#[derive(Debug)]
522pub(crate) struct UsageScope<'a> {
523    pub pool: &'a UsageScopePool,
524    pub buffers: BufferUsageScope,
525    pub textures: TextureUsageScope,
526}
527
528impl<'a> Drop for UsageScope<'a> {
529    fn drop(&mut self) {
530        // clear vecs and push into pool
531        self.buffers.clear();
532        self.textures.clear();
533        self.pool
534            .lock()
535            .push((mem::take(&mut self.buffers), mem::take(&mut self.textures)));
536    }
537}
538
539impl UsageScope<'static> {
540    pub fn new_pooled<'d>(
541        pool: &'d UsageScopePool,
542        tracker_indices: &TrackerIndexAllocators,
543    ) -> UsageScope<'d> {
544        let pooled = pool.lock().pop().unwrap_or_default();
545
546        let mut scope = UsageScope::<'d> {
547            pool,
548            buffers: pooled.0,
549            textures: pooled.1,
550        };
551
552        scope.buffers.set_size(tracker_indices.buffers.size());
553        scope.textures.set_size(tracker_indices.textures.size());
554        scope
555    }
556}
557
558impl<'a> UsageScope<'a> {
559    /// Merge the inner contents of a bind group into the usage scope.
560    ///
561    /// Only stateful things are merged in herell other resources are owned
562    /// indirectly by the bind group.
563    ///
564    /// # Safety
565    ///
566    /// The maximum ID given by each bind group resource must be less than the
567    /// length of the storage given at the call to `new`.
568    pub unsafe fn merge_bind_group(
569        &mut self,
570        bind_group: &BindGroupStates,
571    ) -> Result<(), ResourceUsageCompatibilityError> {
572        unsafe {
573            self.buffers.merge_bind_group(&bind_group.buffers)?;
574            self.textures.merge_bind_group(&bind_group.views)?;
575        }
576
577        Ok(())
578    }
579
580    /// Merge the inner contents of a bind group into the usage scope.
581    ///
582    /// Only stateful things are merged in herell other resources are owned
583    /// indirectly by a bind group or are merged directly into the command buffer tracker.
584    ///
585    /// # Safety
586    ///
587    /// The maximum ID given by each bind group resource must be less than the
588    /// length of the storage given at the call to `new`.
589    pub unsafe fn merge_render_bundle(
590        &mut self,
591        render_bundle: &RenderBundleScope,
592    ) -> Result<(), ResourceUsageCompatibilityError> {
593        self.buffers.merge_usage_scope(&render_bundle.buffers)?;
594        self.textures.merge_usage_scope(&render_bundle.textures)?;
595
596        Ok(())
597    }
598}
599
600/// A tracker used by Device.
601pub(crate) struct DeviceTracker {
602    pub buffers: DeviceBufferTracker,
603    pub textures: DeviceTextureTracker,
604}
605
606impl DeviceTracker {
607    pub fn new() -> Self {
608        Self {
609            buffers: DeviceBufferTracker::new(),
610            textures: DeviceTextureTracker::new(),
611        }
612    }
613}
614
615/// A full double sided tracker used by CommandBuffers.
616pub(crate) struct Tracker {
617    pub buffers: BufferTracker,
618    pub textures: TextureTracker,
619    pub blas_s: BlasTracker,
620    pub tlas_s: StatelessTracker<resource::Tlas>,
621    pub views: StatelessTracker<resource::TextureView>,
622    pub bind_groups: StatelessTracker<binding_model::BindGroup>,
623    pub compute_pipelines: StatelessTracker<pipeline::ComputePipeline>,
624    pub render_pipelines: StatelessTracker<pipeline::RenderPipeline>,
625    pub bundles: StatelessTracker<command::RenderBundle>,
626    pub query_sets: StatelessTracker<resource::QuerySet>,
627}
628
629impl Tracker {
630    pub fn new() -> Self {
631        Self {
632            buffers: BufferTracker::new(),
633            textures: TextureTracker::new(),
634            blas_s: BlasTracker::new(),
635            tlas_s: StatelessTracker::new(),
636            views: StatelessTracker::new(),
637            bind_groups: StatelessTracker::new(),
638            compute_pipelines: StatelessTracker::new(),
639            render_pipelines: StatelessTracker::new(),
640            bundles: StatelessTracker::new(),
641            query_sets: StatelessTracker::new(),
642        }
643    }
644
645    /// Iterates through all resources in the given bind group and adopts
646    /// the state given for those resources in the UsageScope. It also
647    /// removes all touched resources from the usage scope.
648    ///
649    /// If a transition is needed to get the resources into the needed
650    /// state, those transitions are stored within the tracker. A
651    /// subsequent call to [`BufferTracker::drain_transitions`] or
652    /// [`TextureTracker::drain_transitions`] is needed to get those transitions.
653    ///
654    /// This is a really funky method used by Compute Passes to generate
655    /// barriers after a call to dispatch without needing to iterate
656    /// over all elements in the usage scope. We use each the
657    /// bind group as a source of which IDs to look at. The bind groups
658    /// must have first been added to the usage scope.
659    ///
660    /// Only stateful things are merged in here, all other resources are owned
661    /// indirectly by the bind group.
662    ///
663    /// # Safety
664    ///
665    /// The maximum ID given by each bind group resource must be less than the
666    /// value given to `set_size`
667    pub unsafe fn set_and_remove_from_usage_scope_sparse(
668        &mut self,
669        scope: &mut UsageScope,
670        bind_group: &BindGroupStates,
671    ) {
672        unsafe {
673            self.buffers.set_and_remove_from_usage_scope_sparse(
674                &mut scope.buffers,
675                bind_group.buffers.used_tracker_indices(),
676            )
677        };
678        unsafe {
679            self.textures
680                .set_and_remove_from_usage_scope_sparse(&mut scope.textures, &bind_group.views)
681        };
682    }
683}