wgpu_core/device/
life.rs

1use alloc::{sync::Arc, vec::Vec};
2
3use smallvec::SmallVec;
4use thiserror::Error;
5
6use crate::{
7    device::{
8        queue::{EncoderInFlight, SubmittedWorkDoneClosure, TempResource},
9        DeviceError,
10    },
11    ray_tracing::BlasCompactReadyPendingClosure,
12    resource::{Blas, Buffer, QuerySet, Texture, Trackable},
13    snatch::SnatchGuard,
14    SubmissionIndex,
15};
16
17/// A command submitted to the GPU for execution.
18///
19/// ## Keeping resources alive while the GPU is using them
20///
21/// [`wgpu_hal`] requires that, when a command is submitted to a queue, all the
22/// resources it uses must remain alive until it has finished executing.
23///
24/// [`wgpu_hal`]: hal
25struct ActiveSubmission {
26    /// The index of the submission we track.
27    ///
28    /// When `Device::fence`'s value is greater than or equal to this, our queue
29    /// submission has completed.
30    index: SubmissionIndex,
31
32    /// Buffers to be mapped once this submission has completed.
33    mapped: Vec<Arc<Buffer>>,
34
35    /// BLASes to have their compacted size read back once this submission has completed.
36    compact_read_back: Vec<Arc<Blas>>,
37
38    /// Command buffers used by this submission, and the encoder that owns them.
39    ///
40    /// [`wgpu_hal::Queue::submit`] requires the submitted command buffers to
41    /// remain alive until the submission has completed execution. Command
42    /// encoders double as allocation pools for command buffers, so holding them
43    /// here and cleaning them up in [`LifetimeTracker::triage_submissions`]
44    /// satisfies that requirement.
45    ///
46    /// Once this submission has completed, the command buffers are reset and
47    /// the command encoder is recycled.
48    ///
49    /// [`wgpu_hal::Queue::submit`]: hal::Queue::submit
50    encoders: Vec<EncoderInFlight>,
51
52    /// List of queue "on_submitted_work_done" closures to be called once this
53    /// submission has completed.
54    work_done_closures: SmallVec<[SubmittedWorkDoneClosure; 1]>,
55}
56
57impl ActiveSubmission {
58    /// Returns true if this submission contains the given buffer.
59    ///
60    /// This only uses constant-time operations.
61    pub fn contains_buffer(&self, buffer: &Buffer) -> bool {
62        for encoder in &self.encoders {
63            // The ownership location of buffers depends on where the command encoder
64            // came from. If it is the staging command encoder on the queue, it is
65            // in the pending buffer list. If it came from a user command encoder,
66            // it is in the tracker.
67
68            if encoder.trackers.buffers.contains(buffer) {
69                return true;
70            }
71
72            if encoder
73                .pending_buffers
74                .contains_key(&buffer.tracker_index())
75            {
76                return true;
77            }
78        }
79
80        false
81    }
82
83    /// Returns true if this submission contains the given texture.
84    ///
85    /// This only uses constant-time operations.
86    pub fn contains_texture(&self, texture: &Texture) -> bool {
87        for encoder in &self.encoders {
88            // The ownership location of textures depends on where the command encoder
89            // came from. If it is the staging command encoder on the queue, it is
90            // in the pending buffer list. If it came from a user command encoder,
91            // it is in the tracker.
92
93            if encoder.trackers.textures.contains(texture) {
94                return true;
95            }
96
97            if encoder
98                .pending_textures
99                .contains_key(&texture.tracker_index())
100            {
101                return true;
102            }
103        }
104
105        false
106    }
107
108    /// Returns true if this submission contains the given blas.
109    ///
110    /// This only uses constant-time operations.
111    pub fn contains_blas(&self, blas: &Blas) -> bool {
112        for encoder in &self.encoders {
113            if encoder.trackers.blas_s.contains(blas) {
114                return true;
115            }
116
117            if encoder.pending_blas_s.contains_key(&blas.tracker_index()) {
118                return true;
119            }
120        }
121
122        false
123    }
124
125    pub fn contains_query_set(&self, query_set: &QuerySet) -> bool {
126        for encoder in &self.encoders {
127            if encoder.trackers.query_sets.contains(query_set) {
128                return true;
129            }
130        }
131
132        false
133    }
134}
135
136#[derive(Clone, Debug, Error)]
137#[non_exhaustive]
138pub enum WaitIdleError {
139    #[error(transparent)]
140    Device(#[from] DeviceError),
141    #[error("Tried to wait using a submission index ({0}) that has not been returned by a successful submission (last successful submission: {1})")]
142    WrongSubmissionIndex(SubmissionIndex, SubmissionIndex),
143    #[error("Timed out trying to wait for the given submission index.")]
144    Timeout,
145}
146
147impl WaitIdleError {
148    pub fn to_poll_error(&self) -> Option<wgt::PollError> {
149        match self {
150            WaitIdleError::Timeout => Some(wgt::PollError::Timeout),
151            &WaitIdleError::WrongSubmissionIndex(a, b) => {
152                Some(wgt::PollError::WrongSubmissionIndex(a, b))
153            }
154            _ => None,
155        }
156    }
157}
158
159/// Resource tracking for a device.
160///
161/// ## Host mapping buffers
162///
163/// A buffer cannot be mapped until all active queue submissions that use it
164/// have completed. To that end:
165///
166/// -   Each buffer's `ResourceInfo::submission_index` records the index of the
167///     most recent queue submission that uses that buffer.
168///
169/// -   When the device is polled, the following `LifetimeTracker` methods decide
170///     what should happen next:
171///
172///     1)  `triage_submissions` moves entries in `self.active[i]` for completed
173///         submissions to `self.ready_to_map`.  At this point, both
174///         `self.active` and `self.ready_to_map` are up to date with the given
175///         submission index.
176///
177///     2)  `handle_mapping` drains `self.ready_to_map` and actually maps the
178///         buffers, collecting a list of notification closures to call.
179///
180/// Only calling `Global::buffer_map_async` clones a new `Arc` for the
181/// buffer. This new `Arc` is only dropped by `handle_mapping`.
182pub(crate) struct LifetimeTracker {
183    /// Resources used by queue submissions still in flight. One entry per
184    /// submission, with older submissions appearing before younger.
185    ///
186    /// Entries are added by `track_submission` and drained by
187    /// `LifetimeTracker::triage_submissions`. Lots of methods contribute data
188    /// to particular entries.
189    active: Vec<ActiveSubmission>,
190
191    /// Buffers the user has asked us to map, and which are not used by any
192    /// queue submission still in flight.
193    ready_to_map: Vec<Arc<Buffer>>,
194
195    /// BLASes the user has asked us to prepare to compact, and which are not used by any
196    /// queue submission still in flight.
197    ready_to_compact: Vec<Arc<Blas>>,
198
199    /// Queue "on_submitted_work_done" closures that were initiated for while there is no
200    /// currently pending submissions. These cannot be immediately invoked as they
201    /// must happen _after_ all mapped buffer callbacks are mapped, so we defer them
202    /// here until the next time the device is maintained.
203    work_done_closures: SmallVec<[SubmittedWorkDoneClosure; 1]>,
204}
205
206impl LifetimeTracker {
207    pub fn new() -> Self {
208        Self {
209            active: Vec::new(),
210            ready_to_map: Vec::new(),
211            ready_to_compact: Vec::new(),
212            work_done_closures: SmallVec::new(),
213        }
214    }
215
216    /// Return true if there are no queue submissions still in flight.
217    pub fn queue_empty(&self) -> bool {
218        self.active.is_empty()
219    }
220
221    /// Start tracking resources associated with a new queue submission.
222    pub fn track_submission(&mut self, index: SubmissionIndex, encoders: Vec<EncoderInFlight>) {
223        self.active.push(ActiveSubmission {
224            index,
225            mapped: Vec::new(),
226            compact_read_back: Vec::new(),
227            encoders,
228            work_done_closures: SmallVec::new(),
229        });
230    }
231
232    /// Schedule a buffer for mapping.
233    ///
234    /// The buffer will be added either to a pending submission, or to `self.ready_to_map`.
235    /// If it is added to a pending submission, returns the index of that submission.
236    pub(crate) fn map(&mut self, buffer: &Arc<Buffer>) -> Option<SubmissionIndex> {
237        let submission = self
238            .active
239            .iter_mut()
240            .rev()
241            .find(|a| a.contains_buffer(buffer));
242
243        let maybe_submission_index = submission.as_ref().map(|s| s.index);
244
245        submission
246            .map_or(&mut self.ready_to_map, |a| &mut a.mapped)
247            .push(buffer.clone());
248
249        maybe_submission_index
250    }
251
252    pub(crate) fn prepare_compact(&mut self, blas: &Arc<Blas>) -> Option<SubmissionIndex> {
253        // Determine which BLASes are ready to map, and which must wait for the GPU.
254        let submission = self.active.iter_mut().rev().find(|a| a.contains_blas(blas));
255
256        let maybe_submission_index = submission.as_ref().map(|s| s.index);
257
258        submission
259            .map_or(&mut self.ready_to_compact, |a| &mut a.compact_read_back)
260            .push(blas.clone());
261
262        maybe_submission_index
263    }
264
265    /// Returns the submission index of the most recent submission that uses the
266    /// given buffer.
267    pub fn get_buffer_latest_submission_index(&self, buffer: &Buffer) -> Option<SubmissionIndex> {
268        // We iterate in reverse order, so that we can bail out early as soon
269        // as we find a hit.
270        self.active.iter().rev().find_map(|submission| {
271            if submission.contains_buffer(buffer) {
272                Some(submission.index)
273            } else {
274                None
275            }
276        })
277    }
278
279    /// Returns the submission index of the most recent submission that uses the
280    /// given texture.
281    pub fn get_texture_latest_submission_index(
282        &self,
283        texture: &Texture,
284    ) -> Option<SubmissionIndex> {
285        // We iterate in reverse order, so that we can bail out early as soon
286        // as we find a hit.
287        self.active.iter().rev().find_map(|submission| {
288            if submission.contains_texture(texture) {
289                Some(submission.index)
290            } else {
291                None
292            }
293        })
294    }
295
296    /// Returns the submission index of the most recent submission that uses the
297    /// given query set.
298    pub fn get_query_set_latest_submission_index(
299        &self,
300        query_set: &QuerySet,
301    ) -> Option<SubmissionIndex> {
302        // We iterate in reverse order, so that we can bail out early as soon
303        // as we find a hit.
304        self.active.iter().rev().find_map(|submission| {
305            if submission.contains_query_set(query_set) {
306                Some(submission.index)
307            } else {
308                None
309            }
310        })
311    }
312
313    /// Sort out the consequences of completed submissions.
314    ///
315    /// Assume that all submissions up through `last_done` have completed.
316    ///
317    /// -   Buffers used by those submissions are now ready to map, if requested.
318    ///     Add any buffers in the submission's [`mapped`] list to
319    ///     [`self.ready_to_map`], where [`LifetimeTracker::handle_mapping`]
320    ///     will find them.
321    ///
322    /// Return a list of [`SubmittedWorkDoneClosure`]s to run.
323    ///
324    /// [`mapped`]: ActiveSubmission::mapped
325    /// [`self.ready_to_map`]: LifetimeTracker::ready_to_map
326    /// [`SubmittedWorkDoneClosure`]: crate::device::queue::SubmittedWorkDoneClosure
327    #[must_use]
328    pub fn triage_submissions(
329        &mut self,
330        last_done: SubmissionIndex,
331    ) -> SmallVec<[SubmittedWorkDoneClosure; 1]> {
332        profiling::scope!("triage_submissions");
333
334        debug_assert!(self.active.is_sorted_by_key(|a| a.index));
335        let done_count = self.active.partition_point(|a| a.index <= last_done);
336
337        let mut work_done_closures: SmallVec<_> = self.work_done_closures.drain(..).collect();
338        for a in self.active.drain(..done_count) {
339            self.ready_to_map.extend(a.mapped);
340            self.ready_to_compact.extend(a.compact_read_back);
341            for encoder in a.encoders {
342                // This involves actually decrementing the ref count of all command buffer
343                // resources, so can be _very_ expensive.
344                profiling::scope!("drop command buffer trackers");
345                drop(encoder);
346            }
347            work_done_closures.extend(a.work_done_closures);
348        }
349        work_done_closures
350    }
351
352    pub fn schedule_resource_destruction(
353        &mut self,
354        temp_resource: TempResource,
355        last_submit_index: SubmissionIndex,
356    ) {
357        let resources = self
358            .active
359            .iter_mut()
360            .find(|a| a.index == last_submit_index)
361            .map(|a| {
362                // Because this resource's `last_submit_index` matches `a.index`,
363                // we know that we must have done something with the resource,
364                // so `a.encoders` should not be empty.
365                &mut a.encoders.last_mut().unwrap().temp_resources
366            });
367        if let Some(resources) = resources {
368            resources.push(temp_resource);
369        }
370    }
371
372    pub fn add_work_done_closure(
373        &mut self,
374        closure: SubmittedWorkDoneClosure,
375    ) -> Option<SubmissionIndex> {
376        match self.active.last_mut() {
377            Some(active) => {
378                active.work_done_closures.push(closure);
379                Some(active.index)
380            }
381            // We must defer the closure until all previously occurring map_async closures
382            // have fired. This is required by the spec.
383            None => {
384                self.work_done_closures.push(closure);
385                None
386            }
387        }
388    }
389
390    /// Map the buffers in `self.ready_to_map`.
391    ///
392    /// Return a list of mapping notifications to send.
393    ///
394    /// See the documentation for [`LifetimeTracker`] for details.
395    #[must_use]
396    pub(crate) fn handle_mapping(
397        &mut self,
398        snatch_guard: &SnatchGuard,
399    ) -> Vec<super::BufferMapPendingClosure> {
400        if self.ready_to_map.is_empty() {
401            return Vec::new();
402        }
403        let mut pending_callbacks: Vec<super::BufferMapPendingClosure> =
404            Vec::with_capacity(self.ready_to_map.len());
405
406        for buffer in self.ready_to_map.drain(..) {
407            match buffer.map(snatch_guard) {
408                Some(cb) => pending_callbacks.push(cb),
409                None => continue,
410            }
411        }
412        pending_callbacks
413    }
414    /// Read back compact sizes from the BLASes in `self.ready_to_compact`.
415    ///
416    /// Return a list of mapping notifications to send.
417    ///
418    /// See the documentation for [`LifetimeTracker`] for details.
419    #[must_use]
420    pub(crate) fn handle_compact_read_back(&mut self) -> Vec<BlasCompactReadyPendingClosure> {
421        if self.ready_to_compact.is_empty() {
422            return Vec::new();
423        }
424        let mut pending_callbacks: Vec<BlasCompactReadyPendingClosure> =
425            Vec::with_capacity(self.ready_to_compact.len());
426
427        for blas in self.ready_to_compact.drain(..) {
428            match blas.read_back_compact_size() {
429                Some(cb) => pending_callbacks.push(cb),
430                None => continue,
431            }
432        }
433        pending_callbacks
434    }
435}