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,
};
pubstruct 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>,
}
/// 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(crate) fn 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())
};
if needs_clear { // After encoding the clear operation, we must not return without // adding the texture to `pending_writes`. let encoder = pending_writes.activate(); letmut 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 = { letmut 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(crate) fn maintain(
&self,
submission_index: u64,
snatch_guard: &SnatchGuard,
) -> (
SmallVec<[SubmittedWorkDoneClosure; 1]>,
Vec<super::BufferMapPendingClosure>,
Vec<BlasCompactReadyPendingClosure>,
bool,
) { letmut 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();
/// 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)] pubenum 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(crate) struct 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(crate) struct 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,
/// 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(crate) struct 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,
}
let data_size = iflet 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`. letmut 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(); letmut pending_writes = self.pending_writes.lock();
let result = self.write_staging_buffer_impl(
&snatch_guard,
&mut pending_writes,
&staging_buffer,
buffer,
buffer_offset,
);
// 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(); letmut pending_writes = self.pending_writes.lock();
let result = self.write_staging_buffer_impl(
&snatch_guard,
&mut pending_writes,
&staging_buffer,
buffer,
buffer_offset,
);
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()));
}
// 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_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(());
}
letmut 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
}; letmut 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>>>()
{ letmut 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(); letmut 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(); letmut staging_buffer = StagingBuffer::new(&self.device, stage_size)?; for layer in0..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| { letmut 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,
},
};
letmut 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<_>>();
// 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(());
}
letmut 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
}; letmut 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>>>()
{ letmut 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)?;
// `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`. pubfn 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)
}
}
let snatch_guard = self.device.snatchable_lock.read(); letmut submission = self
.allocate_submission(snatch_guard)
.map_err(|(index, e)| (index, e.into()))?; let submit_index = submission.index;
let res = 'error: { letmut used_surface_textures = track::TextureUsageScope::default();
{ if !command_buffers.is_empty() {
profiling::scope!("prepare");
letmut 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)] letmut cmd_buf_data = command_buffer.take_finished();
// 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() { iflet 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);
}; iflet Err(e) = baked.encoder.close() { break'error Err(e.into());
}
used_surface_textures = track::TextureUsageScope::default();
}
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)> { letmut command_index_guard = self.device.command_indices.write();
command_index_guard.active_submission_index += 1; let index = command_index_guard.active_submission_index;
/// 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;
letmut 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() { letmut trackers = self.device.trackers.lock();
// 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);
// 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);
/// `closure` is guaranteed to be called. pubfn 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();
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;
{
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)?;
}
}
¤ 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)
¤
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.