wgpu_core/device/
life.rs

1#[cfg(feature = "trace")]
2use crate::device::trace;
3use crate::{
4    device::{
5        queue::{EncoderInFlight, SubmittedWorkDoneClosure, TempResource},
6        DeviceError,
7    },
8    hal_api::HalApi,
9    hub::{Hub, Token},
10    id,
11    identity::GlobalIdentityHandlerFactory,
12    resource,
13    track::{BindGroupStates, RenderBundleScope, Tracker},
14    RefCount, Stored, SubmissionIndex,
15};
16use smallvec::SmallVec;
17
18use hal::Device as _;
19use parking_lot::Mutex;
20use thiserror::Error;
21
22use std::mem;
23
24/// A struct that keeps lists of resources that are no longer needed by the user.
25#[derive(Debug, Default)]
26pub(super) struct SuspectedResources {
27    pub(super) buffers: Vec<id::Valid<id::BufferId>>,
28    pub(super) textures: Vec<id::Valid<id::TextureId>>,
29    pub(super) texture_views: Vec<id::Valid<id::TextureViewId>>,
30    pub(super) samplers: Vec<id::Valid<id::SamplerId>>,
31    pub(super) bind_groups: Vec<id::Valid<id::BindGroupId>>,
32    pub(super) compute_pipelines: Vec<id::Valid<id::ComputePipelineId>>,
33    pub(super) render_pipelines: Vec<id::Valid<id::RenderPipelineId>>,
34    pub(super) bind_group_layouts: Vec<id::Valid<id::BindGroupLayoutId>>,
35    pub(super) pipeline_layouts: Vec<Stored<id::PipelineLayoutId>>,
36    pub(super) render_bundles: Vec<id::Valid<id::RenderBundleId>>,
37    pub(super) query_sets: Vec<id::Valid<id::QuerySetId>>,
38}
39
40impl SuspectedResources {
41    pub(super) fn clear(&mut self) {
42        self.buffers.clear();
43        self.textures.clear();
44        self.texture_views.clear();
45        self.samplers.clear();
46        self.bind_groups.clear();
47        self.compute_pipelines.clear();
48        self.render_pipelines.clear();
49        self.bind_group_layouts.clear();
50        self.pipeline_layouts.clear();
51        self.render_bundles.clear();
52        self.query_sets.clear();
53    }
54
55    pub(super) fn extend(&mut self, other: &Self) {
56        self.buffers.extend_from_slice(&other.buffers);
57        self.textures.extend_from_slice(&other.textures);
58        self.texture_views.extend_from_slice(&other.texture_views);
59        self.samplers.extend_from_slice(&other.samplers);
60        self.bind_groups.extend_from_slice(&other.bind_groups);
61        self.compute_pipelines
62            .extend_from_slice(&other.compute_pipelines);
63        self.render_pipelines
64            .extend_from_slice(&other.render_pipelines);
65        self.bind_group_layouts
66            .extend_from_slice(&other.bind_group_layouts);
67        self.pipeline_layouts
68            .extend_from_slice(&other.pipeline_layouts);
69        self.render_bundles.extend_from_slice(&other.render_bundles);
70        self.query_sets.extend_from_slice(&other.query_sets);
71    }
72
73    pub(super) fn add_render_bundle_scope<A: HalApi>(&mut self, trackers: &RenderBundleScope<A>) {
74        self.buffers.extend(trackers.buffers.used());
75        self.textures.extend(trackers.textures.used());
76        self.bind_groups.extend(trackers.bind_groups.used());
77        self.render_pipelines
78            .extend(trackers.render_pipelines.used());
79        self.query_sets.extend(trackers.query_sets.used());
80    }
81
82    pub(super) fn add_bind_group_states<A: HalApi>(&mut self, trackers: &BindGroupStates<A>) {
83        self.buffers.extend(trackers.buffers.used());
84        self.textures.extend(trackers.textures.used());
85        self.texture_views.extend(trackers.views.used());
86        self.samplers.extend(trackers.samplers.used());
87    }
88}
89
90/// Raw backend resources that should be freed shortly.
91#[derive(Debug)]
92struct NonReferencedResources<A: hal::Api> {
93    buffers: Vec<A::Buffer>,
94    textures: Vec<A::Texture>,
95    texture_views: Vec<A::TextureView>,
96    samplers: Vec<A::Sampler>,
97    bind_groups: Vec<A::BindGroup>,
98    compute_pipes: Vec<A::ComputePipeline>,
99    render_pipes: Vec<A::RenderPipeline>,
100    bind_group_layouts: Vec<A::BindGroupLayout>,
101    pipeline_layouts: Vec<A::PipelineLayout>,
102    query_sets: Vec<A::QuerySet>,
103}
104
105impl<A: hal::Api> NonReferencedResources<A> {
106    fn new() -> Self {
107        Self {
108            buffers: Vec::new(),
109            textures: Vec::new(),
110            texture_views: Vec::new(),
111            samplers: Vec::new(),
112            bind_groups: Vec::new(),
113            compute_pipes: Vec::new(),
114            render_pipes: Vec::new(),
115            bind_group_layouts: Vec::new(),
116            pipeline_layouts: Vec::new(),
117            query_sets: Vec::new(),
118        }
119    }
120
121    fn extend(&mut self, other: Self) {
122        self.buffers.extend(other.buffers);
123        self.textures.extend(other.textures);
124        self.texture_views.extend(other.texture_views);
125        self.samplers.extend(other.samplers);
126        self.bind_groups.extend(other.bind_groups);
127        self.compute_pipes.extend(other.compute_pipes);
128        self.render_pipes.extend(other.render_pipes);
129        self.query_sets.extend(other.query_sets);
130        assert!(other.bind_group_layouts.is_empty());
131        assert!(other.pipeline_layouts.is_empty());
132    }
133
134    unsafe fn clean(&mut self, device: &A::Device) {
135        if !self.buffers.is_empty() {
136            profiling::scope!("destroy_buffers");
137            for raw in self.buffers.drain(..) {
138                unsafe { device.destroy_buffer(raw) };
139            }
140        }
141        if !self.textures.is_empty() {
142            profiling::scope!("destroy_textures");
143            for raw in self.textures.drain(..) {
144                unsafe { device.destroy_texture(raw) };
145            }
146        }
147        if !self.texture_views.is_empty() {
148            profiling::scope!("destroy_texture_views");
149            for raw in self.texture_views.drain(..) {
150                unsafe { device.destroy_texture_view(raw) };
151            }
152        }
153        if !self.samplers.is_empty() {
154            profiling::scope!("destroy_samplers");
155            for raw in self.samplers.drain(..) {
156                unsafe { device.destroy_sampler(raw) };
157            }
158        }
159        if !self.bind_groups.is_empty() {
160            profiling::scope!("destroy_bind_groups");
161            for raw in self.bind_groups.drain(..) {
162                unsafe { device.destroy_bind_group(raw) };
163            }
164        }
165        if !self.compute_pipes.is_empty() {
166            profiling::scope!("destroy_compute_pipelines");
167            for raw in self.compute_pipes.drain(..) {
168                unsafe { device.destroy_compute_pipeline(raw) };
169            }
170        }
171        if !self.render_pipes.is_empty() {
172            profiling::scope!("destroy_render_pipelines");
173            for raw in self.render_pipes.drain(..) {
174                unsafe { device.destroy_render_pipeline(raw) };
175            }
176        }
177        if !self.bind_group_layouts.is_empty() {
178            profiling::scope!("destroy_bind_group_layouts");
179            for raw in self.bind_group_layouts.drain(..) {
180                unsafe { device.destroy_bind_group_layout(raw) };
181            }
182        }
183        if !self.pipeline_layouts.is_empty() {
184            profiling::scope!("destroy_pipeline_layouts");
185            for raw in self.pipeline_layouts.drain(..) {
186                unsafe { device.destroy_pipeline_layout(raw) };
187            }
188        }
189        if !self.query_sets.is_empty() {
190            profiling::scope!("destroy_query_sets");
191            for raw in self.query_sets.drain(..) {
192                unsafe { device.destroy_query_set(raw) };
193            }
194        }
195    }
196}
197
198/// Resources used by a queue submission, and work to be done once it completes.
199struct ActiveSubmission<A: hal::Api> {
200    /// The index of the submission we track.
201    ///
202    /// When `Device::fence`'s value is greater than or equal to this, our queue
203    /// submission has completed.
204    index: SubmissionIndex,
205
206    /// Resources to be freed once this queue submission has completed.
207    ///
208    /// When the device is polled, for completed submissions,
209    /// `triage_submissions` merges these into
210    /// `LifetimeTracker::free_resources`. From there,
211    /// `LifetimeTracker::cleanup` passes them to the hal to be freed.
212    ///
213    /// This includes things like temporary resources and resources that are
214    /// used by submitted commands but have been dropped by the user (meaning that
215    /// this submission is their last reference.)
216    last_resources: NonReferencedResources<A>,
217
218    /// Buffers to be mapped once this submission has completed.
219    mapped: Vec<id::Valid<id::BufferId>>,
220
221    encoders: Vec<EncoderInFlight<A>>,
222    work_done_closures: SmallVec<[SubmittedWorkDoneClosure; 1]>,
223}
224
225#[derive(Clone, Debug, Error)]
226#[non_exhaustive]
227pub enum WaitIdleError {
228    #[error(transparent)]
229    Device(#[from] DeviceError),
230    #[error("Tried to wait using a submission index from the wrong device. Submission index is from device {0:?}. Called poll on device {1:?}.")]
231    WrongSubmissionIndex(id::QueueId, id::DeviceId),
232    #[error("GPU got stuck :(")]
233    StuckGpu,
234}
235
236/// Resource tracking for a device.
237///
238/// ## Host mapping buffers
239///
240/// A buffer cannot be mapped until all active queue submissions that use it
241/// have completed. To that end:
242///
243/// -   Each buffer's `LifeGuard::submission_index` records the index of the
244///     most recent queue submission that uses that buffer.
245///
246/// -   Calling `map_async` adds the buffer to `self.mapped`, and changes
247///     `Buffer::map_state` to prevent it from being used in any new
248///     submissions.
249///
250/// -   When the device is polled, the following `LifetimeTracker` methods decide
251///     what should happen next:
252///
253///     1)  `triage_mapped` drains `self.mapped`, checking the submission index
254///         of each buffer against the queue submissions that have finished
255///         execution. Buffers used by submissions still in flight go in
256///         `self.active[index].mapped`, and the rest go into
257///         `self.ready_to_map`.
258///
259///     2)  `triage_submissions` moves entries in `self.active[i]` for completed
260///         submissions to `self.ready_to_map`.  At this point, both
261///         `self.active` and `self.ready_to_map` are up to date with the given
262///         submission index.
263///
264///     3)  `handle_mapping` drains `self.ready_to_map` and actually maps the
265///         buffers, collecting a list of notification closures to call. But any
266///         buffers that were dropped by the user get moved to
267///         `self.free_resources`.
268///
269///     4)  `cleanup` frees everything in `free_resources`.
270///
271/// Only `self.mapped` holds a `RefCount` for the buffer; it is dropped by
272/// `triage_mapped`.
273pub(super) struct LifetimeTracker<A: hal::Api> {
274    /// Resources that the user has requested be mapped, but which are used by
275    /// queue submissions still in flight.
276    mapped: Vec<Stored<id::BufferId>>,
277
278    /// Buffers can be used in a submission that is yet to be made, by the
279    /// means of `write_buffer()`, so we have a special place for them.
280    pub future_suspected_buffers: Vec<Stored<id::BufferId>>,
281
282    /// Textures can be used in the upcoming submission by `write_texture`.
283    pub future_suspected_textures: Vec<Stored<id::TextureId>>,
284
285    /// Resources whose user handle has died (i.e. drop/destroy has been called)
286    /// and will likely be ready for destruction soon.
287    pub suspected_resources: SuspectedResources,
288
289    /// Resources used by queue submissions still in flight. One entry per
290    /// submission, with older submissions appearing before younger.
291    ///
292    /// Entries are added by `track_submission` and drained by
293    /// `LifetimeTracker::triage_submissions`. Lots of methods contribute data
294    /// to particular entries.
295    active: Vec<ActiveSubmission<A>>,
296
297    /// Raw backend resources that are neither referenced nor used.
298    ///
299    /// These are freed by `LifeTracker::cleanup`, which is called from periodic
300    /// maintenance functions like `Global::device_poll`, and when a device is
301    /// destroyed.
302    free_resources: NonReferencedResources<A>,
303
304    /// Buffers the user has asked us to map, and which are not used by any
305    /// queue submission still in flight.
306    ready_to_map: Vec<id::Valid<id::BufferId>>,
307}
308
309impl<A: hal::Api> LifetimeTracker<A> {
310    pub fn new() -> Self {
311        Self {
312            mapped: Vec::new(),
313            future_suspected_buffers: Vec::new(),
314            future_suspected_textures: Vec::new(),
315            suspected_resources: SuspectedResources::default(),
316            active: Vec::new(),
317            free_resources: NonReferencedResources::new(),
318            ready_to_map: Vec::new(),
319        }
320    }
321
322    /// Return true if there are no queue submissions still in flight.
323    pub fn queue_empty(&self) -> bool {
324        self.active.is_empty()
325    }
326
327    /// Start tracking resources associated with a new queue submission.
328    pub fn track_submission(
329        &mut self,
330        index: SubmissionIndex,
331        temp_resources: impl Iterator<Item = TempResource<A>>,
332        encoders: Vec<EncoderInFlight<A>>,
333    ) {
334        let mut last_resources = NonReferencedResources::new();
335        for res in temp_resources {
336            match res {
337                TempResource::Buffer(raw) => last_resources.buffers.push(raw),
338                TempResource::Texture(raw, views) => {
339                    last_resources.textures.push(raw);
340                    last_resources.texture_views.extend(views);
341                }
342            }
343        }
344
345        self.active.push(ActiveSubmission {
346            index,
347            last_resources,
348            mapped: Vec::new(),
349            encoders,
350            work_done_closures: SmallVec::new(),
351        });
352    }
353
354    pub fn post_submit(&mut self) {
355        self.suspected_resources.buffers.extend(
356            self.future_suspected_buffers
357                .drain(..)
358                .map(|stored| stored.value),
359        );
360        self.suspected_resources.textures.extend(
361            self.future_suspected_textures
362                .drain(..)
363                .map(|stored| stored.value),
364        );
365    }
366
367    pub(crate) fn map(&mut self, value: id::Valid<id::BufferId>, ref_count: RefCount) {
368        self.mapped.push(Stored { value, ref_count });
369    }
370
371    /// Sort out the consequences of completed submissions.
372    ///
373    /// Assume that all submissions up through `last_done` have completed.
374    ///
375    /// -   Buffers used by those submissions are now ready to map, if
376    ///     requested. Add any buffers in the submission's [`mapped`] list to
377    ///     [`self.ready_to_map`], where [`LifetimeTracker::handle_mapping`] will find
378    ///     them.
379    ///
380    /// -   Resources whose final use was in those submissions are now ready to
381    ///     free. Add any resources in the submission's [`last_resources`] table
382    ///     to [`self.free_resources`], where [`LifetimeTracker::cleanup`] will find
383    ///     them.
384    ///
385    /// Return a list of [`SubmittedWorkDoneClosure`]s to run.
386    ///
387    /// [`mapped`]: ActiveSubmission::mapped
388    /// [`self.ready_to_map`]: LifetimeTracker::ready_to_map
389    /// [`last_resources`]: ActiveSubmission::last_resources
390    /// [`self.free_resources`]: LifetimeTracker::free_resources
391    /// [`SubmittedWorkDoneClosure`]: crate::device::queue::SubmittedWorkDoneClosure
392    #[must_use]
393    pub fn triage_submissions(
394        &mut self,
395        last_done: SubmissionIndex,
396        command_allocator: &Mutex<super::CommandAllocator<A>>,
397    ) -> SmallVec<[SubmittedWorkDoneClosure; 1]> {
398        profiling::scope!("triage_submissions");
399
400        //TODO: enable when `is_sorted_by_key` is stable
401        //debug_assert!(self.active.is_sorted_by_key(|a| a.index));
402        let done_count = self
403            .active
404            .iter()
405            .position(|a| a.index > last_done)
406            .unwrap_or(self.active.len());
407
408        let mut work_done_closures = SmallVec::new();
409        for a in self.active.drain(..done_count) {
410            log::trace!("Active submission {} is done", a.index);
411            self.free_resources.extend(a.last_resources);
412            self.ready_to_map.extend(a.mapped);
413            for encoder in a.encoders {
414                let raw = unsafe { encoder.land() };
415                command_allocator.lock().release_encoder(raw);
416            }
417            work_done_closures.extend(a.work_done_closures);
418        }
419        work_done_closures
420    }
421
422    pub fn cleanup(&mut self, device: &A::Device) {
423        profiling::scope!("LifetimeTracker::cleanup");
424        unsafe {
425            self.free_resources.clean(device);
426        }
427    }
428
429    pub fn schedule_resource_destruction(
430        &mut self,
431        temp_resource: TempResource<A>,
432        last_submit_index: SubmissionIndex,
433    ) {
434        let resources = self
435            .active
436            .iter_mut()
437            .find(|a| a.index == last_submit_index)
438            .map_or(&mut self.free_resources, |a| &mut a.last_resources);
439        match temp_resource {
440            TempResource::Buffer(raw) => resources.buffers.push(raw),
441            TempResource::Texture(raw, views) => {
442                resources.texture_views.extend(views);
443                resources.textures.push(raw);
444            }
445        }
446    }
447
448    pub fn add_work_done_closure(
449        &mut self,
450        closure: SubmittedWorkDoneClosure,
451    ) -> Option<SubmittedWorkDoneClosure> {
452        match self.active.last_mut() {
453            Some(active) => {
454                active.work_done_closures.push(closure);
455                None
456            }
457            // Note: we can't immediately invoke the closure, since it assumes
458            // nothing is currently locked in the hubs.
459            None => Some(closure),
460        }
461    }
462}
463
464impl<A: HalApi> LifetimeTracker<A> {
465    /// Identify resources to free, according to `trackers` and `self.suspected_resources`.
466    ///
467    /// Given `trackers`, the [`Tracker`] belonging to same [`Device`] as
468    /// `self`, and `hub`, the [`Hub`] to which that `Device` belongs:
469    ///
470    /// Remove from `trackers` each resource mentioned in
471    /// [`self.suspected_resources`]. If `trackers` held the final reference to
472    /// that resource, add it to the appropriate free list, to be destroyed by
473    /// the hal:
474    ///
475    /// -   Add resources used by queue submissions still in flight to the
476    ///     [`last_resources`] table of the last such submission's entry in
477    ///     [`self.active`]. When that submission has finished execution. the
478    ///     [`triage_submissions`] method will move them to
479    ///     [`self.free_resources`].
480    ///
481    /// -   Add resources that can be freed right now to [`self.free_resources`]
482    ///     directly. [`LifetimeTracker::cleanup`] will take care of them as
483    ///     part of this poll.
484    ///
485    /// ## Entrained resources
486    ///
487    /// This function finds resources that are used only by other resources
488    /// ready to be freed, and adds those to the free lists as well. For
489    /// example, if there's some texture `T` used only by some texture view
490    /// `TV`, then if `TV` can be freed, `T` gets added to the free lists too.
491    ///
492    /// Since `wgpu-core` resource ownership patterns are acyclic, we can visit
493    /// each type that can be owned after all types that could possibly own
494    /// it. This way, we can detect all free-able objects in a single pass,
495    /// simply by starting with types that are roots of the ownership DAG (like
496    /// render bundles) and working our way towards leaf types (like buffers).
497    ///
498    /// [`Device`]: super::Device
499    /// [`self.suspected_resources`]: LifetimeTracker::suspected_resources
500    /// [`last_resources`]: ActiveSubmission::last_resources
501    /// [`self.active`]: LifetimeTracker::active
502    /// [`triage_submissions`]: LifetimeTracker::triage_submissions
503    /// [`self.free_resources`]: LifetimeTracker::free_resources
504    pub(super) fn triage_suspected<G: GlobalIdentityHandlerFactory>(
505        &mut self,
506        hub: &Hub<A, G>,
507        trackers: &Mutex<Tracker<A>>,
508        #[cfg(feature = "trace")] trace: Option<&Mutex<trace::Trace>>,
509        token: &mut Token<super::Device<A>>,
510    ) {
511        profiling::scope!("triage_suspected");
512
513        if !self.suspected_resources.render_bundles.is_empty() {
514            let (mut guard, _) = hub.render_bundles.write(token);
515            let mut trackers = trackers.lock();
516
517            while let Some(id) = self.suspected_resources.render_bundles.pop() {
518                if trackers.bundles.remove_abandoned(id) {
519                    log::debug!("Bundle {:?} will be destroyed", id);
520                    #[cfg(feature = "trace")]
521                    if let Some(t) = trace {
522                        t.lock().add(trace::Action::DestroyRenderBundle(id.0));
523                    }
524
525                    if let Some(res) = hub.render_bundles.unregister_locked(id.0, &mut *guard) {
526                        self.suspected_resources.add_render_bundle_scope(&res.used);
527                    }
528                }
529            }
530        }
531
532        if !self.suspected_resources.bind_groups.is_empty() {
533            let (mut guard, _) = hub.bind_groups.write(token);
534            let mut trackers = trackers.lock();
535
536            while let Some(id) = self.suspected_resources.bind_groups.pop() {
537                if trackers.bind_groups.remove_abandoned(id) {
538                    log::debug!("Bind group {:?} will be destroyed", id);
539                    #[cfg(feature = "trace")]
540                    if let Some(t) = trace {
541                        t.lock().add(trace::Action::DestroyBindGroup(id.0));
542                    }
543
544                    if let Some(res) = hub.bind_groups.unregister_locked(id.0, &mut *guard) {
545                        self.suspected_resources.add_bind_group_states(&res.used);
546
547                        self.suspected_resources
548                            .bind_group_layouts
549                            .push(res.layout_id);
550
551                        let submit_index = res.life_guard.life_count();
552                        self.active
553                            .iter_mut()
554                            .find(|a| a.index == submit_index)
555                            .map_or(&mut self.free_resources, |a| &mut a.last_resources)
556                            .bind_groups
557                            .push(res.raw);
558                    }
559                }
560            }
561        }
562
563        if !self.suspected_resources.texture_views.is_empty() {
564            let (mut guard, _) = hub.texture_views.write(token);
565            let mut trackers = trackers.lock();
566
567            let mut list = mem::take(&mut self.suspected_resources.texture_views);
568            for id in list.drain(..) {
569                if trackers.views.remove_abandoned(id) {
570                    log::debug!("Texture view {:?} will be destroyed", id);
571                    #[cfg(feature = "trace")]
572                    if let Some(t) = trace {
573                        t.lock().add(trace::Action::DestroyTextureView(id.0));
574                    }
575
576                    if let Some(res) = hub.texture_views.unregister_locked(id.0, &mut *guard) {
577                        self.suspected_resources.textures.push(res.parent_id.value);
578                        let submit_index = res.life_guard.life_count();
579                        self.active
580                            .iter_mut()
581                            .find(|a| a.index == submit_index)
582                            .map_or(&mut self.free_resources, |a| &mut a.last_resources)
583                            .texture_views
584                            .push(res.raw);
585                    }
586                }
587            }
588            self.suspected_resources.texture_views = list;
589        }
590
591        if !self.suspected_resources.textures.is_empty() {
592            let (mut guard, _) = hub.textures.write(token);
593            let mut trackers = trackers.lock();
594
595            for id in self.suspected_resources.textures.drain(..) {
596                if trackers.textures.remove_abandoned(id) {
597                    log::debug!("Texture {:?} will be destroyed", id);
598                    #[cfg(feature = "trace")]
599                    if let Some(t) = trace {
600                        t.lock().add(trace::Action::DestroyTexture(id.0));
601                    }
602
603                    if let Some(res) = hub.textures.unregister_locked(id.0, &mut *guard) {
604                        let submit_index = res.life_guard.life_count();
605                        let raw = match res.inner {
606                            resource::TextureInner::Native { raw: Some(raw) } => raw,
607                            _ => continue,
608                        };
609                        let non_referenced_resources = self
610                            .active
611                            .iter_mut()
612                            .find(|a| a.index == submit_index)
613                            .map_or(&mut self.free_resources, |a| &mut a.last_resources);
614
615                        non_referenced_resources.textures.push(raw);
616                        if let resource::TextureClearMode::RenderPass { clear_views, .. } =
617                            res.clear_mode
618                        {
619                            non_referenced_resources
620                                .texture_views
621                                .extend(clear_views.into_iter());
622                        }
623                    }
624                }
625            }
626        }
627
628        if !self.suspected_resources.samplers.is_empty() {
629            let (mut guard, _) = hub.samplers.write(token);
630            let mut trackers = trackers.lock();
631
632            for id in self.suspected_resources.samplers.drain(..) {
633                if trackers.samplers.remove_abandoned(id) {
634                    log::debug!("Sampler {:?} will be destroyed", id);
635                    #[cfg(feature = "trace")]
636                    if let Some(t) = trace {
637                        t.lock().add(trace::Action::DestroySampler(id.0));
638                    }
639
640                    if let Some(res) = hub.samplers.unregister_locked(id.0, &mut *guard) {
641                        let submit_index = res.life_guard.life_count();
642                        self.active
643                            .iter_mut()
644                            .find(|a| a.index == submit_index)
645                            .map_or(&mut self.free_resources, |a| &mut a.last_resources)
646                            .samplers
647                            .push(res.raw);
648                    }
649                }
650            }
651        }
652
653        if !self.suspected_resources.buffers.is_empty() {
654            let (mut guard, _) = hub.buffers.write(token);
655            let mut trackers = trackers.lock();
656
657            for id in self.suspected_resources.buffers.drain(..) {
658                if trackers.buffers.remove_abandoned(id) {
659                    log::debug!("Buffer {:?} will be destroyed", id);
660                    #[cfg(feature = "trace")]
661                    if let Some(t) = trace {
662                        t.lock().add(trace::Action::DestroyBuffer(id.0));
663                    }
664
665                    if let Some(res) = hub.buffers.unregister_locked(id.0, &mut *guard) {
666                        let submit_index = res.life_guard.life_count();
667                        if let resource::BufferMapState::Init { stage_buffer, .. } = res.map_state {
668                            self.free_resources.buffers.push(stage_buffer);
669                        }
670                        self.active
671                            .iter_mut()
672                            .find(|a| a.index == submit_index)
673                            .map_or(&mut self.free_resources, |a| &mut a.last_resources)
674                            .buffers
675                            .extend(res.raw);
676                    }
677                }
678            }
679        }
680
681        if !self.suspected_resources.compute_pipelines.is_empty() {
682            let (mut guard, _) = hub.compute_pipelines.write(token);
683            let mut trackers = trackers.lock();
684
685            for id in self.suspected_resources.compute_pipelines.drain(..) {
686                if trackers.compute_pipelines.remove_abandoned(id) {
687                    log::debug!("Compute pipeline {:?} will be destroyed", id);
688                    #[cfg(feature = "trace")]
689                    if let Some(t) = trace {
690                        t.lock().add(trace::Action::DestroyComputePipeline(id.0));
691                    }
692
693                    if let Some(res) = hub.compute_pipelines.unregister_locked(id.0, &mut *guard) {
694                        let submit_index = res.life_guard.life_count();
695                        self.active
696                            .iter_mut()
697                            .find(|a| a.index == submit_index)
698                            .map_or(&mut self.free_resources, |a| &mut a.last_resources)
699                            .compute_pipes
700                            .push(res.raw);
701                    }
702                }
703            }
704        }
705
706        if !self.suspected_resources.render_pipelines.is_empty() {
707            let (mut guard, _) = hub.render_pipelines.write(token);
708            let mut trackers = trackers.lock();
709
710            for id in self.suspected_resources.render_pipelines.drain(..) {
711                if trackers.render_pipelines.remove_abandoned(id) {
712                    log::debug!("Render pipeline {:?} will be destroyed", id);
713                    #[cfg(feature = "trace")]
714                    if let Some(t) = trace {
715                        t.lock().add(trace::Action::DestroyRenderPipeline(id.0));
716                    }
717
718                    if let Some(res) = hub.render_pipelines.unregister_locked(id.0, &mut *guard) {
719                        let submit_index = res.life_guard.life_count();
720                        self.active
721                            .iter_mut()
722                            .find(|a| a.index == submit_index)
723                            .map_or(&mut self.free_resources, |a| &mut a.last_resources)
724                            .render_pipes
725                            .push(res.raw);
726                    }
727                }
728            }
729        }
730
731        if !self.suspected_resources.pipeline_layouts.is_empty() {
732            let (mut guard, _) = hub.pipeline_layouts.write(token);
733
734            for Stored {
735                value: id,
736                ref_count,
737            } in self.suspected_resources.pipeline_layouts.drain(..)
738            {
739                //Note: this has to happen after all the suspected pipelines are destroyed
740                if ref_count.load() == 1 {
741                    log::debug!("Pipeline layout {:?} will be destroyed", id);
742                    #[cfg(feature = "trace")]
743                    if let Some(t) = trace {
744                        t.lock().add(trace::Action::DestroyPipelineLayout(id.0));
745                    }
746
747                    if let Some(lay) = hub.pipeline_layouts.unregister_locked(id.0, &mut *guard) {
748                        self.suspected_resources
749                            .bind_group_layouts
750                            .extend_from_slice(&lay.bind_group_layout_ids);
751                        self.free_resources.pipeline_layouts.push(lay.raw);
752                    }
753                }
754            }
755        }
756
757        if !self.suspected_resources.bind_group_layouts.is_empty() {
758            let (mut guard, _) = hub.bind_group_layouts.write(token);
759
760            for id in self.suspected_resources.bind_group_layouts.drain(..) {
761                //Note: this has to happen after all the suspected pipelines are destroyed
762                //Note: nothing else can bump the refcount since the guard is locked exclusively
763                //Note: same BGL can appear multiple times in the list, but only the last
764                // encounter could drop the refcount to 0.
765                if guard[id].multi_ref_count.dec_and_check_empty() {
766                    log::debug!("Bind group layout {:?} will be destroyed", id);
767                    #[cfg(feature = "trace")]
768                    if let Some(t) = trace {
769                        t.lock().add(trace::Action::DestroyBindGroupLayout(id.0));
770                    }
771                    if let Some(lay) = hub.bind_group_layouts.unregister_locked(id.0, &mut *guard) {
772                        self.free_resources.bind_group_layouts.push(lay.raw);
773                    }
774                }
775            }
776        }
777
778        if !self.suspected_resources.query_sets.is_empty() {
779            let (mut guard, _) = hub.query_sets.write(token);
780            let mut trackers = trackers.lock();
781
782            for id in self.suspected_resources.query_sets.drain(..) {
783                if trackers.query_sets.remove_abandoned(id) {
784                    log::debug!("Query set {:?} will be destroyed", id);
785                    // #[cfg(feature = "trace")]
786                    // trace.map(|t| t.lock().add(trace::Action::DestroyComputePipeline(id.0)));
787                    if let Some(res) = hub.query_sets.unregister_locked(id.0, &mut *guard) {
788                        let submit_index = res.life_guard.life_count();
789                        self.active
790                            .iter_mut()
791                            .find(|a| a.index == submit_index)
792                            .map_or(&mut self.free_resources, |a| &mut a.last_resources)
793                            .query_sets
794                            .push(res.raw);
795                    }
796                }
797            }
798        }
799    }
800
801    /// Determine which buffers are ready to map, and which must wait for the
802    /// GPU.
803    ///
804    /// See the documentation for [`LifetimeTracker`] for details.
805    pub(super) fn triage_mapped<G: GlobalIdentityHandlerFactory>(
806        &mut self,
807        hub: &Hub<A, G>,
808        token: &mut Token<super::Device<A>>,
809    ) {
810        if self.mapped.is_empty() {
811            return;
812        }
813        let (buffer_guard, _) = hub.buffers.read(token);
814
815        for stored in self.mapped.drain(..) {
816            let resource_id = stored.value;
817            let buf = &buffer_guard[resource_id];
818
819            let submit_index = buf.life_guard.life_count();
820            log::trace!(
821                "Mapping of {:?} at submission {:?} gets assigned to active {:?}",
822                resource_id,
823                submit_index,
824                self.active.iter().position(|a| a.index == submit_index)
825            );
826
827            self.active
828                .iter_mut()
829                .find(|a| a.index == submit_index)
830                .map_or(&mut self.ready_to_map, |a| &mut a.mapped)
831                .push(resource_id);
832        }
833    }
834
835    /// Map the buffers in `self.ready_to_map`.
836    ///
837    /// Return a list of mapping notifications to send.
838    ///
839    /// See the documentation for [`LifetimeTracker`] for details.
840    #[must_use]
841    pub(super) fn handle_mapping<G: GlobalIdentityHandlerFactory>(
842        &mut self,
843        hub: &Hub<A, G>,
844        raw: &A::Device,
845        trackers: &Mutex<Tracker<A>>,
846        token: &mut Token<super::Device<A>>,
847    ) -> Vec<super::BufferMapPendingClosure> {
848        if self.ready_to_map.is_empty() {
849            return Vec::new();
850        }
851        let (mut buffer_guard, _) = hub.buffers.write(token);
852        let mut pending_callbacks: Vec<super::BufferMapPendingClosure> =
853            Vec::with_capacity(self.ready_to_map.len());
854        let mut trackers = trackers.lock();
855        for buffer_id in self.ready_to_map.drain(..) {
856            let buffer = &mut buffer_guard[buffer_id];
857            if buffer.life_guard.ref_count.is_none() && trackers.buffers.remove_abandoned(buffer_id)
858            {
859                buffer.map_state = resource::BufferMapState::Idle;
860                log::debug!("Mapping request is dropped because the buffer is destroyed.");
861                if let Some(buf) = hub
862                    .buffers
863                    .unregister_locked(buffer_id.0, &mut *buffer_guard)
864                {
865                    self.free_resources.buffers.extend(buf.raw);
866                }
867            } else {
868                let mapping = match std::mem::replace(
869                    &mut buffer.map_state,
870                    resource::BufferMapState::Idle,
871                ) {
872                    resource::BufferMapState::Waiting(pending_mapping) => pending_mapping,
873                    // Mapping cancelled
874                    resource::BufferMapState::Idle => continue,
875                    // Mapping queued at least twice by map -> unmap -> map
876                    // and was already successfully mapped below
877                    active @ resource::BufferMapState::Active { .. } => {
878                        buffer.map_state = active;
879                        continue;
880                    }
881                    _ => panic!("No pending mapping."),
882                };
883                let status = if mapping.range.start != mapping.range.end {
884                    log::debug!("Buffer {:?} map state -> Active", buffer_id);
885                    let host = mapping.op.host;
886                    let size = mapping.range.end - mapping.range.start;
887                    match super::map_buffer(raw, buffer, mapping.range.start, size, host) {
888                        Ok(ptr) => {
889                            buffer.map_state = resource::BufferMapState::Active {
890                                ptr,
891                                range: mapping.range.start..mapping.range.start + size,
892                                host,
893                            };
894                            Ok(())
895                        }
896                        Err(e) => {
897                            log::error!("Mapping failed {:?}", e);
898                            Err(e)
899                        }
900                    }
901                } else {
902                    buffer.map_state = resource::BufferMapState::Active {
903                        ptr: std::ptr::NonNull::dangling(),
904                        range: mapping.range,
905                        host: mapping.op.host,
906                    };
907                    Ok(())
908                };
909                pending_callbacks.push((mapping.op, status));
910            }
911        }
912        pending_callbacks
913    }
914}