Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/wgpu-core/src/device/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 77 kB image not shown  

Quelle  queue.rs

  Sprache: Rust
 

use alloc::{boxed::Box, string::ToString, sync::Arc, vec, vec::Vec};
use core::{
    iter,
    mem::{self, ManuallyDrop},
    num::NonZeroU64,
    ptr::NonNull,
    sync::atomic::Ordering,
};
use smallvec::SmallVec;
use thiserror::Error;
use wgt::{
    error::{ErrorType, WebGpuError},
    AccelerationStructureFlags,
};

use super::{life::LifetimeTracker, Device};
#[cfg(feature = "trace")]
use crate::device::trace::{Action, IntoTrace};
use crate::{
    api_log,
    command::{
        extract_texture_selector, validate_linear_texture_data, validate_texture_buffer_copy,
        validate_texture_copy_dst_format, validate_texture_copy_range, ClearError,
        CommandAllocator, CommandBuffer, CommandEncoder, CommandEncoderError, CopySide,
        TransferError,
    },
    device::{DeviceError, WaitIdleError},
    get_lowest_common_denom,
    global::Global,
    hal_label,
    id::{self, BlasId, QueueId},
    init_tracker::{has_copy_partial_init_tracker_coverage, TextureInitRange},
    lock::{rank, Mutex, MutexGuard, RwLock, RwLockWriteGuard},
    ray_tracing::{BlasCompactReadyPendingClosure, CompactBlasError},
    resource::{
        Blas, BlasCompactState, Buffer, BufferAccessError, BufferMapState, DestroyedBuffer,
        DestroyedResourceError, DestroyedTexture, Fallible, FlushedStagingBuffer,
        InvalidResourceError, Labeled, ParentDevice, ResourceErrorIdent, StagingBuffer, Texture,
        TextureInner, Trackable, TrackingData,
    },
    resource_log,
    scratch::ScratchBuffer,
    snatch::{SnatchGuard, Snatchable},
    track::{self, Tracker, TrackerIndex},
    FastHashMap, SubmissionIndex,
};
use crate::{device::resource::CommandIndices, resource::RawResourceAccess};

pub struct Queue {
    raw: Box<dyn hal::DynQueue>,
    pub(crate) pending_writes: Mutex<PendingWrites>,
    life_tracker: Mutex<LifetimeTracker>,
    // The device needs to be dropped last (`Device.zero_buffer` might be referenced by the encoder in pending writes).
    pub(crate) device: Arc<Device>,
}

impl Queue {
    pub(cratefn new(
        device: Arc<Device>,
        raw: Box<dyn hal::DynQueue>,
        instance_flags: wgt::InstanceFlags,
    ) -> Result<Self, DeviceError> {
        let pending_encoder = device
            .command_allocator
            .acquire_encoder(device.raw(), raw.as_ref())
            .map_err(DeviceError::from_hal);

        let pending_encoder = match pending_encoder {
            Ok(pending_encoder) => pending_encoder,
            Err(e) => {
                return Err(e);
            }
        };

        let mut pending_writes = PendingWrites::new(pending_encoder, instance_flags);

        let zero_buffer = device.zero_buffer.as_ref();
        pending_writes.activate();
        unsafe {
            pending_writes
                .command_encoder
                .transition_buffers(&[hal::BufferBarrier {
                    buffer: zero_buffer,
                    usage: hal::StateTransition {
                        from: wgt::BufferUses::empty(),
                        to: wgt::BufferUses::COPY_DST,
                    },
                }]);
            pending_writes
                .command_encoder
                .clear_buffer(zero_buffer, 0..super::ZERO_BUFFER_SIZE);
            pending_writes
                .command_encoder
                .transition_buffers(&[hal::BufferBarrier {
                    buffer: zero_buffer,
                    usage: hal::StateTransition {
                        from: wgt::BufferUses::COPY_DST,
                        to: wgt::BufferUses::COPY_SRC,
                    },
                }]);
        }

        Ok(Queue {
            raw,
            device,
            pending_writes: Mutex::new(rank::QUEUE_PENDING_WRITES, pending_writes),
            life_tracker: Mutex::new(rank::QUEUE_LIFE_TRACKER, LifetimeTracker::new()),
        })
    }

    pub(cratefn raw(&self) -> &dyn hal::DynQueue {
        self.raw.as_ref()
    }

    #[track_caller]
    pub(cratefn lock_life<'a>(&'self) -> MutexGuard<'a, LifetimeTracker> {
        self.life_tracker.lock()
    }

    /// Ensure the surface texture is in the PRESENT state, clearing it if it was never rendered to.
    /// Submits any necessary work to the GPU before the HAL present call.
    ///
    /// See <https://github.com/gfx-rs/wgpu/issues/6748>
    pub(cratefn prepare_surface_texture_for_present(
        &self,
        texture: &Arc<Texture>,
    ) -> Result<(), DeviceError> {
        let snatch_guard = self.device.snatchable_lock.read();
        let submission = self
            .allocate_submission(snatch_guard)
            .map_err(|(_index, e)| e)?;
        let device = &self.device;

        // If the texture is uninitialized it needs to be cleared before presenting
        let needs_clear = {
            let status = texture.initialization_status.read();
            status
                .mips
                .first()
                .is_some_and(|mip| mip.check(0..1).is_some())
        };

        let mut pending_writes = self.pending_writes.lock();

        if needs_clear {
            // After encoding the clear operation, we must not return without
            // adding the texture to `pending_writes`.
            let encoder = pending_writes.activate();
            let mut trackers = device.trackers.lock();
            crate::command::clear_texture(
                texture,
                TextureInitRange {
                    mip_range: 0..1,
                    layer_range: 0..1,
                },
                encoder,
                &mut trackers.textures,
                &device.alignments,
                device.zero_buffer.as_ref(),
                &submission.snatch_guard,
                device.instance_flags,
            )
            .map_err(|e| match e {
                ClearError::Device(e) => e,
                _ => DeviceError::Lost,
            })?;
            texture.initialization_status.write().mips[0].drain(0..1);
        }

        // Transition the texture to PRESENT in the device tracker.
        // If it's already in PRESENT, this produces no barriers and we can skip the submission.
        //
        // This has to be after any clear_texture call because clear_texture modifies the tracker state internally.
        // Computing transitions afterward ensures they reflect the actual current state.
        let pending = {
            let mut trackers = device.trackers.lock();
            let pending: Vec<track::PendingTransition<wgt::TextureUses>> = trackers
                .textures
                .set_single(
                    texture,
                    texture.full_range.clone(),
                    wgt::TextureUses::PRESENT,
                )
                .collect();
            pending
        };

        if pending.is_empty() {
            // This assert checks that we don't return here if we encoded a
            // clear operation for the texture, which would be a problem since
            // we haven't done anything yet to ensure it stays alive. If we
            // cleared the texture, then we must have produced a barrier to put
            // it in PRESENT state, so `pending` will not be empty.
            debug_assert!(!needs_clear);
            return Ok(());
        }

        // Emit the transition barriers to PRESENT.
        {
            let raw_texture = texture
                .try_raw(&submission.snatch_guard)
                .map_err(|_| DeviceError::Lost)?;
            let barriers: Vec<hal::TextureBarrier<'_, dyn hal::DynTexture>> = pending
                .into_iter()
                .map(|pt| pt.into_hal(raw_texture))
                .collect();

            let encoder = pending_writes.activate();
            // SAFETY:
            // - The encoder is in the recording state after `activate()`
            // - The texture is kept alive by adding it to `PendingWrites` below
            unsafe {
                encoder.transition_textures(&barriers);
            }
        }

        // Add the texture to `PendingWrites`. This will cause `submit()` to:
        // - Flush any pending writes to the texture.
        // - Include the texture in `surface_textures` for the submission.
        // - Keep the texture alive so the texture and its clear_view aren't
        //   destroyed before the GPU finishes the `clear_texture` operation
        //   encoded above.
        pending_writes.insert_texture(texture);

        submission.submit(pending_writes)?;

        Ok(())
    }

    /// Maintains the queue's list of finished command buffers.
    ///
    /// Command buffers submitted before `submission_index` was submitted
    /// stop being tracked and callbacks which are waiting for them are
    /// returned. Also returned is whether the queue is empty. This may
    /// be stale unless new submissions are prevented by locking
    /// [`Device::command_indices`]
    pub(cratefn maintain(
        &self,
        submission_index: u64,
        snatch_guard: &SnatchGuard,
    ) -> (
        SmallVec<[SubmittedWorkDoneClosure; 1]>,
        Vec<super::BufferMapPendingClosure>,
        Vec<BlasCompactReadyPendingClosure>,
        bool,
    ) {
        let mut life_tracker = self.lock_life();
        let submission_closures = life_tracker.triage_submissions(submission_index);

        let mapping_closures = life_tracker.handle_mapping(snatch_guard);
        let blas_closures = life_tracker.handle_compact_read_back();

        let queue_empty = life_tracker.queue_empty();

        (
            submission_closures,
            mapping_closures,
            blas_closures,
            queue_empty,
        )
    }
}

crate::impl_resource_type!(Queue);
// TODO: https://github.com/gfx-rs/wgpu/issues/4014
impl Labeled for Queue {
    fn label(&self) -> &str {
        ""
    }
}
crate::impl_parent_device!(Queue);
crate::impl_storage_item!(Queue);

impl Drop for Queue {
    fn drop(&mut self) {
        resource_log!("Drop {}"self.error_ident());

        // On Vulkan, pending presents are not tracked by fences.
        // wait_for_idle covers both fence-tracked submissions and pending presents.
        match unsafe { self.raw.wait_for_idle() } {
            Ok(()) => {}
            Err(hal::DeviceError::Lost) => {
                self.device.handle_hal_error(hal::DeviceError::Lost);
            }
            Err(e) => {
                panic!("Unexpected error while waiting for queue idle on drop: {e:?}");
            }
        }

        let last_successful_submission_index = self
            .device
            .last_successful_submission_index
            .load(Ordering::Acquire);

        let snatch_guard = self.device.snatchable_lock.read();
        let (submission_closures, mapping_closures, blas_compact_ready_closures, queue_empty) =
            self.maintain(last_successful_submission_index, &snatch_guard);
        drop(snatch_guard);

        assert!(queue_empty);

        let closures = crate::device::UserClosures {
            mappings: mapping_closures,
            blas_compact_ready: blas_compact_ready_closures,
            submissions: submission_closures,
            device_lost_invocations: SmallVec::new(),
        };

        closures.fire();
    }
}

#[cfg(send_sync)]
pub type SubmittedWorkDoneClosure = Box<dyn FnOnce() + Send + 'static>;
#[cfg(not(send_sync))]
pub type SubmittedWorkDoneClosure = Box<dyn FnOnce() + 'static>;

/// A texture or buffer to be freed soon.
///
/// This is just a tagged raw texture or buffer, generally about to be added to
/// some other more specific container like:
///
/// - `PendingWrites::temp_resources`: resources used by queue writes and
///   unmaps, waiting to be folded in with the next queue submission
///
/// - `ActiveSubmission::temp_resources`: temporary resources used by a queue
///   submission, to be freed when it completes
#[derive(Debug)]
pub enum TempResource {
    StagingBuffer(FlushedStagingBuffer),
    ScratchBuffer(ScratchBuffer),
    DestroyedBuffer(DestroyedBuffer),
    DestroyedTexture(DestroyedTexture),
}

/// A series of raw [`CommandBuffer`]s that have been submitted to a
/// queue, and the [`wgpu_hal::CommandEncoder`] that built them.
///
/// [`CommandBuffer`]: hal::Api::CommandBuffer
/// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
pub(cratestruct EncoderInFlight {
    inner: crate::command::InnerCommandEncoder,
    pub(crate) trackers: Tracker,
    pub(crate) temp_resources: Vec<TempResource>,
    /// We only need to keep these resources alive.
    _indirect_draw_validation_resources: crate::indirect_validation::DrawResources,

    /// These are the buffers that have been tracked by `PendingWrites`.
    pub(crate) pending_buffers: FastHashMap<TrackerIndex, Arc<Buffer>>,
    /// These are the textures that have been tracked by `PendingWrites`.
    pub(crate) pending_textures: FastHashMap<TrackerIndex, Arc<Texture>>,
    /// These are the BLASes that have been tracked by `PendingWrites`.
    pub(crate) pending_blas_s: FastHashMap<TrackerIndex, Arc<Blas>>,
}

/// A private command encoder for writes made directly on the device
/// or queue.
///
/// Operations like `buffer_unmap`, `queue_write_buffer`, and
/// `queue_write_texture` need to copy data to the GPU. At the hal
/// level, this must be done by encoding and submitting commands, but
/// these operations are not associated with any specific wgpu command
/// buffer.
///
/// Instead, `Device::pending_writes` owns one of these values, which
/// has its own hal command encoder and resource lists. The commands
/// accumulated here are automatically submitted to the queue at the
/// sooner of:
///
/// 1. The user's next wgpu command buffer submission. (Pending writes
///    are inserted ahead of the user's commands.)
/// 2. The next `mapAsync` request for a buffer that has pending
///    writes.
///
/// Important:
/// When locking pending_writes be sure that tracker is not locked
/// and try to lock trackers for the minimum timespan possible
///
/// All uses of [`StagingBuffer`]s end up here.
#[derive(Debug)]
pub(cratestruct PendingWrites {
    // The command encoder needs to be destroyed before any other resource in pending writes.
    pub command_encoder: Box<dyn hal::DynCommandEncoder>,

    /// True if `command_encoder` is in the "recording" state, as
    /// described in the docs for the [`wgpu_hal::CommandEncoder`]
    /// trait.
    ///
    /// [`wgpu_hal::CommandEncoder`]: hal::CommandEncoder
    pub is_recording: bool,

    temp_resources: Vec<TempResource>,
    dst_buffers: FastHashMap<TrackerIndex, Arc<Buffer>>,
    dst_textures: FastHashMap<TrackerIndex, Arc<Texture>>,
    copied_blas_s: FastHashMap<TrackerIndex, Arc<Blas>>,
    instance_flags: wgt::InstanceFlags,
}

impl PendingWrites {
    pub fn new(
        command_encoder: Box<dyn hal::DynCommandEncoder>,
        instance_flags: wgt::InstanceFlags,
    ) -> Self {
        Self {
            command_encoder,
            is_recording: false,
            temp_resources: Vec::new(),
            dst_buffers: FastHashMap::default(),
            dst_textures: FastHashMap::default(),
            copied_blas_s: FastHashMap::default(),
            instance_flags,
        }
    }

    pub fn insert_buffer(&mut self, buffer: &Arc<Buffer>) {
        self.dst_buffers
            .insert(buffer.tracker_index(), buffer.clone());
    }

    pub fn insert_texture(&mut self, texture: &Arc<Texture>) {
        self.dst_textures
            .insert(texture.tracker_index(), texture.clone());
    }

    pub fn insert_blas(&mut self, blas: &Arc<Blas>) {
        self.copied_blas_s
            .insert(blas.tracker_index(), blas.clone());
    }

    pub fn contains_buffer(&self, buffer: &Arc<Buffer>) -> bool {
        self.dst_buffers.contains_key(&buffer.tracker_index())
    }

    pub fn contains_texture(&self, texture: &Arc<Texture>) -> bool {
        self.dst_textures.contains_key(&texture.tracker_index())
    }

    pub fn consume_temp(&mut self, resource: TempResource) {
        self.temp_resources.push(resource);
    }

    pub fn consume(&mut self, buffer: FlushedStagingBuffer) {
        self.temp_resources
            .push(TempResource::StagingBuffer(buffer));
    }

    fn pre_submit(
        &mut self,
        command_allocator: &CommandAllocator,
        device: &Arc<Device>,
        queue: &Queue,
    ) -> Result<Option<EncoderInFlight>, DeviceError> {
        if self.is_recording {
            let pending_buffers = mem::take(&mut self.dst_buffers);
            let pending_textures = mem::take(&mut self.dst_textures);
            let pending_blas_s = mem::take(&mut self.copied_blas_s);

            let cmd_buf = unsafe { self.command_encoder.end_encoding() }
                .map_err(|e| device.handle_hal_error(e))?;
            self.is_recording = false;

            let new_encoder = command_allocator
                .acquire_encoder(device.raw(), queue.raw())
                .map_err(|e| device.handle_hal_error(e))?;

            let encoder = EncoderInFlight {
                inner: crate::command::InnerCommandEncoder {
                    raw: ManuallyDrop::new(mem::replace(&mut self.command_encoder, new_encoder)),
                    list: vec![cmd_buf],
                    device: device.clone(),
                    is_open: false,
                    api: crate::command::EncodingApi::InternalUse,
                    label: "(wgpu internal) PendingWrites command encoder".into(),
                },
                trackers: Tracker::new(device.ordered_buffer_usages, device.ordered_texture_usages),
                temp_resources: mem::take(&mut self.temp_resources),
                _indirect_draw_validation_resources: crate::indirect_validation::DrawResources::new(
                    device.clone(),
                ),
                pending_buffers,
                pending_textures,
                pending_blas_s,
            };
            Ok(Some(encoder))
        } else {
            self.dst_buffers.clear();
            self.dst_textures.clear();
            self.copied_blas_s.clear();
            Ok(None)
        }
    }

    pub fn activate(&mut self) -> &mut dyn hal::DynCommandEncoder {
        if !self.is_recording {
            unsafe {
                self.command_encoder
                    .begin_encoding(hal_label(
                        Some("(wgpu internal) PendingWrites"),
                        self.instance_flags,
                    ))
                    .unwrap();
            }
            self.is_recording = true;
        }
        self.command_encoder.as_mut()
    }
}

impl Drop for PendingWrites {
    fn drop(&mut self) {
        unsafe {
            if self.is_recording {
                self.command_encoder.discard_encoding();
            }
        }
    }
}

#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum QueueWriteError {
    #[error(transparent)]
    Queue(#[from] DeviceError),
    #[error(transparent)]
    Transfer(#[from] TransferError),
    #[error(transparent)]
    MemoryInitFailure(#[from] ClearError),
    #[error(transparent)]
    DestroyedResource(#[from] DestroyedResourceError),
    #[error(transparent)]
    InvalidResource(#[from] InvalidResourceError),
}

impl WebGpuError for QueueWriteError {
    fn webgpu_error_type(&self) -> ErrorType {
        match self {
            Self::Queue(e) => e.webgpu_error_type(),
            Self::Transfer(e) => e.webgpu_error_type(),
            Self::MemoryInitFailure(e) => e.webgpu_error_type(),
            Self::DestroyedResource(e) => e.webgpu_error_type(),
            Self::InvalidResource(e) => e.webgpu_error_type(),
        }
    }
}

#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum QueueSubmitError {
    #[error(transparent)]
    Queue(#[from] DeviceError),
    #[error(transparent)]
    DestroyedResource(#[from] DestroyedResourceError),
    #[error("{0} is still mapped")]
    BufferStillMapped(ResourceErrorIdent),
    #[error(transparent)]
    InvalidResource(#[from] InvalidResourceError),
    #[error(transparent)]
    CommandEncoder(#[from] CommandEncoderError),
    #[error(transparent)]
    ValidateAsActionsError(#[from] crate::ray_tracing::ValidateAsActionsError),
}

impl WebGpuError for QueueSubmitError {
    fn webgpu_error_type(&self) -> ErrorType {
        match self {
            Self::Queue(e) => e.webgpu_error_type(),
            Self::CommandEncoder(e) => e.webgpu_error_type(),
            Self::ValidateAsActionsError(e) => e.webgpu_error_type(),
            Self::InvalidResource(e) => e.webgpu_error_type(),
            Self::DestroyedResource(_) | Self::BufferStillMapped(_) => ErrorType::Validation,
        }
    }
}

/// A partially-assembled submission.
///
/// Returned from [`Queue::allocate_submission`] and consumed by [`submit`].
/// These are internal APIs used in `Queue::submit` and other places within
/// `wgpu-core` that need to submit work.
///
/// [`submit`]: `PendingSubmission::submit`
pub(cratestruct PendingSubmission<'a> {
    queue: &'a Queue,
    snatch_guard: SnatchGuard<'a>,
    command_index_guard: RwLockWriteGuard<'a, CommandIndices>,
    // Command buffers to be executed, along with trackers for the resources they use.
    pub executions: Vec<EncoderInFlight>,
    // Surface textures referenced by command buffers in this submission. These need to be
    // passed to the HAL `submit` call. Deduplicated using a hashmap to avoid vulkan
    // deadlocking from the same surface texture being submitted multiple times.
    surface_textures: FastHashMap<*const Texture, Arc<Texture>>,
    pub index: SubmissionIndex,
}

pub(cratestruct SubmissionResult<'a> {
    pub snatch_guard: SnatchGuard<'a>,
}

impl<'a> PendingSubmission<'a> {
    fn submit(
        self,
        pending_writes: MutexGuard<'a, PendingWrites>,
    ) -> Result<SubmissionResult<'a>, DeviceError> {
        self.queue.submit_pending_submission(pending_writes, self)
    }
}

//TODO: move out common parts of write_xxx.

impl Queue {
    pub fn write_buffer(
        &self,
        buffer: Arc<Buffer>,
        buffer_offset: wgt::BufferAddress,
        data: &[u8],
    ) -> Result<(), QueueWriteError> {
        profiling::scope!("Queue::write_buffer");
        api_log!("Queue::write_buffer");

        self.device.check_is_valid()?;

        let data_size = data.len() as wgt::BufferAddress;

        self.same_device_as(buffer.as_ref())?;

        let data_size = if let Some(data_size) = wgt::BufferSize::new(data_size) {
            data_size
        } else {
            // even though a zero-length write is a no-op and no copy operation will occur,
            // we must still validate the copy operation. This ensures that invalid
            // API calls—like writing to a mapped buffer or out-of-bounds offsets—are
            // caught consistently, even if no data is actually moved.
            self.validate_write_buffer_impl(buffer.as_ref(), buffer_offset, 0)?;

            log::trace!("Ignoring write_buffer of size 0");
            return Ok(());
        };

        // Platform validation requires that the staging buffer always be
        // freed, even if an error occurs. All paths from here must call
        // `device.pending_writes.consume`.
        let mut staging_buffer = StagingBuffer::new(&self.device, data_size)?;

        let staging_buffer = {
            profiling::scope!("copy");
            staging_buffer.write(data);
            staging_buffer.flush()
        };

        let snatch_guard = self.device.snatchable_lock.read();
        let mut pending_writes = self.pending_writes.lock();

        let result = self.write_staging_buffer_impl(
            &snatch_guard,
            &mut pending_writes,
            &staging_buffer,
            buffer,
            buffer_offset,
        );

        drop(snatch_guard);

        pending_writes.consume(staging_buffer);

        drop(pending_writes);

        result
    }

    pub fn create_staging_buffer(
        &self,
        buffer_size: wgt::BufferSize,
    ) -> Result<(StagingBuffer, NonNull<u8>), QueueWriteError> {
        profiling::scope!("Queue::create_staging_buffer");
        resource_log!("Queue::create_staging_buffer");

        self.device.check_is_valid()?;

        let staging_buffer = StagingBuffer::new(&self.device, buffer_size)?;
        let ptr = unsafe { staging_buffer.ptr() };

        Ok((staging_buffer, ptr))
    }

    pub fn write_staging_buffer(
        &self,
        buffer: Fallible<Buffer>,
        buffer_offset: wgt::BufferAddress,
        staging_buffer: StagingBuffer,
    ) -> Result<(), QueueWriteError> {
        profiling::scope!("Queue::write_staging_buffer");

        self.device.check_is_valid()?;

        let buffer = buffer.get()?;

        // At this point, we have taken ownership of the staging_buffer from the
        // user. Platform validation requires that the staging buffer always
        // be freed, even if an error occurs. All paths from here must call
        // `device.pending_writes.consume`.
        let staging_buffer = staging_buffer.flush();

        let snatch_guard = self.device.snatchable_lock.read();
        let mut pending_writes = self.pending_writes.lock();

        let result = self.write_staging_buffer_impl(
            &snatch_guard,
            &mut pending_writes,
            &staging_buffer,
            buffer,
            buffer_offset,
        );

        drop(snatch_guard);

        pending_writes.consume(staging_buffer);

        drop(pending_writes);

        result
    }

    pub fn validate_write_buffer(
        &self,
        buffer: Fallible<Buffer>,
        buffer_offset: u64,
        buffer_size: wgt::BufferSize,
    ) -> Result<(), QueueWriteError> {
        profiling::scope!("Queue::validate_write_buffer");

        self.device.check_is_valid()?;

        let buffer = buffer.get()?;

        self.validate_write_buffer_impl(&buffer, buffer_offset, buffer_size.into())?;

        Ok(())
    }

    fn validate_write_buffer_impl(
        &self,
        buffer: &Buffer,
        buffer_offset: u64,
        buffer_size: u64,
    ) -> Result<(), TransferError> {
        if !matches!(&*buffer.map_state.lock(), BufferMapState::Idle) {
            return Err(TransferError::BufferNotAvailable);
        }
        buffer.check_usage(wgt::BufferUsages::COPY_DST)?;
        if !buffer_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
            return Err(TransferError::UnalignedCopySize(buffer_size));
        }
        if !buffer_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) {
            return Err(TransferError::UnalignedBufferOffset(buffer_offset));
        }

        if buffer_offset > buffer.size {
            return Err(TransferError::BufferStartOffsetOverrun {
                start_offset: buffer_offset,
                buffer_size: buffer.size,
                side: CopySide::Destination,
            });
        }
        if buffer_size > buffer.size - buffer_offset {
            return Err(TransferError::BufferEndOffsetOverrun {
                start_offset: buffer_offset,
                size: buffer_size,
                buffer_size: buffer.size,
                side: CopySide::Destination,
            });
        }

        Ok(())
    }

    fn write_staging_buffer_impl(
        &self,
        snatch_guard: &SnatchGuard,
        pending_writes: &mut PendingWrites,
        staging_buffer: &FlushedStagingBuffer,
        buffer: Arc<Buffer>,
        buffer_offset: u64,
    ) -> Result<(), QueueWriteError> {
        self.device.check_is_valid()?;

        let transition = {
            let mut trackers = self.device.trackers.lock();
            trackers
                .buffers
                .set_single(&buffer, wgt::BufferUses::COPY_DST)
        };

        let dst_raw = buffer.try_raw(snatch_guard)?;

        self.same_device_as(buffer.as_ref())?;

        self.validate_write_buffer_impl(&buffer, buffer_offset, staging_buffer.size.into())?;

        let region = hal::BufferCopy {
            src_offset: 0,
            dst_offset: buffer_offset,
            size: staging_buffer.size,
        };
        let barriers = iter::once(hal::BufferBarrier {
            buffer: staging_buffer.raw(),
            usage: hal::StateTransition {
                from: wgt::BufferUses::MAP_WRITE,
                to: wgt::BufferUses::COPY_SRC,
            },
        })
        .chain(transition.map(|pending| pending.into_hal(&buffer, snatch_guard)))
        .collect::<Vec<_>>();
        let encoder = pending_writes.activate();
        unsafe {
            encoder.transition_buffers(&barriers);
            encoder.copy_buffer_to_buffer(staging_buffer.raw(), dst_raw, &[region]);
        }

        pending_writes.insert_buffer(&buffer);

        // Ensure the overwritten bytes are marked as initialized so
        // they don't need to be nulled prior to mapping or binding.
        {
            buffer
                .initialization_status
                .write()
                .drain(buffer_offset..(buffer_offset + staging_buffer.size.get()));
        }

        Ok(())
    }

    pub fn write_texture(
        &self,
        destination: wgt::TexelCopyTextureInfo<Arc<Texture>>,
        data: &[u8],
        data_layout: &wgt::TexelCopyBufferLayout,
        size: &wgt::Extent3d,
    ) -> Result<(), QueueWriteError> {
        profiling::scope!("Queue::write_texture");
        api_log!("Queue::write_texture");

        self.device.check_is_valid()?;

        let dst = destination.texture;
        let destination = wgt::TexelCopyTextureInfo {
            texture: (),
            mip_level: destination.mip_level,
            origin: destination.origin,
            aspect: destination.aspect,
        };

        self.same_device_as(dst.as_ref())?;

        dst.check_usage(wgt::TextureUsages::COPY_DST)
            .map_err(TransferError::MissingTextureUsage)?;

        // Note: Doing the copy range validation early is important because ensures that the
        // dimensions are not going to cause overflow in other parts of the validation.
        let (hal_copy_size, array_layer_count) =
            validate_texture_copy_range(&destination, &dst.desc, CopySide::Destination, size)?;

        let (selector, dst_base) = extract_texture_selector(&destination, size, &dst)?;

        validate_texture_copy_dst_format(dst.desc.format, destination.aspect)?;

        validate_texture_buffer_copy(
            &destination,
            dst_base.aspect,
            &dst.desc,
            data_layout,
            false// alignment not required for buffer offset or bytes per row
        )?;

        // Note: `_source_bytes_per_array_layer` is ignored since we
        // have a staging copy, and it can have a different value.
        let (required_bytes_in_copy, _source_bytes_per_array_layer, _) =
            validate_linear_texture_data(
                data_layout,
                dst.desc.format,
                destination.aspect,
                data.len() as wgt::BufferAddress,
                CopySide::Source,
                size,
            )?;

        if dst.desc.format.is_depth_stencil_format() {
            self.device
                .require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
                .map_err(TransferError::from)?;
        }

        let snatch_guard = self.device.snatchable_lock.read();

        let dst_raw = dst.try_raw(&snatch_guard)?;

        // This must happen after parameter validation (so that errors are reported
        // as required by the spec), but before any side effects.
        if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 {
            log::trace!("Ignoring write_texture of size 0");
            return Ok(());
        }

        let mut pending_writes = self.pending_writes.lock();
        let encoder = pending_writes.activate();

        // If the copy does not fully cover the layers, we need to initialize to
        // zero *first* as we don't keep track of partial texture layer inits.
        //
        // Strictly speaking we only need to clear the areas of a layer
        // untouched, but this would get increasingly messy.
        let init_layer_range = if dst.desc.dimension == wgt::TextureDimension::D3 {
            // volume textures don't have a layer range as array volumes aren't supported
            0..1
        } else {
            destination.origin.z..destination.origin.z + size.depth_or_array_layers
        };
        let mut dst_initialization_status = dst.initialization_status.write();
        if dst_initialization_status.mips[destination.mip_level as usize]
            .check(init_layer_range.clone())
            .is_some()
        {
            if has_copy_partial_init_tracker_coverage(size, &destination, &dst.desc) {
                for layer_range in dst_initialization_status.mips[destination.mip_level as usize]
                    .drain(init_layer_range)
                    .collect::<Vec<core::ops::Range<u32>>>()
                {
                    let mut trackers = self.device.trackers.lock();
                    crate::command::clear_texture(
                        &dst,
                        TextureInitRange {
                            mip_range: destination.mip_level..(destination.mip_level + 1),
                            layer_range,
                        },
                        encoder,
                        &mut trackers.textures,
                        &self.device.alignments,
                        self.device.zero_buffer.as_ref(),
                        &snatch_guard,
                        self.device.instance_flags,
                    )
                    .map_err(QueueWriteError::from)?;
                }
            } else {
                dst_initialization_status.mips[destination.mip_level as usize]
                    .drain(init_layer_range);
            }
        }

        let (block_width, block_height) = dst.desc.format.block_dimensions();
        let width_in_blocks = size.width / block_width;
        let height_in_blocks = size.height / block_height;

        let block_size = dst
            .desc
            .format
            .block_copy_size(Some(destination.aspect))
            .unwrap();
        let bytes_in_last_row = width_in_blocks * block_size;

        let bytes_per_row = data_layout.bytes_per_row.unwrap_or(bytes_in_last_row);
        let rows_per_image = data_layout.rows_per_image.unwrap_or(height_in_blocks);

        let bytes_per_row_alignment = get_lowest_common_denom(
            self.device.alignments.buffer_copy_pitch.get() as u32,
            block_size,
        );
        assert!(u32::MAX - bytes_in_last_row >= bytes_per_row_alignment);
        let stage_bytes_per_row = wgt::math::align_to(bytes_in_last_row, bytes_per_row_alignment);

        // Platform validation requires that the staging buffer always be
        // freed, even if an error occurs. All paths from here must call
        // `device.pending_writes.consume`.
        let staging_buffer = if stage_bytes_per_row == bytes_per_row {
            profiling::scope!("copy aligned");
            // Fast path if the data is already being aligned optimally.
            let stage_size = wgt::BufferSize::new(required_bytes_in_copy).unwrap();
            let mut staging_buffer = StagingBuffer::new(&self.device, stage_size)?;
            staging_buffer.write(&data[data_layout.offset as usize..]);
            staging_buffer
        } else {
            profiling::scope!("copy chunked");
            // Copy row by row into the optimal alignment.
            let block_rows_in_copy = u64::from(size.depth_or_array_layers - 1)
                * u64::from(rows_per_image)
                + u64::from(height_in_blocks);
            // The copy size was validated against the source buffer, however,
            // `stage_bytes_per_row` can differ, so let's be paranoid.
            let stage_size = u64::from(stage_bytes_per_row)
                .checked_mul(block_rows_in_copy)
                .and_then(wgt::BufferSize::new)
                .unwrap();
            let mut staging_buffer = StagingBuffer::new(&self.device, stage_size)?;
            for layer in 0..u64::from(size.depth_or_array_layers) {
                let rows_offset = layer * u64::from(rows_per_image);
                for row in rows_offset..rows_offset + u64::from(height_in_blocks) {
                    let src_offset = data_layout.offset + row * u64::from(bytes_per_row);
                    let dst_offset = row * u64::from(stage_bytes_per_row);
                    unsafe {
                        staging_buffer.write_with_offset(
                            data,
                            src_offset as isize,
                            dst_offset as isize,
                            bytes_in_last_row as usize,
                        )
                    }
                }
            }
            staging_buffer
        };

        let staging_buffer = staging_buffer.flush();

        let regions = (0..array_layer_count)
            .map(|array_layer_offset| {
                let mut texture_base = dst_base.clone();
                texture_base.array_layer += array_layer_offset;
                hal::BufferTextureCopy {
                    buffer_layout: wgt::TexelCopyBufferLayout {
                        offset: array_layer_offset as u64
                            * rows_per_image as u64
                            * stage_bytes_per_row as u64,
                        bytes_per_row: Some(stage_bytes_per_row),
                        rows_per_image: Some(rows_per_image),
                    },
                    texture_base,
                    size: hal_copy_size,
                }
            })
            .collect::<Vec<_>>();

        {
            let buffer_barrier = hal::BufferBarrier {
                buffer: staging_buffer.raw(),
                usage: hal::StateTransition {
                    from: wgt::BufferUses::MAP_WRITE,
                    to: wgt::BufferUses::COPY_SRC,
                },
            };

            let mut trackers = self.device.trackers.lock();
            let transition =
                trackers
                    .textures
                    .set_single(&dst, selector, wgt::TextureUses::COPY_DST);
            let texture_barriers = transition
                .map(|pending| pending.into_hal(dst_raw))
                .collect::<Vec<_>>();

            unsafe {
                encoder.transition_textures(&texture_barriers);
                encoder.transition_buffers(&[buffer_barrier]);
                encoder.copy_buffer_to_texture(staging_buffer.raw(), dst_raw, ®ions);
            }
        }

        pending_writes.consume(staging_buffer);
        pending_writes.insert_texture(&dst);

        Ok(())
    }

    #[cfg(webgl)]
    pub fn copy_external_image_to_texture(
        &self,
        source: &wgt::CopyExternalImageSourceInfo,
        destination: wgt::CopyExternalImageDestInfo<Fallible<Texture>>,
        size: wgt::Extent3d,
    ) -> Result<(), QueueWriteError> {
        use crate::conv;

        profiling::scope!("Queue::copy_external_image_to_texture");

        self.device.check_is_valid()?;

        let mut needs_flag = false;
        needs_flag |= matches!(source.source, wgt::ExternalImageSource::OffscreenCanvas(_));
        needs_flag |= source.origin != wgt::Origin2d::ZERO;
        needs_flag |= destination.color_space != wgt::PredefinedColorSpace::Srgb;
        #[allow(clippy::bool_comparison)]
        if matches!(source.source, wgt::ExternalImageSource::ImageBitmap(_)) {
            needs_flag |= source.flip_y != false;
            needs_flag |= destination.premultiplied_alpha != false;
        }

        if needs_flag {
            self.device
                .require_downlevel_flags(wgt::DownlevelFlags::UNRESTRICTED_EXTERNAL_TEXTURE_COPIES)
                .map_err(TransferError::from)?;
        }

        let src_width = source.source.width();
        let src_height = source.source.height();

        let dst = destination.texture.get()?;
        let premultiplied_alpha = destination.premultiplied_alpha;
        let destination = wgt::TexelCopyTextureInfo {
            texture: (),
            mip_level: destination.mip_level,
            origin: destination.origin,
            aspect: destination.aspect,
        };

        if !conv::is_valid_external_image_copy_dst_texture_format(dst.desc.format) {
            return Err(
                TransferError::ExternalCopyToForbiddenTextureFormat(dst.desc.format).into(),
            );
        }
        if dst.desc.dimension != wgt::TextureDimension::D2 {
            return Err(TransferError::InvalidDimensionExternal.into());
        }
        dst.check_usage(wgt::TextureUsages::COPY_DST | wgt::TextureUsages::RENDER_ATTACHMENT)
            .map_err(TransferError::MissingTextureUsage)?;
        if dst.desc.sample_count != 1 {
            return Err(TransferError::InvalidSampleCount {
                sample_count: dst.desc.sample_count,
            }
            .into());
        }

        if source.origin.x > src_width || src_width - source.origin.x < size.width {
            return Err(TransferError::TextureOverrun {
                start_offset: source.origin.x,
                end_offset: source.origin.x.saturating_add(size.width),
                texture_size: src_width,
                dimension: crate::resource::TextureErrorDimension::X,
                side: CopySide::Source,
            }
            .into());
        }
        if source.origin.y > src_height || src_height - source.origin.y < size.height {
            return Err(TransferError::TextureOverrun {
                start_offset: source.origin.y,
                end_offset: source.origin.y.saturating_add(size.height),
                texture_size: src_height,
                dimension: crate::resource::TextureErrorDimension::Y,
                side: CopySide::Source,
            }
            .into());
        }
        if size.depth_or_array_layers != 1 {
            return Err(TransferError::TextureOverrun {
                start_offset: 0,
                end_offset: size.depth_or_array_layers,
                texture_size: 1,
                dimension: crate::resource::TextureErrorDimension::Z,
                side: CopySide::Source,
            }
            .into());
        }

        // Note: Doing the copy range validation early is important because ensures that the
        // dimensions are not going to cause overflow in other parts of the validation.
        let (hal_copy_size, _) =
            validate_texture_copy_range(&destination, &dst.desc, CopySide::Destination, &size)?;

        let (selector, dst_base) = extract_texture_selector(&destination, &size, &dst)?;

        // This must happen after parameter validation (so that errors are reported
        // as required by the spec), but before any side effects.
        if size.width == 0 || size.height == 0 || size.depth_or_array_layers == 0 {
            log::trace!("Ignoring copy_external_image_to_texture of size 0");
            return Ok(());
        }

        let mut pending_writes = self.pending_writes.lock();
        let encoder = pending_writes.activate();

        // If the copy does not fully cover the layers, we need to initialize to
        // zero *first* as we don't keep track of partial texture layer inits.
        //
        // Strictly speaking we only need to clear the areas of a layer
        // untouched, but this would get increasingly messy.
        let init_layer_range = if dst.desc.dimension == wgt::TextureDimension::D3 {
            // volume textures don't have a layer range as array volumes aren't supported
            0..1
        } else {
            destination.origin.z..destination.origin.z + size.depth_or_array_layers
        };
        let mut dst_initialization_status = dst.initialization_status.write();
        if dst_initialization_status.mips[destination.mip_level as usize]
            .check(init_layer_range.clone())
            .is_some()
        {
            if has_copy_partial_init_tracker_coverage(&size, &destination, &dst.desc) {
                for layer_range in dst_initialization_status.mips[destination.mip_level as usize]
                    .drain(init_layer_range)
                    .collect::<Vec<core::ops::Range<u32>>>()
                {
                    let mut trackers = self.device.trackers.lock();
                    crate::command::clear_texture(
                        &dst,
                        TextureInitRange {
                            mip_range: destination.mip_level..(destination.mip_level + 1),
                            layer_range,
                        },
                        encoder,
                        &mut trackers.textures,
                        &self.device.alignments,
                        self.device.zero_buffer.as_ref(),
                        &self.device.snatchable_lock.read(),
                        self.device.instance_flags,
                    )
                    .map_err(QueueWriteError::from)?;
                }
            } else {
                dst_initialization_status.mips[destination.mip_level as usize]
                    .drain(init_layer_range);
            }
        }

        let snatch_guard = self.device.snatchable_lock.read();
        let dst_raw = dst.try_raw(&snatch_guard)?;

        let regions = hal::TextureCopy {
            src_base: hal::TextureCopyBase {
                mip_level: 0,
                array_layer: 0,
                origin: source.origin.to_3d(0),
                aspect: hal::FormatAspects::COLOR,
            },
            dst_base,
            size: hal_copy_size,
        };

        let mut trackers = self.device.trackers.lock();
        let transitions = trackers
            .textures
            .set_single(&dst, selector, wgt::TextureUses::COPY_DST);

        // `copy_external_image_to_texture` is exclusive to the WebGL backend.
        // Don't go through the `DynCommandEncoder` abstraction and directly to the WebGL backend.
        let encoder_webgl = encoder
            .as_any_mut()
            .downcast_mut::<hal::gles::CommandEncoder>()
            .unwrap();
        let dst_raw_webgl = dst_raw
            .as_any()
            .downcast_ref::<hal::gles::Texture>()
            .unwrap();
        let transitions_webgl = transitions.map(|pending| {
            let dyn_transition = pending.into_hal(dst_raw);
            hal::TextureBarrier {
                texture: dst_raw_webgl,
                range: dyn_transition.range,
                usage: dyn_transition.usage,
            }
        });

        use hal::CommandEncoder as _;
        unsafe {
            encoder_webgl.transition_textures(transitions_webgl);
            encoder_webgl.copy_external_image_to_texture(
                source,
                dst_raw_webgl,
                premultiplied_alpha,
                iter::once(regions),
            );
        }

        Ok(())
    }

    /// Flush `PendingWrites` if it contains a write to `buffer`.
    pub fn flush_writes_for_buffer(
        &self,
        buffer: &Arc<Buffer>,
        snatch_guard: SnatchGuard,
    ) -> Result<(), BufferAccessError> {
        let submission = self
            .allocate_submission(snatch_guard)
            .map_err(|(_index, e)| e)?;

        let pending_writes = self.pending_writes.lock();
        if !pending_writes.contains_buffer(buffer) {
            return Ok(());
        }

        submission.submit(pending_writes)?;

        Ok(())
    }

    fn flush_pending_writes(&self) -> Result<Option<SubmissionIndex>, DeviceError> {
        let snatch_guard = self.device.snatchable_lock.read();
        let submission = self
            .allocate_submission(snatch_guard)
            .map_err(|(_index, e)| e)?;
        let submit_index = submission.index;
        let pending_writes = self.pending_writes.lock();
        if pending_writes.is_recording {
            submission.submit(pending_writes)?;
            Ok(Some(submit_index))
        } else {
            Ok(None)
        }
    }

    #[cfg(feature = "trace")]
    fn trace_submission(
        &self,
        submit_index: SubmissionIndex,
        commands: Vec<crate::command::Command<crate::command::PointerReferences>>,
    ) {
        if let Some(ref mut trace) = *self.device.trace.lock() {
            trace.add(Action::Submit(submit_index, commands));
        }
    }

    #[cfg(feature = "trace")]
    fn trace_failed_submission(
        &self,
        submit_index: SubmissionIndex,
        commands: Option<Vec<crate::command::Command<crate::command::PointerReferences>>>,
        error: alloc::string::String,
    ) {
        if let Some(ref mut trace) = *self.device.trace.lock() {
            trace.add(Action::FailedCommands {
                commands,
                failed_at_submit: Some(submit_index),
                error,
            });
        }
    }

    pub fn submit(
        &self,
        command_buffers: &[Arc<CommandBuffer>],
    ) -> Result<SubmissionIndex, (SubmissionIndex, QueueSubmitError)> {
        profiling::scope!("Queue::submit");
        api_log!("Queue::submit");

        let snatch_guard = self.device.snatchable_lock.read();
        let mut submission = self
            .allocate_submission(snatch_guard)
            .map_err(|(index, e)| (index, e.into()))?;
        let submit_index = submission.index;

        let res = 'error: {
            let mut used_surface_textures = track::TextureUsageScope::default();

            {
                if !command_buffers.is_empty() {
                    profiling::scope!("prepare");

                    let mut first_error = None;

                    //TODO: if multiple command buffers are submitted, we can re-use the last
                    // native command buffer of the previous chain instead of always creating
                    // a temporary one, since the chains are not finished.

                    // finish all the command buffers first
                    for command_buffer in command_buffers {
                        profiling::scope!("process command buffer");

                        // we reset the used surface textures every time we use
                        // it, so make sure to set_size on it.
                        used_surface_textures.set_size(self.device.tracker_indices.textures.size());

                        // Note that we are required to invalidate all command buffers in both the success and failure paths.
                        // This is why we `continue` and don't early return via `?`.
                        #[allow(unused_mut)]
                        let mut cmd_buf_data = command_buffer.take_finished();

                        if first_error.is_some() {
                            continue;
                        }

                        #[cfg(feature = "trace")]
                        let trace_commands = cmd_buf_data
                            .as_mut()
                            .ok()
                            .and_then(|data| mem::take(&mut data.trace_commands));

                        let mut baked = match cmd_buf_data {
                            Ok(cmd_buf_data) => {
                                let res = validate_command_buffer(
                                    command_buffer,
                                    self,
                                    &cmd_buf_data,
                                    &submission.snatch_guard,
                                    &mut submission.surface_textures,
                                    &mut used_surface_textures,
                                    &mut submission.command_index_guard,
                                );
                                if let Err(err) = res {
                                    #[cfg(feature = "trace")]
                                    self.trace_failed_submission(
                                        submit_index,
                                        trace_commands,
                                        err.to_string(),
                                    );
                                    first_error.get_or_insert(err);
                                    continue;
                                }

                                #[cfg(feature = "trace")]
                                if let Some(commands) = trace_commands {
                                    self.trace_submission(submit_index, commands);
                                }

                                cmd_buf_data.set_acceleration_structure_dependencies(
                                    &submission.snatch_guard,
                                );
                                cmd_buf_data.into_baked_commands()
                            }
                            Err(err) => {
                                #[cfg(feature = "trace")]
                                self.trace_failed_submission(
                                    submit_index,
                                    trace_commands,
                                    err.to_string(),
                                );
                                first_error.get_or_insert(err.into());
                                continue;
                            }
                        };

                        if let Err(e) = baked.process_deferred_query_set_resolves(
                            &self.device,
                            &submission.snatch_guard,
                        ) {
                            break 'error Err(e.into());
                        }

                        // execute resource transitions
                        if let Err(e) = baked.encoder.open_pass(hal_label(
                            Some("(wgpu internal) Transit"),
                            self.device.instance_flags,
                        )) {
                            break 'error Err(e.into());
                        }

                        //Note: locking the trackers has to be done after the storages
                        let mut trackers = self.device.trackers.lock();
                        if let Err(e) =
                            baked.initialize_buffer_memory(&mut trackers, &submission.snatch_guard)
                        {
                            break 'error Err(e.into());
                        }
                        if let Err(e) = baked.initialize_texture_memory(
                            &mut trackers,
                            &self.device,
                            &submission.snatch_guard,
                        ) {
                            break 'error Err(e.into());
                        }

                        //Note: stateless trackers are not merged:
                        // device already knows these resources exist.
                        CommandEncoder::insert_barriers_from_device_tracker(
                            baked.encoder.raw.as_mut(),
                            &mut trackers,
                            &baked.trackers,
                            &submission.snatch_guard,
                        );

                        if let Err(e) = baked.encoder.close_and_push_front() {
                            break 'error Err(e.into());
                        }

                        // Transition surface textures into `Present` state.
                        // Note: we could technically do it after all of the command buffers,
                        // but here we have a command encoder by hand, so it's easier to use it.
                        if !used_surface_textures.is_empty() {
                            if let Err(e) = baked.encoder.open_pass(hal_label(
                                Some("(wgpu internal) Present"),
                                self.device.instance_flags,
                            )) {
                                break 'error Err(e.into());
                            }
                            let texture_barriers = trackers
                                .textures
                                .set_from_usage_scope_and_drain_transitions(
                                    &used_surface_textures,
                                    &submission.snatch_guard,
                                )
                                .collect::<Vec<_>>();
                            unsafe {
                                baked.encoder.raw.transition_textures(&texture_barriers);
                            };
                            if let Err(e) = baked.encoder.close() {
                                break 'error Err(e.into());
                            }
                            used_surface_textures = track::TextureUsageScope::default();
                        }

                        // done
                        submission.executions.push(EncoderInFlight {
                            inner: baked.encoder,
                            trackers: baked.trackers,
                            temp_resources: baked.temp_resources,
                            _indirect_draw_validation_resources: baked
                                .indirect_draw_validation_resources,
                            pending_buffers: FastHashMap::default(),
                            pending_textures: FastHashMap::default(),
                            pending_blas_s: FastHashMap::default(),
                        });
                    }

                    if let Some(first_error) = first_error {
                        break 'error Err(first_error);
                    }
                }
            }

            let pending_writes = self.pending_writes.lock();

            let SubmissionResult { snatch_guard } = match submission.submit(pending_writes) {
                Ok(result) => result,
                Err(e) => break 'error Err(e.into()),
            };

            profiling::scope!("cleanup");

            // This will schedule destruction of all resources that are no longer needed
            // by the user but used in the command stream, among other things.
            // `device.maintain` consumes and will release the snatch guard.
            let (closures, result) = self.device.maintain(wgt::PollType::Poll, snatch_guard);
            match result {
                Ok(status) => {
                    debug_assert!(matches!(
                        status,
                        wgt::PollStatus::QueueEmpty | wgt::PollStatus::Poll
                    ));
                }
                Err(WaitIdleError::Device(err)) => break 'error Err(QueueSubmitError::Queue(err)),
                Err(WaitIdleError::WrongSubmissionIndex(..)) => {
                    unreachable!("Cannot get WrongSubmissionIndex from Poll")
                }
                Err(WaitIdleError::Timeout) => unreachable!("Cannot get Timeout from Poll"),
            };

            Ok(closures)
        };

        let callbacks = match res {
            Ok(ok) => ok,
            Err(e) => return Err((submit_index, e)),
        };

        // the closures should execute with nothing locked!
        callbacks.fire();

        self.device.lose_if_oom();

        api_log!("Queue::submit returned submit index {submit_index}");

        Ok(submit_index)
    }

    /// Allocate a submission index and prepare for a submission.
    ///
    /// This is an internal API used in [`Queue::submit`] and other places within
    /// `wgpu-core` that need to submit work.
    ///
    /// Returns the index and a [`PendingSubmission`].
    ///
    /// The caller passes in the already-acquired [`SnatchGuard`]. This function acquires
    /// the fence lock and the command index lock.
    ///
    /// The caller should update [`PendingSubmission::executions`] with details of the
    /// submission.
    ///
    /// To finalize and submit the submission, call [`PendingSubmission::submit`] (which is
    /// a convenience wrapper around [`Queue::submit_pending_submission`]).
    ///
    /// After calling this function and before submitting, the caller must acquire the
    /// pending writes lock, and pass it to `submit`.
    ///
    /// It is also acceptable to drop the `PendingSubmission` without submitting. This may
    /// be necessary when locks are required to access the state that determines whether a
    /// submission is needed.
    fn allocate_submission<'a>(
        &'a self,
        snatch_guard: SnatchGuard<'a>,
    ) -> Result<PendingSubmission<'a>, (SubmissionIndex, DeviceError)> {
        let mut command_index_guard = self.device.command_indices.write();
        command_index_guard.active_submission_index += 1;
        let index = command_index_guard.active_submission_index;

        if let Err(e) = self.device.check_is_valid() {
            return Err((index, e));
        }

        let submission = PendingSubmission {
            queue: self,
            snatch_guard,
            command_index_guard,
            executions: Vec::new(),
            surface_textures: FastHashMap::default(),
            index,
        };

        Ok(submission)
    }

    /// Finalize and submit a [`PendingSubmission`] that was returned by
    /// [`Queue::allocate_submission`].
    ///
    /// This is an internal API used in `Queue::submit` and other places within
    /// `wgpu-core` that need to submit work. See [`Queue::allocate_submission`]
    /// for more details.
    ///
    /// This function:
    ///
    /// - Performs a HAL submission of the pending writes command
    ///   encoder and any other command encoders that were added to the
    ///   [`PendingSubmission`].
    /// - Advances `last_successful_submission_index` and registers the
    ///   submission with the lifetime tracker.
    /// - Returns a [`SubmissionResult`], which contains the snatch guard.
    fn submit_pending_submission<'a>(
        &self,
        mut pending_writes: MutexGuard<'_, PendingWrites>,
        prepared: PendingSubmission<'a>,
    ) -> Result<SubmissionResult<'a>, DeviceError> {
        let PendingSubmission {
            queue: _,
            snatch_guard,
            command_index_guard,
            mut executions,
            mut surface_textures,
            index: submit_index,
        } = prepared;

        let mut used_surface_textures = track::TextureUsageScope::default();
        used_surface_textures.set_size(self.device.tracker_indices.textures.size());
        for texture in pending_writes.dst_textures.values() {
            match texture.try_inner(&snatch_guard) {
                Ok(TextureInner::Native { .. }) => {}
                Ok(TextureInner::Surface { .. }) => {
                    // Compare the Arcs by pointer as Textures don't implement Eq
                    surface_textures.insert(Arc::as_ptr(texture), texture.clone());

                    unsafe {
                        used_surface_textures
                            .merge_single(texture, None, wgt::TextureUses::PRESENT)
                            .unwrap()
                    };
                }
                // The texture must not have been destroyed when its usage here was
                // encoded. If it was destroyed after that, then it was transferred
                // to `pending_writes.temp_resources` at the time of destruction, so
                // we are still okay to use it.
                Err(DestroyedResourceError(_)) => {}
            }
        }

        if !used_surface_textures.is_empty() {
            let mut trackers = self.device.trackers.lock();

            let texture_barriers = trackers
                .textures
                .set_from_usage_scope_and_drain_transitions(&used_surface_textures, &snatch_guard)
                .collect::<Vec<_>>();
            unsafe {
                pending_writes
                    .command_encoder
                    .transition_textures(&texture_barriers);
            };
        }

        match pending_writes.pre_submit(&self.device.command_allocator, &self.device, self) {
            Ok(Some(pending_execution)) => {
                executions.insert(0, pending_execution);
            }
            Ok(None) => {}
            Err(e) => return Err(e),
        }
        let hal_command_buffers = executions
            .iter()
            .flat_map(|e| e.inner.list.iter().map(|b| b.as_ref()))
            .collect::<Vec<_>>();

        {
            let mut submit_surface_textures =
                SmallVec::<[&dyn hal::DynSurfaceTexture; 2]>::with_capacity(surface_textures.len());
            for texture in surface_textures.values() {
                let raw = match texture.inner.get(&snatch_guard) {
                    Some(TextureInner::Surface { raw, .. }) => raw.as_ref(),
                    _ => unreachable!(),
                };
                submit_surface_textures.push(raw);
            }

            unsafe {
                self.raw().submit(
                    &hal_command_buffers,
                    &submit_surface_textures,
                    (self.device.fence.as_ref(), submit_index),
                )
            }
            .map_err(|e| self.device.handle_hal_error(e))?;

            // Submissions must have strictly increasing indices, so we must hold the
            // command index guard until we have submitted, to prevent another submission
            // from claiming the next index and reaching `submit` before we do.
            drop(pending_writes);

            // Advance the successful submission index.
            self.device
                .last_successful_submission_index
                .fetch_max(submit_index, Ordering::SeqCst);
        }

        // this will register the new submission to the life time tracker
        self.lock_life().track_submission(submit_index, executions);

        // `device.maintain` relies on being able to prevent new submissions by
        // using `command_index_guard` while also checking whether there are
        // no tracked submissions to guarantee no new submissions will happen
        // after a device is lost. This requires `command_index_guard` to be
        // held over `self.lock_life()`
        drop(command_index_guard);

        Ok(SubmissionResult { snatch_guard })
    }

    pub fn get_timestamp_period(&self) -> f32 {
        unsafe { self.raw().get_timestamp_period() }
    }

    /// `closure` is guaranteed to be called.
    pub fn on_submitted_work_done(
        &self,
        closure: SubmittedWorkDoneClosure,
    ) -> Option<SubmissionIndex> {
        api_log!("Queue::on_submitted_work_done");

        // A `DeviceError` means we're losing the device anyways, so we can ignore it here
        // (mostly to avoid a breaking change to the `on_submitted_work_done` signature
        // for an error case that it is unlikely the caller will be able to handle).
        let _: Result<_, DeviceError> = self.flush_pending_writes();

        self.lock_life().add_work_done_closure(closure)
    }

    pub fn compact_blas(&self, blas: &Arc<Blas>) -> Result<Arc<Blas>, CompactBlasError> {
        profiling::scope!("Queue::compact_blas");
        api_log!("Queue::compact_blas");

        let new_label = blas.label.clone() + " (compacted)";

        self.device.check_is_valid()?;
        self.same_device_as(blas.as_ref())?;

        let device = blas.device.clone();

        let snatch_guard = device.snatchable_lock.read();

        let BlasCompactState::Ready { size } = *blas.compacted_state.lock() else {
            return Err(CompactBlasError::BlasNotReady);
        };

        let mut size_info = blas.size_info;
        size_info.acceleration_structure_size = size;

        let mut pending_writes = self.pending_writes.lock();
        let cmd_buf_raw = pending_writes.activate();

        let raw = unsafe {
            device
                .raw()
                .create_acceleration_structure(&hal::AccelerationStructureDescriptor {
                    label: hal_label(Some(&new_label), device.instance_flags),
                    size: size_info.acceleration_structure_size,
                    format: hal::AccelerationStructureFormat::BottomLevel,
                    allow_compaction: false,
                })
        }
        .map_err(DeviceError::from_hal)?;

        let src_raw = blas.try_raw(&snatch_guard)?;

        unsafe {
            cmd_buf_raw.copy_acceleration_structure_to_acceleration_structure(
                src_raw,
                raw.as_ref(),
                wgt::AccelerationStructureCopy::Compact,
            )
        };

        let handle = unsafe {
            device
                .raw()
                .get_acceleration_structure_device_address(raw.as_ref())
        };

        drop(snatch_guard);

        let mut command_indices_lock = device.command_indices.write();
        command_indices_lock.next_acceleration_structure_build_command_index += 1;
        let built_index =
            NonZeroU64::new(command_indices_lock.next_acceleration_structure_build_command_index)
                .unwrap();

        let new_blas = Arc::new(Blas {
            raw: Snatchable::new(raw),
            device: device.clone(),
            size_info,
            sizes: blas.sizes.clone(),
            flags: blas.flags & !AccelerationStructureFlags::ALLOW_COMPACTION,
            update_mode: blas.update_mode,
            // Bypass the submit checks which update this because we don't submit this normally.
            built_index: RwLock::new(rank::BLAS_BUILT_INDEX, Some(built_index)),
            handle,
            label: new_label,
            tracking_data: TrackingData::new(blas.device.tracker_indices.blas_s.clone()),
            compaction_buffer: None,
            compacted_state: Mutex::new(rank::BLAS_COMPACTION_STATE, BlasCompactState::Compacted),
        });

        pending_writes.insert_blas(blas);
        pending_writes.insert_blas(&new_blas);

        Ok(new_blas)
    }
}

impl Global {
    pub fn queue_write_buffer(
        &self,
        queue_id: QueueId,
        buffer_id: id::BufferId,
        buffer_offset: wgt::BufferAddress,
        data: &[u8],
    ) -> Result<(), QueueWriteError> {
        let queue = self.hub.queues.get(queue_id);
        let buffer = self.hub.buffers.get(buffer_id).get()?;

        #[cfg(feature = "trace")]
        if let Some(ref mut trace) = *queue.device.trace.lock() {
            use crate::device::trace::DataKind;
            let size = data.len() as u64;
            let data = trace.make_binary(DataKind::Bin, data);
            trace.add(Action::WriteBuffer {
                id: buffer.to_trace(),
                data,
                offset: buffer_offset,
                size,
                queued: true,
            });
        }

        queue.write_buffer(buffer, buffer_offset, data)
    }

    pub fn queue_create_staging_buffer(
        &self,
        queue_id: QueueId,
        buffer_size: wgt::BufferSize,
        id_in: Option<id::StagingBufferId>,
    ) -> Result<(id::StagingBufferId, NonNull<u8>), QueueWriteError> {
        let queue = self.hub.queues.get(queue_id);
        let (staging_buffer, ptr) = queue.create_staging_buffer(buffer_size)?;

        let fid = self.hub.staging_buffers.prepare(id_in);
        let id = fid.assign(staging_buffer);

        Ok((id, ptr))
    }

    pub fn queue_write_staging_buffer(
        &self,
        queue_id: QueueId,
        buffer_id: id::BufferId,
        buffer_offset: wgt::BufferAddress,
        staging_buffer_id: id::StagingBufferId,
    ) -> Result<(), QueueWriteError> {
        let queue = self.hub.queues.get(queue_id);
        let buffer = self.hub.buffers.get(buffer_id);
        let staging_buffer = self.hub.staging_buffers.remove(staging_buffer_id);
        queue.write_staging_buffer(buffer, buffer_offset, staging_buffer)
    }

    pub fn queue_validate_write_buffer(
        &self,
        queue_id: QueueId,
        buffer_id: id::BufferId,
        buffer_offset: u64,
        buffer_size: wgt::BufferSize,
    ) -> Result<(), QueueWriteError> {
        let queue = self.hub.queues.get(queue_id);
        let buffer = self.hub.buffers.get(buffer_id);
        queue.validate_write_buffer(buffer, buffer_offset, buffer_size)
    }

    pub fn queue_write_texture(
        &self,
        queue_id: QueueId,
        destination: &wgt::TexelCopyTextureInfo<id::TextureId>,
        data: &[u8],
        data_layout: &wgt::TexelCopyBufferLayout,
        size: &wgt::Extent3d,
    ) -> Result<(), QueueWriteError> {
        let queue = self.hub.queues.get(queue_id);
        let texture = self.hub.textures.get(destination.texture).get()?;
        let destination = wgt::TexelCopyTextureInfo {
            texture,
            mip_level: destination.mip_level,
            origin: destination.origin,
            aspect: destination.aspect,
        };

        #[cfg(feature = "trace")]
        if let Some(ref mut trace) = *queue.device.trace.lock() {
            use crate::device::trace::DataKind;
            let data = trace.make_binary(DataKind::Bin, data);
            trace.add(Action::WriteTexture {
                to: destination.to_trace(),
                data,
                layout: *data_layout,
                size: *size,
            });
        }

        queue.write_texture(destination, data, data_layout, size)
    }

    #[cfg(webgl)]
    pub fn queue_copy_external_image_to_texture(
        &self,
        queue_id: QueueId,
        source: &wgt::CopyExternalImageSourceInfo,
        destination: crate::command::CopyExternalImageDestInfo,
        size: wgt::Extent3d,
    ) -> Result<(), QueueWriteError> {
        let queue = self.hub.queues.get(queue_id);
        let destination = wgt::CopyExternalImageDestInfo {
            texture: self.hub.textures.get(destination.texture),
            mip_level: destination.mip_level,
            origin: destination.origin,
            aspect: destination.aspect,
            color_space: destination.color_space,
            premultiplied_alpha: destination.premultiplied_alpha,
        };
        queue.copy_external_image_to_texture(source, destination, size)
    }

    pub fn queue_submit(
        &self,
        queue_id: QueueId,
        command_buffer_ids: &[id::CommandBufferId],
    ) -> Result<SubmissionIndex, (SubmissionIndex, QueueSubmitError)> {
        let queue = self.hub.queues.get(queue_id);
        let command_buffer_guard = self.hub.command_buffers.read();
        let command_buffers = command_buffer_ids
            .iter()
            .map(|id| command_buffer_guard.get(*id))
            .collect::<Vec<_>>();
        drop(command_buffer_guard);
        queue.submit(&command_buffers)
    }

    pub fn queue_get_timestamp_period(&self, queue_id: QueueId) -> f32 {
        let queue = self.hub.queues.get(queue_id);

        if queue.device.timestamp_normalizer.get().unwrap().enabled() {
            return 1.0;
        }

        queue.get_timestamp_period()
    }

    pub fn queue_on_submitted_work_done(
        &self,
        queue_id: QueueId,
        closure: SubmittedWorkDoneClosure,
    ) -> SubmissionIndex {
        api_log!("Queue::on_submitted_work_done {queue_id:?}");

        let queue = self.hub.queues.get(queue_id);
        let result = queue.on_submitted_work_done(closure);
        result.unwrap_or(0// '0' means no wait is necessary
    }

    pub fn queue_compact_blas(
        &self,
        queue_id: QueueId,
        blas_id: BlasId,
        id_in: Option<BlasId>,
    ) -> (BlasId, Option<u64>, Option<CompactBlasError>) {
        api_log!("Queue::compact_blas {queue_id:?}, {blas_id:?}");

        let fid = self.hub.blas_s.prepare(id_in);

        let queue = self.hub.queues.get(queue_id);
        let blas = self.hub.blas_s.get(blas_id);
        let device = &queue.device;

        // TODO: Tracing

        let error = 'error: {
            match device.require_features(wgpu_types::Features::EXPERIMENTAL_RAY_QUERY) {
                Ok(_) => {}
                Err(err) => break 'error err.into(),
            }

            let blas = match blas.get() {
                Ok(blas) => blas,
                Err(err) => break 'error err.into(),
            };

            let new_blas = match queue.compact_blas(&blas) {
                Ok(blas) => blas,
                Err(err) => break 'error err,
            };

            // We should have no more errors after this because we have marked the command encoder as successful.
            let old_blas_size = blas.size_info.acceleration_structure_size;
            let new_blas_size = new_blas.size_info.acceleration_structure_size;
            let handle = new_blas.handle;

            let id = fid.assign(Fallible::Valid(new_blas));

            api_log!("CommandEncoder::compact_blas {blas_id:?} (size: {old_blas_size}) -> {id:?} (size: {new_blas_size})");

            return (id, Some(handle), None);
        };

        let id = fid.assign(Fallible::Invalid(Arc::new(error.to_string())));

        (id, None, Some(error))
    }
}

fn validate_command_buffer(
    command_buffer: &CommandBuffer,
    queue: &Queue,
    cmd_buf_data: &crate::command::CommandBufferMutable,
    snatch_guard: &SnatchGuard,
    surface_textures: &mut FastHashMap<*const Texture, Arc<Texture>>,
    used_surface_textures: &mut track::TextureUsageScope,
    command_index_guard: &mut RwLockWriteGuard<CommandIndices>,
) -> Result<(), QueueSubmitError> {
    command_buffer.same_device_as(queue)?;

    {
        profiling::scope!("check resource state");

        {
            profiling::scope!("buffers");
            for buffer in cmd_buf_data.trackers.buffers.used_resources() {
                buffer.check_destroyed(snatch_guard)?;

                match *buffer.map_state.lock() {
                    BufferMapState::Idle => (),
                    _ => return Err(QueueSubmitError::BufferStillMapped(buffer.error_ident())),
                }
            }
        }
        {
            profiling::scope!("textures");
            for texture in cmd_buf_data.trackers.textures.used_resources() {
                let should_extend = match texture.try_inner(snatch_guard)? {
                    TextureInner::Native { .. } => false,
                    TextureInner::Surface { .. } => {
                        // Compare the Arcs by pointer as Textures don't implement Eq.
                        surface_textures.insert(Arc::as_ptr(texture), texture.clone());

                        true
                    }
                };
                if should_extend {
                    unsafe {
                        used_surface_textures
                            .merge_single(texture, None, wgt::TextureUses::PRESENT)
                            .unwrap();
                    };
                }
            }
        }
        // WebGPU requires that we check every bind group referenced during
        // encoding, even ones that may have been replaced before being used.
        // TODO(<https://github.com/gfx-rs/wgpu/issues/8510>): Optimize this.
        {
            profiling::scope!("bind groups");
            for bind_group in &cmd_buf_data.trackers.bind_groups {
                // This checks the bind group and all resources it references.
                bind_group.try_raw(snatch_guard)?;
            }
        }

        if let Err(e) =
            cmd_buf_data.validate_acceleration_structure_actions(snatch_guard, command_index_guard)
        {
            return Err(e.into());
        }
    }
    Ok(())
}

Messung V0.5 in Prozent
C=90 H=91 G=90

¤ Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.0.58Bemerkung:  (vorverarbeitet am  2026-08-27) ¤

*Bot Zugriff






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.