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}