Skip to main content

wgpu_core/device/
queue.rs

1#[cfg(feature = "trace")]
2use alloc::string::ToString as _;
3use alloc::{boxed::Box, string::String, sync::Arc, vec, vec::Vec};
4use core::{
5    mem::{self, ManuallyDrop},
6    num::NonZeroU64,
7    ptr::NonNull,
8    sync::atomic::Ordering,
9};
10use smallvec::SmallVec;
11use thiserror::Error;
12use wgt::{
13    error::{ErrorType, WebGpuError},
14    AccelerationStructureFlags,
15};
16
17use super::{life::LifetimeTracker, Device};
18#[cfg(feature = "trace")]
19use crate::device::trace::{Action, IntoTrace};
20use crate::{
21    api_log,
22    command::{
23        extract_texture_selector, validate_linear_texture_data, validate_texture_buffer_copy,
24        validate_texture_copy_dst_format, validate_texture_copy_range, ClearError,
25        CommandAllocator, CommandBuffer, CommandEncoder, CommandEncoderError, CopySide,
26        TransferError,
27    },
28    device::{DeviceError, QueueDescriptor, WaitIdleError},
29    get_lowest_common_denom, hal_label,
30    init_tracker::{has_copy_partial_init_tracker_coverage, TextureInitRange},
31    lock::{rank, Mutex, MutexGuard, RwLock, RwLockWriteGuard},
32    ray_tracing::{BlasCompactReadyPendingClosure, CompactBlasError},
33    resource::{
34        Blas, BlasCompactState, BlasDescriptor, BlasState, Buffer, BufferAccessError,
35        BufferMapState, DestroyedBuffer, DestroyedQuerySet, DestroyedResourceError,
36        DestroyedTexture, FlushedStagingBuffer, InvalidOrDestroyedResourceError,
37        InvalidResourceError, Labeled, ParentDevice, ResourceErrorIdent, ResourceState,
38        StagingBuffer, Texture, TextureInner, Trackable, TrackingData,
39    },
40    resource_log,
41    scratch::ScratchBuffer,
42    snatch::{SnatchGuard, Snatchable},
43    track::{self, Tracker, TrackerIndex},
44    FastHashMap, LabelHelpers, SubmissionIndex,
45};
46use crate::{device::resource::CommandIndices, resource::RawResourceAccess};
47
48pub struct Queue {
49    raw: Box<dyn hal::DynQueue>,
50    pub(crate) pending_writes: Mutex<PendingWrites>,
51    life_tracker: Mutex<LifetimeTracker>,
52    label: String,
53    // The device needs to be dropped last (`Device.zero_buffer` might be referenced by the encoder in pending writes).
54    pub(crate) device: Arc<Device>,
55}
56
57impl Queue {
58    pub(crate) fn new(
59        device: Arc<Device>,
60        raw: Box<dyn hal::DynQueue>,
61        desc: QueueDescriptor,
62        instance_flags: wgt::InstanceFlags,
63    ) -> Result<Self, DeviceError> {
64        let pending_encoder = device
65            .command_allocator
66            .acquire_encoder(device.raw(), raw.as_ref())
67            .map_err(DeviceError::from_hal);
68
69        let pending_encoder = match pending_encoder {
70            Ok(pending_encoder) => pending_encoder,
71            Err(e) => {
72                return Err(e);
73            }
74        };
75
76        let mut pending_writes = PendingWrites::new(pending_encoder, instance_flags);
77
78        let zero_buffer = device.zero_buffer.as_ref();
79        pending_writes.activate();
80        unsafe {
81            pending_writes
82                .command_encoder
83                .transition_buffers(&[hal::BufferBarrier {
84                    buffer: zero_buffer,
85                    usage: hal::StateTransition {
86                        from: wgt::BufferUses::empty(),
87                        to: wgt::BufferUses::COPY_DST,
88                    },
89                }]);
90            pending_writes
91                .command_encoder
92                .clear_buffer(zero_buffer, 0..super::ZERO_BUFFER_SIZE);
93            pending_writes
94                .command_encoder
95                .transition_buffers(&[hal::BufferBarrier {
96                    buffer: zero_buffer,
97                    usage: hal::StateTransition {
98                        from: wgt::BufferUses::COPY_DST,
99                        to: wgt::BufferUses::COPY_SRC,
100                    },
101                }]);
102        }
103
104        Ok(Queue {
105            raw,
106            device,
107            label: desc.label.to_string(),
108            pending_writes: Mutex::new(rank::QUEUE_PENDING_WRITES, pending_writes),
109            life_tracker: Mutex::new(rank::QUEUE_LIFE_TRACKER, LifetimeTracker::new()),
110        })
111    }
112
113    pub(crate) fn raw(&self) -> &dyn hal::DynQueue {
114        self.raw.as_ref()
115    }
116
117    #[track_caller]
118    pub(crate) fn lock_life<'a>(&'a self) -> MutexGuard<'a, LifetimeTracker> {
119        self.life_tracker.lock()
120    }
121
122    /// Ensure the surface texture is in the PRESENT state, clearing it if it was never rendered to.
123    /// Submits any necessary work to the GPU before the HAL present call.
124    ///
125    /// See <https://github.com/gfx-rs/wgpu/issues/6748>
126    pub(crate) fn prepare_surface_texture_for_present(
127        &self,
128        texture: &Arc<Texture>,
129    ) -> Result<(), DeviceError> {
130        let snatch_guard = self.device.snatchable_lock.read();
131        let submission = self.allocate_submission(snatch_guard)?;
132        let device = &self.device;
133
134        // If the texture is uninitialized it needs to be cleared before presenting
135        let needs_clear = {
136            let status = texture.initialization_status.read();
137            status
138                .mips
139                .first()
140                .is_some_and(|mip| mip.check(0..1).is_some())
141        };
142
143        let mut pending_writes = self.pending_writes.lock();
144
145        if needs_clear {
146            // After encoding the clear operation, we must not return without
147            // adding the texture to `pending_writes`.
148            let encoder = pending_writes.activate();
149            let mut trackers = device.trackers.lock();
150            crate::command::clear_texture(
151                texture,
152                TextureInitRange {
153                    mip_range: 0..1,
154                    layer_range: 0..1,
155                },
156                None,
157                encoder,
158                &mut trackers.textures,
159                &device.alignments,
160                device.zero_buffer.as_ref(),
161                &submission.snatch_guard,
162                device.instance_flags,
163            )
164            .map_err(|e| match e {
165                ClearError::Device(e) => e,
166                _ => DeviceError::Lost,
167            })?;
168            texture.initialization_status.write().mips[0].drain(0..1);
169        }
170
171        // Transition the texture to PRESENT in the device tracker.
172        // If it's already in PRESENT, this produces no barriers and we can skip the submission.
173        //
174        // This has to be after any clear_texture call because clear_texture modifies the tracker state internally.
175        // Computing transitions afterward ensures they reflect the actual current state.
176        let pending = {
177            let mut trackers = device.trackers.lock();
178            let pending: Vec<track::PendingTransition<wgt::TextureUses>> = trackers
179                .textures
180                .set_single(
181                    texture,
182                    texture.full_range.clone(),
183                    wgt::TextureUses::PRESENT,
184                )
185                .collect();
186            pending
187        };
188
189        if pending.is_empty() {
190            // This assert checks that we don't return here if we encoded a
191            // clear operation for the texture, which would be a problem since
192            // we haven't done anything yet to ensure it stays alive. If we
193            // cleared the texture, then we must have produced a barrier to put
194            // it in PRESENT state, so `pending` will not be empty.
195            debug_assert!(!needs_clear);
196            return Ok(());
197        }
198
199        // Emit the transition barriers to PRESENT.
200        {
201            let raw_texture = texture
202                .raw(&submission.snatch_guard)
203                .ok_or(DeviceError::Lost)?;
204            let barriers: Vec<hal::TextureBarrier<'_, dyn hal::DynTexture>> = pending
205                .into_iter()
206                .map(|pt| pt.into_hal(raw_texture))
207                .collect();
208
209            let encoder = pending_writes.activate();
210            // SAFETY:
211            // - The encoder is in the recording state after `activate()`
212            // - The texture is kept alive by adding it to `PendingWrites` below
213            unsafe {
214                encoder.transition_textures(&barriers);
215            }
216        }
217
218        // Add the texture to `PendingWrites`. This will cause `submit()` to:
219        // - Flush any pending writes to the texture.
220        // - Include the texture in `surface_textures` for the submission.
221        // - Keep the texture alive so the texture and its clear_view aren't
222        //   destroyed before the GPU finishes the `clear_texture` operation
223        //   encoded above.
224        pending_writes.insert_texture(texture);
225
226        submission.submit(pending_writes)?;
227
228        Ok(())
229    }
230
231    /// Maintains the queue's list of finished command buffers.
232    ///
233    /// Command buffers submitted before `submission_index` was submitted
234    /// stop being tracked and callbacks which are waiting for them are
235    /// returned. Also returned is whether the queue is empty. This may
236    /// be stale unless new submissions are prevented by locking
237    /// [`Device::command_indices`]
238    pub(crate) fn maintain(
239        &self,
240        submission_index: u64,
241        snatch_guard: &SnatchGuard,
242    ) -> (
243        SmallVec<[SubmittedWorkDoneClosure; 1]>,
244        Vec<super::BufferMapPendingClosure>,
245        Vec<BlasCompactReadyPendingClosure>,
246        bool,
247    ) {
248        let mut life_tracker = self.lock_life();
249        let submission_closures = life_tracker.triage_submissions(submission_index);
250
251        let mapping_closures = life_tracker.handle_mapping(snatch_guard);
252        let blas_closures = life_tracker.handle_compact_read_back();
253
254        let queue_empty = life_tracker.queue_empty();
255
256        (
257            submission_closures,
258            mapping_closures,
259            blas_closures,
260            queue_empty,
261        )
262    }
263}
264
265crate::impl_resource_type!(Queue);
266crate::impl_labeled!(Queue);
267crate::impl_parent_device!(Queue);
268crate::impl_storage_item!(Queue);
269
270impl Drop for Queue {
271    #[allow(trivial_casts)]
272    fn drop(&mut self) {
273        profiling::scope!("Queue::drop");
274        api_log!("Queue::drop {:?}", self as *const _);
275        resource_log!("Drop {}", self.error_ident());
276
277        // On Vulkan, pending presents are not tracked by fences.
278        // wait_for_idle covers both fence-tracked submissions and pending presents.
279        match unsafe { self.raw.wait_for_idle() } {
280            Ok(()) => {}
281            Err(hal::DeviceError::Lost) => {
282                self.device.handle_hal_error(hal::DeviceError::Lost);
283            }
284            Err(e) => {
285                panic!("Unexpected error while waiting for queue idle on drop: {e:?}");
286            }
287        }
288
289        let last_successful_submission_index = self
290            .device
291            .last_successful_submission_index
292            .load(Ordering::Acquire);
293
294        let snatch_guard = self.device.snatchable_lock.read();
295        let (submission_closures, mapping_closures, blas_compact_ready_closures, queue_empty) =
296            self.maintain(last_successful_submission_index, &snatch_guard);
297        drop(snatch_guard);
298
299        assert!(queue_empty);
300
301        let closures = crate::device::UserClosures {
302            mappings: mapping_closures,
303            blas_compact_ready: blas_compact_ready_closures,
304            submissions: submission_closures,
305            device_lost_invocations: SmallVec::new(),
306        };
307
308        closures.fire();
309    }
310}
311
312#[cfg(send_sync)]
313pub type SubmittedWorkDoneClosure = Box<dyn FnOnce() + Send + 'static>;
314#[cfg(not(send_sync))]
315pub type SubmittedWorkDoneClosure = Box<dyn FnOnce() + 'static>;
316
317/// A texture or buffer to be freed soon.
318///
319/// This is just a tagged raw texture or buffer, generally about to be added to
320/// some other more specific container like:
321///
322/// - `PendingWrites::temp_resources`: resources used by queue writes and
323///   unmaps, waiting to be folded in with the next queue submission
324///
325/// - `ActiveSubmission::temp_resources`: temporary resources used by a queue
326///   submission, to be freed when it completes
327#[derive(Debug)]
328pub enum TempResource {
329    StagingBuffer(FlushedStagingBuffer),
330    ScratchBuffer(ScratchBuffer),
331    DestroyedBuffer(DestroyedBuffer),
332    DestroyedTexture(DestroyedTexture),
333    DestroyedQuerySet(DestroyedQuerySet),
334}
335
336/// A series of raw [`CommandBuffer`]s that have been submitted to a
337/// queue, and the [`wgpu_hal::CommandEncoder`] that built them.
338///
339/// [`CommandBuffer`]: hal::Api::CommandBuffer
340/// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
341pub(crate) struct EncoderInFlight {
342    inner: crate::command::InnerCommandEncoder,
343    pub(crate) trackers: Tracker,
344    pub(crate) temp_resources: Vec<TempResource>,
345    /// We only need to keep these resources alive.
346    _indirect_draw_validation_resources: crate::indirect_validation::DrawResources,
347
348    /// These are the buffers that have been tracked by `PendingWrites`.
349    pub(crate) pending_buffers: FastHashMap<TrackerIndex, Arc<Buffer>>,
350    /// These are the textures that have been tracked by `PendingWrites`.
351    pub(crate) pending_textures: FastHashMap<TrackerIndex, Arc<Texture>>,
352    /// These are the BLASes that have been tracked by `PendingWrites`.
353    pub(crate) pending_blas_s: FastHashMap<TrackerIndex, Arc<Blas>>,
354}
355
356/// A private command encoder for writes made directly on the device
357/// or queue.
358///
359/// Operations like `buffer_unmap`, `queue_write_buffer`, and
360/// `queue_write_texture` need to copy data to the GPU. At the hal
361/// level, this must be done by encoding and submitting commands, but
362/// these operations are not associated with any specific wgpu command
363/// buffer.
364///
365/// Instead, `Device::pending_writes` owns one of these values, which
366/// has its own hal command encoder and resource lists. The commands
367/// accumulated here are automatically submitted to the queue at the
368/// sooner of:
369///
370/// 1. The user's next wgpu command buffer submission. (Pending writes
371///    are inserted ahead of the user's commands.)
372/// 2. The next `mapAsync` request for a buffer that has pending
373///    writes.
374///
375/// Important:
376/// When locking pending_writes be sure that tracker is not locked
377/// and try to lock trackers for the minimum timespan possible
378///
379/// All uses of [`StagingBuffer`]s end up here.
380#[derive(Debug)]
381pub(crate) struct PendingWrites {
382    // The command encoder needs to be destroyed before any other resource in pending writes.
383    pub command_encoder: Box<dyn hal::DynCommandEncoder>,
384
385    /// True if `command_encoder` is in the "recording" state, as
386    /// described in the docs for the [`wgpu_hal::CommandEncoder`]
387    /// trait.
388    ///
389    /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
390    pub is_recording: bool,
391
392    temp_resources: Vec<TempResource>,
393    dst_buffers: FastHashMap<TrackerIndex, Arc<Buffer>>,
394    dst_textures: FastHashMap<TrackerIndex, Arc<Texture>>,
395    copied_blas_s: FastHashMap<TrackerIndex, Arc<Blas>>,
396    instance_flags: wgt::InstanceFlags,
397
398    /// Staging buffers that are the source of a copy recorded in
399    /// `command_encoder`, and so need a `MAP_WRITE` -> `COPY_SRC` transition.
400    ///
401    /// See [`hoisted_buffer_barriers`] for why these are not recorded
402    /// immediately. Moved to `temp_resources` in [`pre_submit`].
403    ///
404    /// [`hoisted_buffer_barriers`]: PendingWrites::hoisted_buffer_barriers
405    /// [`pre_submit`]: PendingWrites::pre_submit
406    hoisted_staging_buffers: Vec<FlushedStagingBuffer>,
407
408    /// Buffer transitions that must happen before the copies recorded in
409    /// `command_encoder`.
410    ///
411    /// Recording a barrier immediately before each copy means that a frame
412    /// which calls `Queue::write_buffer` a few dozen times ends up with many
413    /// back-to-back barrier/copy pairs, when a single barrier covering every
414    /// buffer would have done just as well.
415    ///
416    /// Instead we accumulate the transitions here and record them all at once,
417    /// into a command buffer that [`pre_submit`] places in front of
418    /// `command_encoder`'s.
419    ///
420    /// Only a buffer's *first* transition in a batch may be hoisted like this.
421    /// A buffer written more than once per batch gets a second, `COPY_DST` ->
422    /// `COPY_DST` transition from the tracker (unless the adapter reports
423    /// `COPY_DST` as an ordered usage), and that barrier exists precisely to
424    /// separate the two copies, so it has to stay where it was recorded. See
425    /// [`hoist_buffer_barrier`].
426    ///
427    /// [`pre_submit`]: PendingWrites::pre_submit
428    /// [`hoist_buffer_barrier`]: PendingWrites::hoist_buffer_barrier
429    hoisted_buffer_barriers: Vec<(Arc<Buffer>, hal::StateTransition<wgt::BufferUses>)>,
430}
431
432impl PendingWrites {
433    pub fn new(
434        command_encoder: Box<dyn hal::DynCommandEncoder>,
435        instance_flags: wgt::InstanceFlags,
436    ) -> Self {
437        Self {
438            command_encoder,
439            is_recording: false,
440            temp_resources: Vec::new(),
441            dst_buffers: FastHashMap::default(),
442            dst_textures: FastHashMap::default(),
443            copied_blas_s: FastHashMap::default(),
444            instance_flags,
445            hoisted_staging_buffers: Vec::new(),
446            hoisted_buffer_barriers: Vec::new(),
447        }
448    }
449
450    pub fn insert_buffer(&mut self, buffer: &Arc<Buffer>) {
451        self.dst_buffers
452            .insert(buffer.tracker_index(), buffer.clone());
453    }
454
455    pub fn insert_texture(&mut self, texture: &Arc<Texture>) {
456        self.dst_textures
457            .insert(texture.tracker_index(), texture.clone());
458    }
459
460    pub fn insert_blas(&mut self, blas: &Arc<Blas>) {
461        self.copied_blas_s
462            .insert(blas.tracker_index(), blas.clone());
463    }
464
465    pub fn contains_buffer(&self, buffer: &Arc<Buffer>) -> bool {
466        self.dst_buffers.contains_key(&buffer.tracker_index())
467    }
468
469    pub fn contains_texture(&self, texture: &Arc<Texture>) -> bool {
470        self.dst_textures.contains_key(&texture.tracker_index())
471    }
472
473    pub fn consume_temp(&mut self, resource: TempResource) {
474        self.temp_resources.push(resource);
475    }
476
477    /// Take ownership of a staging buffer that is the source of a copy
478    /// recorded into this batch.
479    ///
480    /// This also queues up the staging buffer's `MAP_WRITE` -> `COPY_SRC`
481    /// transition, so callers must not record one themselves.
482    pub fn consume(&mut self, buffer: FlushedStagingBuffer) {
483        self.hoisted_staging_buffers.push(buffer);
484    }
485
486    /// Queue up a buffer transition to be recorded ahead of this batch's
487    /// copies.
488    ///
489    /// Returns the transition instead if it cannot be hoisted, in which case
490    /// the caller must record it immediately, before its own copy. This happens
491    /// when this batch has already copied into `buffer`: the transition is then
492    /// a `COPY_DST` -> `COPY_DST` write-after-write barrier separating the
493    /// earlier copy from the caller's, as hoisting it to the front of the
494    /// batch would leave the two copies unsynchronized.
495    ///
496    /// See [`hoisted_buffer_barriers`].
497    ///
498    /// [`hoisted_buffer_barriers`]: PendingWrites::hoisted_buffer_barriers
499    #[must_use]
500    fn hoist_buffer_barrier(
501        &mut self,
502        buffer: &Arc<Buffer>,
503        transition: Option<track::PendingTransition<wgt::BufferUses>>,
504    ) -> Option<track::PendingTransition<wgt::BufferUses>> {
505        let transition = transition?;
506
507        // `dst_buffers` is only populated once a copy has been recorded, so
508        // true here means an earlier copy in this batch already wrote `buffer`.
509        if self.dst_buffers.contains_key(&buffer.tracker_index()) {
510            return Some(transition);
511        }
512
513        self.hoisted_buffer_barriers
514            .push((buffer.clone(), transition.usage));
515        None
516    }
517
518    pub fn clear_buffer(
519        &mut self,
520        device: &Arc<Device>,
521        buffer: &Arc<Buffer>,
522        range: core::ops::Range<wgt::BufferAddress>,
523        snatch_guard: &SnatchGuard,
524    ) -> Result<(), QueueWriteError> {
525        let transition = {
526            let mut trackers = device.trackers.lock();
527            trackers
528                .buffers
529                .set_single(buffer, wgt::BufferUses::COPY_DST)
530        };
531        let inline_transition = self.hoist_buffer_barrier(buffer, transition);
532
533        let dst_raw = buffer.try_raw(snatch_guard)?;
534
535        let encoder = self.activate();
536        unsafe {
537            if let Some(transition) = inline_transition {
538                encoder.transition_buffers(&[transition.into_hal(buffer, snatch_guard)]);
539            }
540            encoder.clear_buffer(dst_raw, range.clone());
541        }
542
543        self.insert_buffer(buffer);
544
545        // Ensure the overwritten bytes are marked as initialized so
546        // they don't need to be nulled prior to mapping or binding.
547        buffer.initialization_status.write().drain(range);
548
549        Ok(())
550    }
551
552    /// Record every transition this batch of writes needs as a single barrier,
553    /// into a command buffer placed ahead of the one holding the batch's
554    /// copies.
555    ///
556    /// Returns the staging buffers whose transitions were recorded. The caller
557    /// is responsible for keeping them alive until the submission completes.
558    fn record_hoisted_barriers(
559        &mut self,
560        encoder: &mut EncoderInFlight,
561        snatch_guard: &SnatchGuard,
562    ) -> Result<Vec<FlushedStagingBuffer>, DeviceError> {
563        let staging_buffers = mem::take(&mut self.hoisted_staging_buffers);
564        let buffer_barriers = mem::take(&mut self.hoisted_buffer_barriers);
565
566        let barriers = staging_buffers
567            .iter()
568            .map(|staging_buffer| hal::BufferBarrier {
569                buffer: staging_buffer.raw(),
570                usage: hal::StateTransition {
571                    from: wgt::BufferUses::MAP_WRITE,
572                    to: wgt::BufferUses::COPY_SRC,
573                },
574            })
575            .chain(buffer_barriers.iter().filter_map(|(buffer, usage)| {
576                // The buffer may have been destroyed since the copy was
577                // recorded, in which case there is nothing to sync.
578                Some(hal::BufferBarrier {
579                    buffer: buffer.raw(snatch_guard)?,
580                    usage: usage.clone(),
581                })
582            }))
583            .collect::<Vec<_>>();
584
585        if !barriers.is_empty() {
586            encoder
587                .inner
588                .open_pass(Some("(wgpu internal) PendingWrites Transit"))?;
589            unsafe { encoder.inner.raw.transition_buffers(&barriers) };
590            encoder.inner.close_and_push_front()?;
591        }
592
593        Ok(staging_buffers)
594    }
595
596    fn pre_submit(
597        &mut self,
598        command_allocator: &CommandAllocator,
599        device: &Arc<Device>,
600        queue: &Queue,
601        snatch_guard: &SnatchGuard,
602    ) -> Result<Option<EncoderInFlight>, DeviceError> {
603        if self.is_recording {
604            let pending_buffers = mem::take(&mut self.dst_buffers);
605            let pending_textures = mem::take(&mut self.dst_textures);
606            let pending_blas_s = mem::take(&mut self.copied_blas_s);
607
608            let cmd_buf = unsafe { self.command_encoder.end_encoding() }
609                .map_err(|e| device.handle_hal_error(e))?;
610            self.is_recording = false;
611
612            let new_encoder = command_allocator
613                .acquire_encoder(device.raw(), queue.raw())
614                .map_err(|e| device.handle_hal_error(e))?;
615
616            let mut encoder = EncoderInFlight {
617                inner: crate::command::InnerCommandEncoder {
618                    raw: ManuallyDrop::new(mem::replace(&mut self.command_encoder, new_encoder)),
619                    list: vec![cmd_buf],
620                    device: device.clone(),
621                    is_open: false,
622                    api: crate::command::EncodingApi::InternalUse,
623                    label: "(wgpu internal) PendingWrites command encoder".into(),
624                },
625                trackers: Tracker::new(device.ordered_buffer_usages, device.ordered_texture_usages),
626                temp_resources: mem::take(&mut self.temp_resources),
627                _indirect_draw_validation_resources: crate::indirect_validation::DrawResources::new(
628                    device.clone(),
629                ),
630                pending_buffers,
631                pending_textures,
632                pending_blas_s,
633            };
634
635            let staging_buffers = self.record_hoisted_barriers(&mut encoder, snatch_guard)?;
636            encoder
637                .temp_resources
638                .extend(staging_buffers.into_iter().map(TempResource::StagingBuffer));
639
640            Ok(Some(encoder))
641        } else {
642            self.dst_buffers.clear();
643            self.dst_textures.clear();
644            self.copied_blas_s.clear();
645            Ok(None)
646        }
647    }
648
649    pub fn activate(&mut self) -> &mut dyn hal::DynCommandEncoder {
650        if !self.is_recording {
651            unsafe {
652                self.command_encoder
653                    .begin_encoding(hal_label(
654                        Some("(wgpu internal) PendingWrites"),
655                        self.instance_flags,
656                    ))
657                    .unwrap();
658            }
659            self.is_recording = true;
660        }
661        self.command_encoder.as_mut()
662    }
663}
664
665impl Drop for PendingWrites {
666    fn drop(&mut self) {
667        unsafe {
668            if self.is_recording {
669                self.command_encoder.discard_encoding();
670            }
671        }
672    }
673}
674
675#[derive(Clone, Debug, Error)]
676#[non_exhaustive]
677pub enum QueueWriteError {
678    #[error(transparent)]
679    Queue(#[from] DeviceError),
680    #[error(transparent)]
681    Transfer(#[from] TransferError),
682    #[error(transparent)]
683    MemoryInitFailure(#[from] ClearError),
684    #[error(transparent)]
685    DestroyedResource(#[from] DestroyedResourceError),
686    #[error(transparent)]
687    InvalidResource(#[from] InvalidResourceError),
688}
689
690impl From<InvalidOrDestroyedResourceError> for QueueWriteError {
691    fn from(e: InvalidOrDestroyedResourceError) -> Self {
692        match e {
693            InvalidOrDestroyedResourceError::InvalidResource(e) => Self::InvalidResource(e),
694            InvalidOrDestroyedResourceError::DestroyedResource(e) => Self::DestroyedResource(e),
695        }
696    }
697}
698
699impl WebGpuError for QueueWriteError {
700    fn webgpu_error_type(&self) -> ErrorType {
701        match self {
702            Self::Queue(e) => e.webgpu_error_type(),
703            Self::Transfer(e) => e.webgpu_error_type(),
704            Self::MemoryInitFailure(e) => e.webgpu_error_type(),
705            Self::DestroyedResource(e) => e.webgpu_error_type(),
706            Self::InvalidResource(e) => e.webgpu_error_type(),
707        }
708    }
709}
710
711#[derive(Clone, Debug, Error)]
712#[non_exhaustive]
713pub enum QueueSubmitError {
714    #[error(transparent)]
715    Queue(#[from] DeviceError),
716    #[error(transparent)]
717    DestroyedResource(#[from] DestroyedResourceError),
718    #[error("{0} is still mapped")]
719    BufferStillMapped(ResourceErrorIdent),
720    #[error(transparent)]
721    InvalidResource(#[from] InvalidResourceError),
722    #[error(transparent)]
723    CommandEncoder(#[from] CommandEncoderError),
724    #[error(transparent)]
725    ValidateAsActionsError(#[from] crate::ray_tracing::ValidateAsActionsError),
726}
727
728impl From<InvalidOrDestroyedResourceError> for QueueSubmitError {
729    fn from(e: InvalidOrDestroyedResourceError) -> Self {
730        match e {
731            InvalidOrDestroyedResourceError::InvalidResource(e) => Self::InvalidResource(e),
732            InvalidOrDestroyedResourceError::DestroyedResource(e) => Self::DestroyedResource(e),
733        }
734    }
735}
736
737impl WebGpuError for QueueSubmitError {
738    fn webgpu_error_type(&self) -> ErrorType {
739        match self {
740            Self::Queue(e) => e.webgpu_error_type(),
741            Self::CommandEncoder(e) => e.webgpu_error_type(),
742            Self::ValidateAsActionsError(e) => e.webgpu_error_type(),
743            Self::InvalidResource(e) => e.webgpu_error_type(),
744            Self::DestroyedResource(_) | Self::BufferStillMapped(_) => ErrorType::Validation,
745        }
746    }
747}
748
749/// A command submission in the process of being assembled.
750///
751/// Within `wgpu_core`, enqueuing commands for execution on the GPU is a
752/// three-step process:
753///
754/// 1) Call [`Queue::allocate_submission`] to acquire the necessary locks,
755///    assign a submission index, wrap them all up as a [`PendingSubmission`],
756///    and return it.
757///
758/// 2) Add the command buffers to be submitted to [`executions`], and note any
759///    surface textures they reference in [`surface_textures`]. Contribute to
760///    [`Queue::pending_writes`] as necessary.
761///
762/// 3) Acquire the pending writes lock. This may be done at any point between
763///    the return from [`Queue::allocate_submission`] and the call to
764///    [`submit`]. Typically it should be done as late as is possible given
765///    any necessary pending writes activity.
766///
767/// 4) Call the `PendingSubmission`'s [`submit`] method (which is a convenience
768///    wrapper around [`Queue::submit_pending_submission`]). Pass the pending
769///    writes mutex guard to [`submit`].
770///
771/// It is also acceptable to drop the `PendingSubmission` without submitting;
772/// this frees its locks in the appropriate order. This may be necessary when
773/// those locks are required to access the state that determines whether a
774/// submission is needed at all.
775///
776/// This split allows the various places in `wgpu_core` that need to submit
777/// commands to the GPU to share the common initial code for locking and final
778/// code for actually submitting the commands to `wgpu_hal`:
779///
780/// - [`Queue::submit`] just submits user-constructed [`CommandBuffer`]s.
781///
782/// - [`Queue::prepare_surface_texture_for_present`] examines the surface
783///   texture being presented, and submits deferred initialization commands and
784///   barriers to get it ready.
785///
786/// - [`Queue::flush_writes_for_buffer`] and [`Queue::flush_pending_writes`]
787///   simply submit the operations already staged in [`Queue::pending_writes`].
788///
789/// Returned from [`Queue::allocate_submission`] and consumed by [`submit`].
790/// These are internal APIs used in `Queue::submit` and other places within
791/// `wgpu-core` that need to submit work.
792///
793/// [`submit`]: PendingSubmission::submit
794/// [`executions`]: PendingSubmission::executions
795/// [`surface_textures`]: PendingSubmission::surface_textures
796pub(crate) struct PendingSubmission<'a> {
797    queue: &'a Queue,
798
799    // These lock guards must appear in this struct in the order given.
800    //
801    // The instrumented locks in [`lock::ranked`] require that locks be acquired
802    // and released in a stack-like order. Since `rank::DEVICE_COMMAND_INDICES`
803    // follows `rank::DEVICE_SNATCHABLE_LOCK`, the lock on
804    // `Device::command_indices` must be released before the lock on
805    // `Device::snatchable_lock`. Rust drops struct members from first to last,
806    // so this ordering of fields ensures the order we want.
807    /// A guard for the lock on `Device::command_indices`.
808    command_index_guard: RwLockWriteGuard<'a, CommandIndices>,
809
810    /// A guard for the lock on `Device::snatchable_lock`.
811    snatch_guard: SnatchGuard<'a>,
812
813    /// Command buffers to be submitted, along with trackers for the resources
814    /// they use.
815    pub executions: Vec<EncoderInFlight>,
816
817    /// Surface textures referenced by command buffers in this submission.
818    ///
819    /// These need to be passed to [`wgpu_hal::Queue::submit`], which
820    /// requires that the list contains no duplicates, so we store them in a
821    /// `HashMap` keyed by `SurfaceTexture` address.
822    surface_textures: FastHashMap<*const Texture, Arc<Texture>>,
823
824    /// The index this submission has been assigned.
825    pub index: SubmissionIndex,
826}
827
828pub(crate) struct SubmissionResult<'a> {
829    pub snatch_guard: SnatchGuard<'a>,
830}
831
832impl<'a> PendingSubmission<'a> {
833    fn submit(
834        self,
835        pending_writes: MutexGuard<'a, PendingWrites>,
836    ) -> Result<SubmissionResult<'a>, DeviceError> {
837        self.queue.submit_pending_submission(pending_writes, self)
838    }
839}
840
841//TODO: move out common parts of write_xxx.
842
843impl Queue {
844    pub(crate) fn write_buffer_inner(
845        &self,
846        buffer: Arc<Buffer>,
847        buffer_offset: wgt::BufferAddress,
848        data: &[u8],
849    ) -> Result<(), QueueWriteError> {
850        profiling::scope!("Queue::write_buffer");
851        api_log!("Queue::write_buffer");
852
853        #[cfg(feature = "trace")]
854        if let Some(ref mut trace) = *self.device.trace.lock() {
855            use crate::device::trace::DataKind;
856            let size = data.len() as u64;
857            let data = trace.make_binary(DataKind::Bin, data);
858            trace.add(Action::WriteBuffer {
859                id: buffer.to_trace(),
860                data,
861                offset: buffer_offset,
862                size,
863                queued: true,
864            });
865        }
866
867        buffer.check_is_valid()?;
868        self.device.check_is_valid()?;
869
870        let data_size = data.len() as wgt::BufferAddress;
871
872        self.same_device_as(buffer.as_ref())?;
873
874        let data_size = if let Some(data_size) = wgt::BufferSize::new(data_size) {
875            data_size
876        } else {
877            // even though a zero-length write is a no-op and no copy operation will occur,
878            // we must still validate the copy operation. This ensures that invalid
879            // API calls—like writing to a mapped buffer or out-of-bounds offsets—are
880            // caught consistently, even if no data is actually moved.
881            self.validate_write_buffer_impl(buffer.as_ref(), buffer_offset, 0)?;
882
883            log::trace!("Ignoring write_buffer of size 0");
884            return Ok(());
885        };
886
887        // Platform validation requires that the staging buffer always be
888        // freed, even if an error occurs. All paths from here must call
889        // `device.pending_writes.consume`.
890        let mut staging_buffer = StagingBuffer::new(&self.device, data_size)?;
891
892        let staging_buffer = {
893            profiling::scope!("copy");
894            staging_buffer.write_exact(data);
895            staging_buffer.flush()
896        };
897
898        let snatch_guard = self.device.snatchable_lock.read();
899        let mut pending_writes = self.pending_writes.lock();
900
901        let result = self.write_staging_buffer_impl(
902            &snatch_guard,
903            &mut pending_writes,
904            &staging_buffer,
905            buffer,
906            buffer_offset,
907        );
908
909        pending_writes.consume(staging_buffer);
910
911        result
912    }
913
914    pub fn write_buffer(
915        &self,
916        buffer: Arc<Buffer>,
917        buffer_offset: wgt::BufferAddress,
918        data: &[u8],
919    ) {
920        if let Err(error) = self.write_buffer_inner(buffer, buffer_offset, data) {
921            self.device
922                .handle_error(error, Some(self.label()), "Queue::write_buffer");
923        }
924    }
925
926    pub fn create_staging_buffer(
927        &self,
928        buffer_size: wgt::BufferSize,
929    ) -> Result<(StagingBuffer, NonNull<u8>), QueueWriteError> {
930        profiling::scope!("Queue::create_staging_buffer");
931        resource_log!("Queue::create_staging_buffer");
932
933        self.device.check_is_valid()?;
934
935        let staging_buffer = StagingBuffer::new(&self.device, buffer_size)?;
936        let ptr = unsafe { staging_buffer.ptr() };
937
938        Ok((staging_buffer, ptr))
939    }
940
941    pub fn write_staging_buffer(
942        &self,
943        buffer: Arc<Buffer>,
944        buffer_offset: wgt::BufferAddress,
945        staging_buffer: StagingBuffer,
946    ) -> Result<(), QueueWriteError> {
947        profiling::scope!("Queue::write_staging_buffer");
948
949        buffer.check_is_valid()?;
950        self.device.check_is_valid()?;
951
952        // At this point, we have taken ownership of the staging_buffer from the
953        // user. Platform validation requires that the staging buffer always
954        // be freed, even if an error occurs. All paths from here must call
955        // `device.pending_writes.consume`.
956        let staging_buffer = staging_buffer.flush();
957
958        let snatch_guard = self.device.snatchable_lock.read();
959        let mut pending_writes = self.pending_writes.lock();
960
961        let result = self.write_staging_buffer_impl(
962            &snatch_guard,
963            &mut pending_writes,
964            &staging_buffer,
965            buffer,
966            buffer_offset,
967        );
968
969        drop(snatch_guard);
970
971        pending_writes.consume(staging_buffer);
972
973        drop(pending_writes);
974
975        result
976    }
977
978    pub fn validate_write_buffer(
979        &self,
980        buffer: Arc<Buffer>,
981        buffer_offset: u64,
982        buffer_size: wgt::BufferSize,
983    ) -> Result<(), QueueWriteError> {
984        profiling::scope!("Queue::validate_write_buffer");
985
986        self.device.check_is_valid()?;
987        buffer.check_is_valid()?;
988
989        self.validate_write_buffer_impl(&buffer, buffer_offset, buffer_size.into())?;
990
991        Ok(())
992    }
993
994    fn validate_write_buffer_impl(
995        &self,
996        buffer: &Buffer,
997        buffer_offset: u64,
998        buffer_size: u64,
999    ) -> Result<(), TransferError> {
1000        if !matches!(&*buffer.map_state.read(), BufferMapState::Idle) {
1001            return Err(TransferError::BufferNotAvailable);
1002        }
1003        buffer.check_usage(wgt::BufferUsages::COPY_DST)?;
1004        if !buffer_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
1005            return Err(TransferError::UnalignedCopySize(buffer_size));
1006        }
1007        if !buffer_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
1008            return Err(TransferError::UnalignedBufferOffset(buffer_offset));
1009        }
1010
1011        if buffer_offset > buffer.size {
1012            return Err(TransferError::BufferStartOffsetOverrun {
1013                start_offset: buffer_offset,
1014                buffer_size: buffer.size,
1015                side: CopySide::Destination,
1016            });
1017        }
1018        if buffer_size > buffer.size - buffer_offset {
1019            return Err(TransferError::BufferEndOffsetOverrun {
1020                start_offset: buffer_offset,
1021                size: buffer_size,
1022                buffer_size: buffer.size,
1023                side: CopySide::Destination,
1024            });
1025        }
1026
1027        Ok(())
1028    }
1029
1030    fn write_staging_buffer_impl(
1031        &self,
1032        snatch_guard: &SnatchGuard,
1033        pending_writes: &mut PendingWrites,
1034        staging_buffer: &FlushedStagingBuffer,
1035        buffer: Arc<Buffer>,
1036        buffer_offset: u64,
1037    ) -> Result<(), QueueWriteError> {
1038        self.device.check_is_valid()?;
1039
1040        let transition = {
1041            let mut trackers = self.device.trackers.lock();
1042            trackers
1043                .buffers
1044                .set_single(&buffer, wgt::BufferUses::COPY_DST)
1045        };
1046
1047        let dst_raw = buffer.try_raw(snatch_guard)?;
1048
1049        self.same_device_as(buffer.as_ref())?;
1050
1051        self.validate_write_buffer_impl(&buffer, buffer_offset, staging_buffer.size.into())?;
1052
1053        let region = hal::BufferCopy {
1054            src_offset: 0,
1055            dst_offset: buffer_offset,
1056            size: staging_buffer.size,
1057        };
1058        // The staging buffer's own `MAP_WRITE` -> `COPY_SRC` transition is
1059        // hoisted by `PendingWrites::consume`, which our caller invokes.
1060        let inline_transition = pending_writes.hoist_buffer_barrier(&buffer, transition);
1061        let encoder = pending_writes.activate();
1062        unsafe {
1063            if let Some(transition) = inline_transition {
1064                encoder.transition_buffers(&[transition.into_hal(&buffer, snatch_guard)]);
1065            }
1066            encoder.copy_buffer_to_buffer(staging_buffer.raw(), dst_raw, &[region]);
1067        }
1068
1069        pending_writes.insert_buffer(&buffer);
1070
1071        // Ensure the overwritten bytes are marked as initialized so
1072        // they don't need to be nulled prior to mapping or binding.
1073        {
1074            buffer
1075                .initialization_status
1076                .write()
1077                .drain(buffer_offset..(buffer_offset + staging_buffer.size.get()));
1078        }
1079
1080        Ok(())
1081    }
1082
1083    pub fn write_texture_inner(
1084        &self,
1085        destination: wgt::TexelCopyTextureInfo<Arc<Texture>>,
1086        data: &[u8],
1087        data_layout: &wgt::TexelCopyBufferLayout,
1088        size: &wgt::Extent3d,
1089    ) -> Result<(), QueueWriteError> {
1090        profiling::scope!("Queue::write_texture");
1091        api_log!("Queue::write_texture");
1092
1093        #[cfg(feature = "trace")]
1094        if let Some(ref mut trace) = *self.device.trace.lock() {
1095            use crate::device::trace::DataKind;
1096            let data = trace.make_binary(DataKind::Bin, data);
1097            trace.add(Action::WriteTexture {
1098                to: destination.to_trace(),
1099                data,
1100                layout: *data_layout,
1101                size: *size,
1102            });
1103        }
1104
1105        self.device.check_is_valid()?;
1106
1107        let dst = destination.texture;
1108        let destination = wgt::TexelCopyTextureInfo {
1109            texture: (),
1110            mip_level: destination.mip_level,
1111            origin: destination.origin,
1112            aspect: destination.aspect,
1113        };
1114
1115        self.same_device_as(dst.as_ref())?;
1116
1117        dst.check_valid()?;
1118
1119        dst.check_usage(wgt::TextureUsages::COPY_DST)
1120            .map_err(TransferError::MissingTextureUsage)?;
1121
1122        // Note: Doing the copy range validation early is important because ensures that the
1123        // dimensions are not going to cause overflow in other parts of the validation.
1124        let (hal_copy_size, array_layer_count) =
1125            validate_texture_copy_range(&destination, &dst.desc, CopySide::Destination, size)?;
1126
1127        let (selector, dst_base) = extract_texture_selector(&destination, size, &dst)?;
1128
1129        validate_texture_copy_dst_format(dst.desc.format, destination.aspect)?;
1130
1131        validate_texture_buffer_copy(
1132            &destination,
1133            dst_base.aspect,
1134            &dst.desc,
1135            data_layout,
1136            false, // alignment not required for buffer offset or bytes per row
1137            self.device.features,
1138        )?;
1139
1140        // Note: `_source_bytes_per_array_layer` is ignored since we
1141        // have a staging copy, and it can have a different value.
1142        let (required_bytes_in_copy, _source_bytes_per_array_layer, _) =
1143            validate_linear_texture_data(
1144                data_layout,
1145                dst.desc.format,
1146                destination.aspect,
1147                data.len() as wgt::BufferAddress,
1148                CopySide::Source,
1149                size,
1150            )?;
1151
1152        if dst.desc.format.is_depth_stencil_format() {
1153            self.device
1154                .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
1155                .map_err(TransferError::from)?;
1156        }
1157
1158        let snatch_guard = self.device.snatchable_lock.read();
1159
1160        let dst_raw = dst.try_inner(&snatch_guard)?.raw();
1161
1162        // This must happen after parameter validation (so that errors are reported
1163        // as required by the spec), but before any side effects.
1164        if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 {
1165            log::trace!("Ignoring write_texture of size 0");
1166            return Ok(());
1167        }
1168
1169        let mut pending_writes = self.pending_writes.lock();
1170        let encoder = pending_writes.activate();
1171
1172        // If the copy does not fully cover the layers, we need to initialize to
1173        // zero *first* as we don't keep track of partial texture layer inits.
1174        //
1175        // Strictly speaking we only need to clear the areas of a layer
1176        // untouched, but this would get increasingly messy.
1177        let init_layer_range = if dst.desc.dimension == wgt::TextureDimension::D3 {
1178            // volume textures don't have a layer range as array volumes aren't supported
1179            0..1
1180        } else {
1181            destination.origin.z..destination.origin.z + size.depth_or_array_layers
1182        };
1183        let layer_ranges_to_clear = {
1184            let mut dst_initialization_status = dst.initialization_status.write();
1185            if dst_initialization_status.mips[destination.mip_level as usize]
1186                .check(init_layer_range.clone())
1187                .is_some()
1188            {
1189                if has_copy_partial_init_tracker_coverage(size, &destination, &dst.desc) {
1190                    dst_initialization_status.mips[destination.mip_level as usize]
1191                        .drain(init_layer_range)
1192                        .collect::<Vec<core::ops::Range<u32>>>()
1193                } else {
1194                    dst_initialization_status.mips[destination.mip_level as usize]
1195                        .drain(init_layer_range);
1196                    vec![]
1197                }
1198            } else {
1199                vec![]
1200            }
1201        };
1202        if !layer_ranges_to_clear.is_empty() {
1203            let mut trackers = self.device.trackers.lock();
1204            for layer_range in layer_ranges_to_clear {
1205                crate::command::clear_texture(
1206                    &dst,
1207                    TextureInitRange {
1208                        mip_range: destination.mip_level..(destination.mip_level + 1),
1209                        layer_range,
1210                    },
1211                    None,
1212                    encoder,
1213                    &mut trackers.textures,
1214                    &self.device.alignments,
1215                    self.device.zero_buffer.as_ref(),
1216                    &snatch_guard,
1217                    self.device.instance_flags,
1218                )
1219                .map_err(QueueWriteError::from)?;
1220            }
1221        }
1222
1223        let (block_width, block_height) = dst.desc.format.block_dimensions();
1224        let width_in_blocks = size.width / block_width;
1225        let height_in_blocks = size.height / block_height;
1226
1227        let block_size = dst
1228            .desc
1229            .format
1230            .block_copy_size(Some(destination.aspect))
1231            .unwrap();
1232        let bytes_in_last_row = width_in_blocks * block_size;
1233
1234        let bytes_per_row = data_layout.bytes_per_row.unwrap_or(bytes_in_last_row);
1235        let rows_per_image = data_layout.rows_per_image.unwrap_or(height_in_blocks);
1236
1237        let bytes_per_row_alignment = get_lowest_common_denom(
1238            self.device.alignments.buffer_copy_pitch.get() as u32,
1239            block_size,
1240        );
1241        assert!(u32::MAX - bytes_in_last_row >= bytes_per_row_alignment);
1242        let stage_bytes_per_row = wgt::math::align_to(bytes_in_last_row, bytes_per_row_alignment);
1243
1244        // Platform validation requires that the staging buffer always be
1245        // freed, even if an error occurs. All paths from here must call
1246        // `device.pending_writes.consume`.
1247        let staging_buffer = if stage_bytes_per_row == bytes_per_row {
1248            profiling::scope!("copy aligned");
1249            // Fast path if the data is already being aligned optimally.
1250            let stage_size = wgt::BufferSize::new(required_bytes_in_copy).unwrap();
1251            let mut staging_buffer = StagingBuffer::new(&self.device, stage_size)?;
1252            staging_buffer
1253                .write_exact(&data[data_layout.offset as usize..][..stage_size.get() as usize]);
1254            staging_buffer
1255        } else {
1256            profiling::scope!("copy chunked");
1257            // Copy row by row into the optimal alignment.
1258            let block_rows_in_copy = u64::from(size.depth_or_array_layers - 1)
1259                * u64::from(rows_per_image)
1260                + u64::from(height_in_blocks);
1261            // The copy size was validated against the source buffer, however,
1262            // `stage_bytes_per_row` can differ, so let's be paranoid.
1263            let stage_size = u64::from(stage_bytes_per_row)
1264                .checked_mul(block_rows_in_copy)
1265                .and_then(wgt::BufferSize::new)
1266                .unwrap();
1267            let mut staging_buffer = StagingBuffer::new(&self.device, stage_size)?;
1268            for layer in 0..u64::from(size.depth_or_array_layers) {
1269                let rows_offset = layer * u64::from(rows_per_image);
1270                for row in rows_offset..rows_offset + u64::from(height_in_blocks) {
1271                    let src_offset = data_layout.offset + row * u64::from(bytes_per_row);
1272                    let dst_offset = row * u64::from(stage_bytes_per_row);
1273                    unsafe {
1274                        staging_buffer.write_with_offset(
1275                            data,
1276                            src_offset as isize,
1277                            dst_offset as isize,
1278                            bytes_in_last_row as usize,
1279                        )
1280                    }
1281                }
1282            }
1283            staging_buffer
1284        };
1285
1286        let staging_buffer = staging_buffer.flush();
1287
1288        let regions = (0..array_layer_count)
1289            .map(|array_layer_offset| {
1290                let mut texture_base = dst_base.clone();
1291                texture_base.array_layer += array_layer_offset;
1292                hal::BufferTextureCopy {
1293                    buffer_layout: wgt::TexelCopyBufferLayout {
1294                        offset: array_layer_offset as u64
1295                            * rows_per_image as u64
1296                            * stage_bytes_per_row as u64,
1297                        bytes_per_row: Some(stage_bytes_per_row),
1298                        rows_per_image: Some(rows_per_image),
1299                    },
1300                    texture_base,
1301                    size: hal_copy_size,
1302                }
1303            })
1304            .collect::<Vec<_>>();
1305
1306        {
1307            let mut trackers = self.device.trackers.lock();
1308            let transition =
1309                trackers
1310                    .textures
1311                    .set_single(&dst, selector, wgt::TextureUses::COPY_DST);
1312            let texture_barriers = transition
1313                .map(|pending| pending.into_hal(dst_raw))
1314                .collect::<Vec<_>>();
1315
1316            // The staging buffer's `MAP_WRITE` -> `COPY_SRC` transition is
1317            // hoisted to the front of the batch by `PendingWrites::consume`,
1318            // below.
1319            unsafe {
1320                encoder.transition_textures(&texture_barriers);
1321                encoder.copy_buffer_to_texture(staging_buffer.raw(), dst_raw, &regions);
1322            }
1323        }
1324
1325        pending_writes.consume(staging_buffer);
1326        pending_writes.insert_texture(&dst);
1327
1328        Ok(())
1329    }
1330
1331    pub fn write_texture(
1332        &self,
1333        destination: wgt::TexelCopyTextureInfo<Arc<Texture>>,
1334        data: &[u8],
1335        data_layout: &wgt::TexelCopyBufferLayout,
1336        size: &wgt::Extent3d,
1337    ) {
1338        if let Err(error) = self.write_texture_inner(destination, data, data_layout, size) {
1339            self.device
1340                .handle_error(error, Some(self.label()), "Queue::write_texture");
1341        }
1342    }
1343
1344    #[cfg(webgl)]
1345    pub fn copy_external_image_to_texture(
1346        &self,
1347        source: &wgt::CopyExternalImageSourceInfo,
1348        destination: wgt::CopyExternalImageDestInfo<Arc<Texture>>,
1349        size: wgt::Extent3d,
1350    ) -> Result<(), QueueWriteError> {
1351        use crate::conv;
1352
1353        profiling::scope!("Queue::copy_external_image_to_texture");
1354
1355        self.device.check_is_valid()?;
1356
1357        let mut needs_flag = false;
1358        // `OffscreenCanvas` needs no downlevel flag: WebGL2's `texSubImage2D`
1359        // accepts it as a `TexImageSource` and the gles backend uploads it the
1360        // same way as `HTMLCanvasElement`.
1361        needs_flag |= source.origin != wgt::Origin2d::ZERO;
1362        needs_flag |= destination.color_space != wgt::PredefinedColorSpace::Srgb;
1363        #[allow(clippy::bool_comparison)]
1364        if matches!(source.source, wgt::ExternalImageSource::ImageBitmap(_)) {
1365            needs_flag |= source.flip_y != false;
1366            needs_flag |= destination.premultiplied_alpha != false;
1367        }
1368
1369        if needs_flag {
1370            self.device
1371                .require_downlevel_flags(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES)
1372                .map_err(TransferError::from)?;
1373        }
1374
1375        let src_width = source.source.width();
1376        let src_height = source.source.height();
1377
1378        let dst = destination.texture;
1379        let premultiplied_alpha = destination.premultiplied_alpha;
1380        let destination = wgt::TexelCopyTextureInfo {
1381            texture: (),
1382            mip_level: destination.mip_level,
1383            origin: destination.origin,
1384            aspect: destination.aspect,
1385        };
1386
1387        dst.check_valid()?;
1388
1389        if !conv::is_valid_external_image_copy_dst_texture_format(dst.desc.format) {
1390            return Err(
1391                TransferError::ExternalCopyToForbiddenTextureFormat(dst.desc.format).into(),
1392            );
1393        }
1394        if dst.desc.dimension != wgt::TextureDimension::D2 {
1395            return Err(TransferError::InvalidDimensionExternal.into());
1396        }
1397        dst.check_usage(wgt::TextureUsages::COPY_DST | wgt::TextureUsages::RENDER_ATTACHMENT)
1398            .map_err(TransferError::MissingTextureUsage)?;
1399        if dst.desc.sample_count != 1 {
1400            return Err(TransferError::InvalidSampleCount {
1401                sample_count: dst.desc.sample_count,
1402            }
1403            .into());
1404        }
1405
1406        if source.origin.x > src_width || src_width - source.origin.x < size.width {
1407            return Err(TransferError::TextureOverrun {
1408                start_offset: source.origin.x,
1409                end_offset: source.origin.x.saturating_add(size.width),
1410                texture_size: src_width,
1411                dimension: crate::resource::TextureErrorDimension::X,
1412                side: CopySide::Source,
1413            }
1414            .into());
1415        }
1416        if source.origin.y > src_height || src_height - source.origin.y < size.height {
1417            return Err(TransferError::TextureOverrun {
1418                start_offset: source.origin.y,
1419                end_offset: source.origin.y.saturating_add(size.height),
1420                texture_size: src_height,
1421                dimension: crate::resource::TextureErrorDimension::Y,
1422                side: CopySide::Source,
1423            }
1424            .into());
1425        }
1426        if size.depth_or_array_layers != 1 {
1427            return Err(TransferError::TextureOverrun {
1428                start_offset: 0,
1429                end_offset: size.depth_or_array_layers,
1430                texture_size: 1,
1431                dimension: crate::resource::TextureErrorDimension::Z,
1432                side: CopySide::Source,
1433            }
1434            .into());
1435        }
1436
1437        // Note: Doing the copy range validation early is important because ensures that the
1438        // dimensions are not going to cause overflow in other parts of the validation.
1439        let (hal_copy_size, _) =
1440            validate_texture_copy_range(&destination, &dst.desc, CopySide::Destination, &size)?;
1441
1442        let (selector, dst_base) = extract_texture_selector(&destination, &size, &dst)?;
1443
1444        let snatch_guard = self.device.snatchable_lock.read();
1445
1446        let dst_raw = dst.try_raw(&snatch_guard)?;
1447
1448        // This must happen after parameter validation (so that errors are reported
1449        // as required by the spec), but before any side effects.
1450        if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 {
1451            log::trace!("Ignoring copy_external_image_to_texture of size 0");
1452            return Ok(());
1453        }
1454
1455        let mut pending_writes = self.pending_writes.lock();
1456        let encoder = pending_writes.activate();
1457
1458        // If the copy does not fully cover the layers, we need to initialize to
1459        // zero *first* as we don't keep track of partial texture layer inits.
1460        //
1461        // Strictly speaking we only need to clear the areas of a layer
1462        // untouched, but this would get increasingly messy.
1463        let init_layer_range = if dst.desc.dimension == wgt::TextureDimension::D3 {
1464            // volume textures don't have a layer range as array volumes aren't supported
1465            0..1
1466        } else {
1467            destination.origin.z..destination.origin.z + size.depth_or_array_layers
1468        };
1469        let layer_ranges_to_clear = {
1470            let mut dst_initialization_status = dst.initialization_status.write();
1471            if dst_initialization_status.mips[destination.mip_level as usize]
1472                .check(init_layer_range.clone())
1473                .is_some()
1474            {
1475                if has_copy_partial_init_tracker_coverage(&size, &destination, &dst.desc) {
1476                    dst_initialization_status.mips[destination.mip_level as usize]
1477                        .drain(init_layer_range)
1478                        .collect::<Vec<core::ops::Range<u32>>>()
1479                } else {
1480                    dst_initialization_status.mips[destination.mip_level as usize]
1481                        .drain(init_layer_range);
1482                    vec![]
1483                }
1484            } else {
1485                vec![]
1486            }
1487        };
1488        if !layer_ranges_to_clear.is_empty() {
1489            let mut trackers = self.device.trackers.lock();
1490            for layer_range in layer_ranges_to_clear {
1491                crate::command::clear_texture(
1492                    &dst,
1493                    TextureInitRange {
1494                        mip_range: destination.mip_level..(destination.mip_level + 1),
1495                        layer_range,
1496                    },
1497                    None,
1498                    encoder,
1499                    &mut trackers.textures,
1500                    &self.device.alignments,
1501                    self.device.zero_buffer.as_ref(),
1502                    &snatch_guard,
1503                    self.device.instance_flags,
1504                )
1505                .map_err(QueueWriteError::from)?;
1506            }
1507        }
1508
1509        let regions = hal::TextureCopy {
1510            src_base: hal::TextureCopyBase {
1511                mip_level: 0,
1512                array_layer: 0,
1513                origin: source.origin.to_3d(0),
1514                aspect: hal::FormatAspects::COLOR,
1515            },
1516            dst_base,
1517            size: hal_copy_size,
1518        };
1519
1520        let mut trackers = self.device.trackers.lock();
1521        let transitions = trackers
1522            .textures
1523            .set_single(&dst, selector, wgt::TextureUses::COPY_DST);
1524
1525        // `copy_external_image_to_texture` is exclusive to the WebGL backend.
1526        // Don't go through the `DynCommandEncoder` abstraction and directly to the WebGL backend.
1527        let encoder_webgl = encoder
1528            .as_any_mut()
1529            .downcast_mut::<hal::gles::CommandEncoder>()
1530            .unwrap();
1531        let dst_raw_webgl = dst_raw
1532            .as_any()
1533            .downcast_ref::<hal::gles::Texture>()
1534            .unwrap();
1535        let transitions_webgl = transitions.map(|pending| {
1536            let dyn_transition = pending.into_hal(dst_raw);
1537            hal::TextureBarrier {
1538                texture: dst_raw_webgl,
1539                range: dyn_transition.range,
1540                usage: dyn_transition.usage,
1541                queue_family_ownership_transfer: None,
1542            }
1543        });
1544
1545        use hal::CommandEncoder as _;
1546        unsafe {
1547            encoder_webgl.transition_textures(transitions_webgl);
1548            encoder_webgl.copy_external_image_to_texture(
1549                source,
1550                dst_raw_webgl,
1551                premultiplied_alpha,
1552                core::iter::once(regions),
1553            );
1554        }
1555
1556        pending_writes.insert_texture(&dst);
1557
1558        Ok(())
1559    }
1560
1561    /// Flush `PendingWrites` if it contains a write to `buffer`.
1562    pub fn flush_writes_for_buffer(
1563        &self,
1564        buffer: &Arc<Buffer>,
1565        snatch_guard: SnatchGuard,
1566    ) -> Result<(), BufferAccessError> {
1567        let submission = self.allocate_submission(snatch_guard)?;
1568
1569        let pending_writes = self.pending_writes.lock();
1570        if !pending_writes.contains_buffer(buffer) {
1571            return Ok(());
1572        }
1573
1574        submission.submit(pending_writes)?;
1575
1576        Ok(())
1577    }
1578
1579    fn flush_pending_writes(&self) -> Result<Option<SubmissionIndex>, DeviceError> {
1580        let snatch_guard = self.device.snatchable_lock.read();
1581        let submission = self.allocate_submission(snatch_guard)?;
1582        let submit_index = submission.index;
1583        let pending_writes = self.pending_writes.lock();
1584        if pending_writes.is_recording {
1585            submission.submit(pending_writes)?;
1586            Ok(Some(submit_index))
1587        } else {
1588            Ok(None)
1589        }
1590    }
1591
1592    #[cfg(feature = "trace")]
1593    fn trace_submission(
1594        &self,
1595        submit_index: SubmissionIndex,
1596        commands: Vec<crate::command::Command<crate::command::PointerReferences>>,
1597    ) {
1598        if let Some(ref mut trace) = *self.device.trace.lock() {
1599            trace.add(Action::Submit(submit_index, commands));
1600        }
1601    }
1602
1603    #[cfg(feature = "trace")]
1604    fn trace_failed_submission(
1605        &self,
1606        submit_index: SubmissionIndex,
1607        commands: Option<Vec<crate::command::Command<crate::command::PointerReferences>>>,
1608        error: String,
1609    ) {
1610        if let Some(ref mut trace) = *self.device.trace.lock() {
1611            trace.add(Action::FailedCommands {
1612                commands,
1613                failed_at_submit: Some(submit_index),
1614                error,
1615            });
1616        }
1617    }
1618
1619    fn submit_inner(
1620        &self,
1621        command_buffers: &[Arc<CommandBuffer>],
1622    ) -> Result<SubmissionIndex, Box<(Option<SubmissionIndex>, QueueSubmitError)>> {
1623        profiling::scope!("Queue::submit");
1624        api_log!("Queue::submit");
1625
1626        let snatch_guard = self.device.snatchable_lock.read();
1627        let mut submission = self
1628            .allocate_submission(snatch_guard)
1629            .map_err(|e| (None, e.into()))?;
1630        let submit_index = submission.index;
1631
1632        // If we encounter an error after we have started updating global state and before
1633        // successful submission, we must lose the device to avoid continuing with
1634        // potentially inaccurate resource state.
1635        let mut lose_device_on_error = false;
1636        let mut successfully_submitted = false;
1637
1638        let res = 'error: {
1639            let mut used_surface_textures = track::TextureUsageScope::default();
1640            let mut baked_command_buffers = Vec::with_capacity(command_buffers.len());
1641
1642            if !command_buffers.is_empty() {
1643                profiling::scope!("prepare");
1644
1645                let mut first_error = None;
1646
1647                // We are required to invalidate all command buffers in both the success and
1648                // failure paths, so we `continue` after errors, and disallow `?`.
1649                #[deny(clippy::question_mark_used)]
1650                for command_buffer in command_buffers {
1651                    profiling::scope!("process command buffer");
1652
1653                    // we reset the used surface textures every time we use
1654                    // it, so make sure to set_size on it.
1655                    used_surface_textures.set_size(self.device.tracker_indices.textures.size());
1656
1657                    // Anything other than our own submission work in the remainder of this
1658                    // function that attempts to use the WebGPU command buffer after this
1659                    // point, will find it vacant (invalid), and produce an error.
1660                    #[cfg_attr(not(feature = "trace"), expect(unused_mut))]
1661                    let mut cmd_buf_data = command_buffer.take_finished();
1662
1663                    if first_error.is_some() {
1664                        continue;
1665                    }
1666
1667                    #[cfg(feature = "trace")]
1668                    let trace_commands = cmd_buf_data
1669                        .as_mut()
1670                        .ok()
1671                        .and_then(|data| mem::take(&mut data.trace_commands));
1672
1673                    let mut baked = match cmd_buf_data {
1674                        Ok(cmd_buf_data) => {
1675                            let res = validate_command_buffer(
1676                                command_buffer,
1677                                self,
1678                                &cmd_buf_data,
1679                                &submission.snatch_guard,
1680                                &mut submission.surface_textures,
1681                                &mut used_surface_textures,
1682                                &mut submission.command_index_guard,
1683                            );
1684                            if let Err(err) = res {
1685                                #[cfg(feature = "trace")]
1686                                self.trace_failed_submission(
1687                                    submit_index,
1688                                    trace_commands,
1689                                    err.to_string(),
1690                                );
1691                                first_error.get_or_insert(err);
1692                                continue;
1693                            }
1694
1695                            #[cfg(feature = "trace")]
1696                            if let Some(commands) = trace_commands {
1697                                self.trace_submission(submit_index, commands);
1698                            }
1699
1700                            cmd_buf_data
1701                                .set_acceleration_structure_dependencies(&submission.snatch_guard);
1702                            cmd_buf_data.into_baked_commands()
1703                        }
1704                        Err(err) => {
1705                            #[cfg(feature = "trace")]
1706                            self.trace_failed_submission(
1707                                submit_index,
1708                                trace_commands,
1709                                err.to_string(),
1710                            );
1711                            first_error.get_or_insert(err.into());
1712                            continue;
1713                        }
1714                    };
1715
1716                    if let Err(e) = baked
1717                        .process_deferred_query_set_resolves(&self.device, &submission.snatch_guard)
1718                    {
1719                        break 'error Err(e.into());
1720                    }
1721
1722                    baked_command_buffers.push(baked);
1723                }
1724
1725                if let Some(first_error) = first_error {
1726                    break 'error Err(first_error);
1727                }
1728
1729                // At this point we have validated all the command buffers, and we start
1730                // making updates to global state. If we fail between here and successful
1731                // submission, we must lose the device, or else we could leave that global
1732                // state inaccurate or inconsistent.
1733                //
1734                // At time of writing, there were two kinds of errors that can occur in
1735                // this stage:
1736                //  - `hal` command encoding errors. These produce device loss in
1737                //    `handle_hal_error`, independent of what we do here.
1738                //  - Errors from texture initialization. The error cases that
1739                //    can actually occur should also be encoder errors, but we map
1740                //    everything to device loss, just in case.
1741                lose_device_on_error = true;
1742
1743                // Note: locking the trackers has to be done after the storages
1744                let mut trackers = self.device.trackers.lock();
1745
1746                for mut baked in baked_command_buffers {
1747                    profiling::scope!("process baked commands");
1748
1749                    // execute resource transitions
1750                    if let Err(e) = baked.encoder.open_pass(hal_label(
1751                        Some("(wgpu internal) Transit"),
1752                        self.device.instance_flags,
1753                    )) {
1754                        break 'error Err(e.into());
1755                    }
1756
1757                    baked.initialize_buffer_memory(&mut trackers, &submission.snatch_guard);
1758
1759                    let depth_slice_discards = match baked.initialize_texture_memory(
1760                        &mut trackers,
1761                        &self.device,
1762                        &submission.snatch_guard,
1763                    ) {
1764                        Ok(discards) => discards,
1765                        Err(e) => {
1766                            break 'error Err(QueueSubmitError::CommandEncoder(
1767                                CommandEncoderError::Clear(e),
1768                            ));
1769                        }
1770                    };
1771
1772                    //Note: stateless trackers are not merged:
1773                    // device already knows these resources exist.
1774                    CommandEncoder::insert_barriers_from_device_tracker(
1775                        baked.encoder.raw.as_mut(),
1776                        &mut trackers,
1777                        &baked.trackers,
1778                        &submission.snatch_guard,
1779                    );
1780
1781                    if let Err(e) = baked.encoder.close_and_push_front() {
1782                        break 'error Err(e.into());
1783                    }
1784
1785                    if !depth_slice_discards.is_empty() || !used_surface_textures.is_empty() {
1786                        if let Err(e) = baked.encoder.open_pass(hal_label(
1787                            Some("(wgpu internal) Finalize"),
1788                            self.device.instance_flags,
1789                        )) {
1790                            break 'error Err(e.into());
1791                        }
1792
1793                        if let Err(e) = baked.initialize_discarded_depth_slices(
1794                            depth_slice_discards,
1795                            &mut trackers,
1796                            &self.device,
1797                            &submission.snatch_guard,
1798                        ) {
1799                            break 'error Err(QueueSubmitError::CommandEncoder(
1800                                CommandEncoderError::Clear(e),
1801                            ));
1802                        }
1803
1804                        // Transition surface textures into `Present` state.
1805                        // Note: we could technically do it after all of the command buffers,
1806                        // but here we have a command encoder by hand, so it's easier to use it.
1807                        let texture_barriers = trackers
1808                            .textures
1809                            .set_from_usage_scope_and_drain_transitions(
1810                                &used_surface_textures,
1811                                &submission.snatch_guard,
1812                            )
1813                            .collect::<Vec<_>>();
1814                        unsafe {
1815                            baked.encoder.raw.transition_textures(&texture_barriers);
1816                        };
1817                        if let Err(e) = baked.encoder.close() {
1818                            break 'error Err(e.into());
1819                        }
1820                        used_surface_textures = track::TextureUsageScope::default();
1821                    }
1822
1823                    // done
1824                    submission.executions.push(EncoderInFlight {
1825                        inner: baked.encoder,
1826                        trackers: baked.trackers,
1827                        temp_resources: baked.temp_resources,
1828                        _indirect_draw_validation_resources: baked
1829                            .indirect_draw_validation_resources,
1830                        pending_buffers: FastHashMap::default(),
1831                        pending_textures: FastHashMap::default(),
1832                        pending_blas_s: FastHashMap::default(),
1833                    });
1834                }
1835            }
1836
1837            let pending_writes = self.pending_writes.lock();
1838
1839            let SubmissionResult { snatch_guard } = match submission.submit(pending_writes) {
1840                Ok(result) => result,
1841                Err(e) => break 'error Err(e.into()),
1842            };
1843            successfully_submitted = true;
1844
1845            profiling::scope!("cleanup");
1846
1847            // Failing in `Device::maintain` won't put resource state at risk, but returning
1848            // an error from a successful submission could be confusing, do we really want
1849            // to do that?
1850            lose_device_on_error = false;
1851
1852            // This will schedule destruction of all resources that are no longer needed
1853            // by the user but used in the command stream, among other things.
1854            // `device.maintain` consumes and will release the snatch guard.
1855            let (closures, result) = self.device.maintain(wgt::PollType::Poll, snatch_guard);
1856            match result {
1857                Ok(status) => {
1858                    debug_assert!(matches!(
1859                        status,
1860                        wgt::PollStatus::QueueEmpty | wgt::PollStatus::Poll
1861                    ));
1862                }
1863                Err(WaitIdleError::Device(err)) => break 'error Err(QueueSubmitError::Queue(err)),
1864                Err(WaitIdleError::WrongSubmissionIndex(..)) => {
1865                    unreachable!("Cannot get WrongSubmissionIndex from Poll")
1866                }
1867                Err(WaitIdleError::Timeout) => unreachable!("Cannot get Timeout from Poll"),
1868            };
1869
1870            Ok(closures)
1871        }; // 'error
1872
1873        let callbacks = match res {
1874            Ok(ok) => ok,
1875            Err(e) => {
1876                if lose_device_on_error {
1877                    self.device.lose("submission failed");
1878                }
1879                return Err(Box::new((
1880                    successfully_submitted.then_some(submit_index),
1881                    e,
1882                )));
1883            }
1884        };
1885
1886        // the closures should execute with nothing locked!
1887        callbacks.fire();
1888
1889        self.device.lose_if_oom();
1890
1891        api_log!("Queue::submit returned submit index {submit_index}");
1892
1893        Ok(submit_index)
1894    }
1895
1896    pub fn submit(&self, command_buffers: &[Arc<CommandBuffer>]) -> Option<SubmissionIndex> {
1897        match self.submit_inner(command_buffers) {
1898            Ok(submit_index) => Some(submit_index),
1899            Err(e) => {
1900                let (submit_index, e) = *e;
1901                self.device
1902                    .handle_error(e, Some(self.label()), "Queue::submit");
1903                submit_index
1904            }
1905        }
1906    }
1907
1908    /// Allocate a submission index and prepare for a submission.
1909    ///
1910    /// This is an internal API used in [`Queue::submit`] and other places within
1911    /// `wgpu-core` that need to submit work.
1912    ///
1913    /// Returns the index and a [`PendingSubmission`].
1914    ///
1915    /// The caller passes in the already-acquired [`SnatchGuard`]. This function acquires
1916    /// the command index lock.
1917    ///
1918    /// The caller should update [`PendingSubmission::executions`] with details of the
1919    /// submission.
1920    ///
1921    /// To finalize and submit the submission, call [`PendingSubmission::submit`] (which is
1922    /// a convenience wrapper around [`Queue::submit_pending_submission`]).
1923    ///
1924    /// After calling this function and before submitting, the caller must acquire the
1925    /// pending writes lock, and pass it to `submit`.
1926    ///
1927    /// It is also acceptable to drop the `PendingSubmission` without submitting. This may
1928    /// be necessary when locks are required to access the state that determines whether a
1929    /// submission is needed.
1930    fn allocate_submission<'a>(
1931        &'a self,
1932        snatch_guard: SnatchGuard<'a>,
1933    ) -> Result<PendingSubmission<'a>, DeviceError> {
1934        let mut command_index_guard = self.device.command_indices.write();
1935        command_index_guard.active_submission_index += 1;
1936        let index = command_index_guard.active_submission_index;
1937
1938        self.device.check_is_valid()?;
1939
1940        let submission = PendingSubmission {
1941            queue: self,
1942            command_index_guard,
1943            snatch_guard,
1944            executions: Vec::new(),
1945            surface_textures: FastHashMap::default(),
1946            index,
1947        };
1948
1949        Ok(submission)
1950    }
1951
1952    /// Finalize and submit a [`PendingSubmission`] that was returned by
1953    /// [`Queue::allocate_submission`].
1954    ///
1955    /// This is an internal API used in `Queue::submit` and other places within
1956    /// `wgpu-core` that need to submit work. See [`Queue::allocate_submission`]
1957    /// for more details.
1958    ///
1959    /// This function:
1960    ///
1961    /// - Performs a HAL submission of the pending writes command
1962    ///   encoder and any other command encoders that were added to the
1963    ///   [`PendingSubmission`].
1964    /// - Advances `last_successful_submission_index` and registers the
1965    ///   submission with the lifetime tracker.
1966    /// - Returns a [`SubmissionResult`], which contains the snatch guard.
1967    fn submit_pending_submission<'a>(
1968        &self,
1969        mut pending_writes: MutexGuard<'_, PendingWrites>,
1970        prepared: PendingSubmission<'a>,
1971    ) -> Result<SubmissionResult<'a>, DeviceError> {
1972        let PendingSubmission {
1973            queue: _,
1974            snatch_guard,
1975            command_index_guard,
1976            mut executions,
1977            mut surface_textures,
1978            index: submit_index,
1979        } = prepared;
1980
1981        let mut used_surface_textures = track::TextureUsageScope::default();
1982        used_surface_textures.set_size(self.device.tracker_indices.textures.size());
1983        for texture in pending_writes.dst_textures.values() {
1984            match texture.try_inner(&snatch_guard) {
1985                Ok(TextureInner::Native { .. }) => {}
1986                Ok(TextureInner::Surface { .. }) => {
1987                    // Compare the Arcs by pointer as Textures don't implement Eq
1988                    surface_textures.insert(Arc::as_ptr(texture), texture.clone());
1989
1990                    unsafe {
1991                        used_surface_textures
1992                            .merge_single(texture, None, wgt::TextureUses::PRESENT)
1993                            .unwrap()
1994                    };
1995                }
1996                // The texture must not have been destroyed when its usage here was
1997                // encoded. If it was destroyed after that, then it was transferred
1998                // to `pending_writes.temp_resources` at the time of destruction, so
1999                // we are still okay to use it.
2000                Err(InvalidOrDestroyedResourceError::DestroyedResource(_)) => {}
2001                Err(InvalidOrDestroyedResourceError::InvalidResource(_)) => {
2002                    unreachable!()
2003                }
2004            }
2005        }
2006
2007        if !used_surface_textures.is_empty() {
2008            let mut trackers = self.device.trackers.lock();
2009
2010            let texture_barriers = trackers
2011                .textures
2012                .set_from_usage_scope_and_drain_transitions(&used_surface_textures, &snatch_guard)
2013                .collect::<Vec<_>>();
2014            unsafe {
2015                pending_writes
2016                    .command_encoder
2017                    .transition_textures(&texture_barriers);
2018            };
2019        }
2020
2021        match pending_writes.pre_submit(
2022            &self.device.command_allocator,
2023            &self.device,
2024            self,
2025            &snatch_guard,
2026        ) {
2027            Ok(Some(pending_execution)) => {
2028                executions.insert(0, pending_execution);
2029            }
2030            Ok(None) => {}
2031            Err(e) => return Err(e),
2032        }
2033        let hal_command_buffers = executions
2034            .iter()
2035            .flat_map(|e| e.inner.list.iter().map(|b| b.as_ref()))
2036            .collect::<Vec<_>>();
2037
2038        {
2039            let mut submit_surface_textures =
2040                SmallVec::<[&dyn hal::DynSurfaceTexture; 2]>::with_capacity(surface_textures.len());
2041            for texture in surface_textures.values() {
2042                let raw = match texture.try_inner(&snatch_guard).ok() {
2043                    Some(TextureInner::Surface { raw, .. }) => raw.as_ref(),
2044                    _ => unreachable!(),
2045                };
2046                submit_surface_textures.push(raw);
2047            }
2048
2049            unsafe {
2050                self.raw().submit(
2051                    &hal_command_buffers,
2052                    &submit_surface_textures,
2053                    (self.device.fence.as_ref(), submit_index),
2054                )
2055            }
2056            .map_err(|e| self.device.handle_hal_error(e))?;
2057
2058            // Submissions must have strictly increasing indices, so we must hold the
2059            // command index guard until we have submitted, to prevent another submission
2060            // from claiming the next index and reaching `submit` before we do.
2061            drop(pending_writes);
2062
2063            // Advance the successful submission index.
2064            self.device
2065                .last_successful_submission_index
2066                .fetch_max(submit_index, Ordering::SeqCst);
2067        }
2068
2069        // this will register the new submission to the life time tracker
2070        self.lock_life().track_submission(submit_index, executions);
2071
2072        // `device.maintain` relies on being able to prevent new submissions by
2073        // using `command_index_guard` while also checking whether there are
2074        // no tracked submissions to guarantee no new submissions will happen
2075        // after a device is lost. This requires `command_index_guard` to be
2076        // held over `self.lock_life()`
2077        drop(command_index_guard);
2078
2079        Ok(SubmissionResult { snatch_guard })
2080    }
2081
2082    pub(crate) fn get_raw_timestamp_period(&self) -> f32 {
2083        unsafe { self.raw().get_timestamp_period() }
2084    }
2085
2086    pub fn get_timestamp_period(&self) -> f32 {
2087        if self.device.timestamp_normalizer.get().unwrap().enabled() {
2088            return 1.0;
2089        }
2090
2091        self.get_raw_timestamp_period()
2092    }
2093
2094    /// `closure` is guaranteed to be called.
2095    pub fn on_submitted_work_done(
2096        &self,
2097        closure: SubmittedWorkDoneClosure,
2098    ) -> Option<SubmissionIndex> {
2099        api_log!("Queue::on_submitted_work_done");
2100
2101        // A `DeviceError` means we're losing the device anyways, so we can ignore it here
2102        // (mostly to avoid a breaking change to the `on_submitted_work_done` signature
2103        // for an error case that it is unlikely the caller will be able to handle).
2104        let _: Result<_, DeviceError> = self.flush_pending_writes();
2105
2106        self.lock_life().add_work_done_closure(closure)
2107    }
2108
2109    #[allow(trivial_casts)]
2110    pub fn compact_blas(&self, blas: &Arc<Blas>) -> (Arc<Blas>, Option<CompactBlasError>) {
2111        api_log!(
2112            "Queue::compact_blas {:?}, {:?}",
2113            self as *const _,
2114            Arc::as_ptr(blas)
2115        );
2116
2117        let (blas, error) = match self.compact_blas_inner(blas) {
2118            Ok(blas) => (blas, None),
2119            Err(err) => {
2120                let new_label = blas.label.clone() + " (compacted)";
2121                (
2122                    Blas::invalid(
2123                        self.device.clone(),
2124                        &BlasDescriptor {
2125                            label: Some(new_label.into()),
2126                            flags: blas.flags,
2127                            update_mode: blas.update_mode,
2128                        },
2129                    ),
2130                    Some(err),
2131                )
2132            }
2133        };
2134
2135        // TODO: Tracing
2136
2137        (blas, error)
2138    }
2139
2140    pub(crate) fn compact_blas_inner(
2141        &self,
2142        blas: &Arc<Blas>,
2143    ) -> Result<Arc<Blas>, CompactBlasError> {
2144        profiling::scope!("Queue::compact_blas");
2145        api_log!("Queue::compact_blas");
2146
2147        let new_label = blas.label.clone() + " (compacted)";
2148
2149        self.device.check_is_valid()?;
2150
2151        self.device
2152            .require_features(wgpu_types::Features::EXPERIMENTAL_RAY_QUERY)?;
2153
2154        blas.check_is_valid()?;
2155        self.same_device_as(blas.as_ref())?;
2156
2157        let device = blas.device.clone();
2158
2159        let snatch_guard = device.snatchable_lock.read();
2160
2161        let BlasCompactState::Ready { size } = *blas.compacted_state.lock() else {
2162            return Err(CompactBlasError::BlasNotReady);
2163        };
2164
2165        let mut size_info = blas.size_info;
2166        size_info.acceleration_structure_size = size;
2167
2168        let mut command_indices_lock = device.command_indices.write();
2169        let mut pending_writes = self.pending_writes.lock();
2170        let cmd_buf_raw = pending_writes.activate();
2171
2172        let raw = unsafe {
2173            device
2174                .raw()
2175                .create_acceleration_structure(&hal::AccelerationStructureDescriptor {
2176                    label: hal_label(Some(&new_label), device.instance_flags),
2177                    size: size_info.acceleration_structure_size,
2178                    format: hal::AccelerationStructureFormat::BottomLevel,
2179                    allow_compaction: false,
2180                })
2181        }
2182        .map_err(DeviceError::from_hal)?;
2183
2184        let src_raw = blas.try_raw(&snatch_guard)?;
2185
2186        unsafe {
2187            cmd_buf_raw.copy_acceleration_structure_to_acceleration_structure(
2188                src_raw,
2189                raw.as_ref(),
2190                wgt::AccelerationStructureCopy::Compact,
2191            )
2192        };
2193
2194        let handle = unsafe {
2195            device
2196                .raw()
2197                .get_acceleration_structure_device_address(raw.as_ref())
2198        };
2199
2200        command_indices_lock.next_acceleration_structure_build_command_index += 1;
2201        let built_index =
2202            NonZeroU64::new(command_indices_lock.next_acceleration_structure_build_command_index)
2203                .unwrap();
2204
2205        let new_blas = Arc::new(Blas {
2206            state: ResourceState::Valid(BlasState {
2207                raw: Snatchable::new(raw),
2208            }),
2209            device: device.clone(),
2210            size_info,
2211            sizes: blas.sizes.clone(),
2212            flags: blas.flags & !AccelerationStructureFlags::ALLOW_COMPACTION,
2213            update_mode: blas.update_mode,
2214            // Bypass the submit checks which update this because we don't submit this normally.
2215            built_index: RwLock::new(rank::BLAS_BUILT_INDEX, Some(built_index)),
2216            handle,
2217            label: new_label,
2218            tracking_data: TrackingData::new(blas.device.tracker_indices.blas_s.clone()),
2219            compaction_buffer: None,
2220            compacted_state: Mutex::new(rank::BLAS_COMPACTION_STATE, BlasCompactState::Compacted),
2221        });
2222
2223        pending_writes.insert_blas(blas);
2224        pending_writes.insert_blas(&new_blas);
2225
2226        // We should have no more errors after this because we have marked the command encoder as successful.
2227        let old_blas_size = blas.size_info.acceleration_structure_size;
2228        let new_blas_size = new_blas.size_info.acceleration_structure_size;
2229
2230        api_log!("CommandEncoder::compact_blas {:?} (size: {old_blas_size}) -> {:?} (size: {new_blas_size})", Arc::as_ptr(blas), Arc::as_ptr(&new_blas));
2231
2232        Ok(new_blas)
2233    }
2234}
2235
2236fn validate_command_buffer(
2237    command_buffer: &CommandBuffer,
2238    queue: &Queue,
2239    cmd_buf_data: &crate::command::CommandBufferMutable,
2240    snatch_guard: &SnatchGuard,
2241    surface_textures: &mut FastHashMap<*const Texture, Arc<Texture>>,
2242    used_surface_textures: &mut track::TextureUsageScope,
2243    command_index_guard: &mut RwLockWriteGuard<CommandIndices>,
2244) -> Result<(), QueueSubmitError> {
2245    command_buffer.same_device_as(queue)?;
2246
2247    {
2248        profiling::scope!("check resource state");
2249
2250        {
2251            profiling::scope!("buffers");
2252            for buffer in cmd_buf_data.trackers.buffers.used_resources() {
2253                buffer.check_destroyed(snatch_guard)?;
2254
2255                match *buffer.map_state.read() {
2256                    BufferMapState::Idle => (),
2257                    _ => return Err(QueueSubmitError::BufferStillMapped(buffer.error_ident())),
2258                }
2259            }
2260        }
2261        {
2262            profiling::scope!("textures");
2263            for texture in cmd_buf_data.trackers.textures.used_resources() {
2264                let should_extend = match texture.try_inner(snatch_guard)? {
2265                    TextureInner::Native { .. } => false,
2266                    TextureInner::Surface { .. } => {
2267                        // Compare the Arcs by pointer as Textures don't implement Eq.
2268                        surface_textures.insert(Arc::as_ptr(texture), texture.clone());
2269
2270                        true
2271                    }
2272                };
2273                if should_extend {
2274                    unsafe {
2275                        used_surface_textures
2276                            .merge_single(texture, None, wgt::TextureUses::PRESENT)
2277                            .unwrap();
2278                    };
2279                }
2280            }
2281        }
2282        {
2283            profiling::scope!("query sets");
2284            for query_set in cmd_buf_data.trackers.query_sets.used_resources() {
2285                query_set.try_raw(snatch_guard)?;
2286            }
2287        }
2288        // WebGPU requires that we check every bind group referenced during
2289        // encoding, even ones that may have been replaced before being used.
2290        // TODO(<https://github.com/gfx-rs/wgpu/issues/8510>): Optimize this.
2291        {
2292            profiling::scope!("bind groups");
2293            for bind_group in &cmd_buf_data.trackers.bind_groups {
2294                // This checks the bind group and all resources it references.
2295                bind_group.try_raw(snatch_guard)?;
2296            }
2297        }
2298
2299        if let Err(e) =
2300            cmd_buf_data.validate_acceleration_structure_actions(snatch_guard, command_index_guard)
2301        {
2302            return Err(e.into());
2303        }
2304    }
2305    Ok(())
2306}