usecrate::scratch::ScratchBuffer; use std::{
iter,
mem::{self, ManuallyDrop},
ptr::NonNull,
sync::{atomic::Ordering, Arc},
}; use thiserror::Error;
usesuper::{life::LifetimeTracker, Device};
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>,
}
impl Drop for Queue { fn drop(&mutself) {
resource_log!("Drop {}", self.error_ident());
let last_successful_submission_index = self
.device
.last_successful_submission_index
.load(Ordering::Acquire);
let fence = self.device.fence.read();
// Try waiting on the last submission using the following sequence of timeouts let timeouts_in_ms = [100, 200, 400, 800, 1600, 3200];
for (i, timeout_ms) in timeouts_in_ms.into_iter().enumerate() { let is_last_iter = i == timeouts_in_ms.len() - 1;
api_log!( "Waiting on last submission. try: {}/{}. timeout: {}ms",
i + 1,
timeouts_in_ms.len(),
timeout_ms
);
let wait_res = unsafe { self.device.raw().wait(
fence.as_ref(),
last_successful_submission_index, #[cfg(not(target_arch = "wasm32"))]
timeout_ms, #[cfg(target_arch = "wasm32")] 0, // WebKit and Chromium don't support a non-0 timeout
)
}; // Note: If we don't panic below we are in UB land (destroying resources while they are still in use by the GPU). match wait_res {
Ok(true) => break,
Ok(false) => { // It's fine that we timed out on WebGL; GL objects can be deleted early as they // will be kept around by the driver if GPU work hasn't finished. // Moreover, the way we emulate read mappings on WebGL allows us to execute map_buffer earlier than on other // backends since getBufferSubData is synchronous with respect to the other previously enqueued GL commands. // Relying on this behavior breaks the clean abstraction wgpu-hal tries to maintain and // we should find ways to improve this. See https://github.com/gfx-rs/wgpu/issues/6538. #[cfg(target_arch = "wasm32")]
{ break;
} #[cfg(not(target_arch = "wasm32"))]
{ if is_last_iter {
panic!( "We timed out while waiting on the last successful submission to complete!"
);
}
}
}
Err(e) => match e {
hal::DeviceError::OutOfMemory => { if is_last_iter {
panic!( "We ran into an OOM error while waiting on the last successful submission to complete!"
);
}
}
hal::DeviceError::Lost => { self.device.handle_hal_error(e); // will lose the device break;
}
hal::DeviceError::ResourceCreationFailed => unreachable!(),
hal::DeviceError::Unexpected => {
panic!( "We ran into an unexpected error while waiting on the last successful submission to complete!"
);
}
},
}
}
drop(fence);
let snatch_guard = self.device.snatchable_lock.read(); let (submission_closures, mapping_closures, queue_empty) = self.maintain(last_successful_submission_index, &snatch_guard);
drop(snatch_guard);
/// 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::CommandEncoder, pub(crate) trackers: Tracker, pub(crate) temp_resources: Vec<TempResource>,
/// 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>>,
}
/// 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 the next /// time the user submits a wgpu command buffer, ahead of the user's /// commands. /// /// 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,
let data_size = iflet Some(data_size) = wgt::BufferSize::new(data_size) {
data_size
} else {
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)?; letmut pending_writes = self.pending_writes.lock();
let staging_buffer = {
profiling::scope!("copy");
staging_buffer.write(data);
staging_buffer.flush()
};
let result = self.write_staging_buffer_impl(
&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 result = self.write_staging_buffer_impl(
&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: hal::BufferUses::MAP_WRITE,
to: hal::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)?;
if !dst_base.aspect.is_one() { return Err(TransferError::CopyAspectNotOne.into());
}
// 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, false,
)?;
if dst.desc.format.is_depth_stencil_format() { self.device
.require_downlevel_flags(wgt::DownlevelFlags::DEPTH_TEXTURE_AND_BUFFER_COPIES)
.map_err(TransferError::from)?;
}
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.mip_level, &dst.desc) { for layer_range in dst_initialization_status.mips[destination.mip_level as usize]
.drain(init_layer_range)
.collect::<Vec<std::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(),
)
.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 (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,
); 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 =
(size.depth_or_array_layers - 1) * rows_per_image + height_in_blocks; let stage_size =
wgt::BufferSize::new(stage_bytes_per_row as u64 * block_rows_in_copy as u64)
.unwrap(); letmut staging_buffer = StagingBuffer::new(&self.device, stage_size)?; let copy_bytes_per_row = stage_bytes_per_row.min(bytes_per_row) as usize; for layer in0..size.depth_or_array_layers { let rows_offset = layer * rows_per_image; for row in rows_offset..rows_offset + height_in_blocks { let src_offset = data_layout.offset as u32 + row * bytes_per_row; let dst_offset = row * stage_bytes_per_row; unsafe {
staging_buffer.write_with_offset(
data,
src_offset as isize,
dst_offset as isize,
copy_bytes_per_row,
)
}
}
}
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: hal::BufferUses::MAP_WRITE,
to: hal::BufferUses::COPY_SRC,
},
};
letmut trackers = self.device.trackers.lock(); let transition =
trackers
.textures
.set_single(&dst, selector, hal::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)?;
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.mip_level, &dst.desc) { for layer_range in dst_initialization_status.mips[destination.mip_level as usize]
.drain(init_layer_range)
.collect::<Vec<std::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(),
)
.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),
);
}
// Use a hashmap here to deduplicate the surface textures that are used in the command buffers. // This avoids vulkan deadlocking from the same surface texture being submitted multiple times. letmut submit_surface_textures_owned = FastHashMap::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(Some("(wgpu internal) Present"))
{ break'error Err(e.into());
} let texture_barriers = trackers
.textures
.set_from_usage_scope_and_drain_transitions(
&used_surface_textures,
&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();
}
for texture in submit_surface_textures_owned.values() { let raw = match texture.inner.get(&snatch_guard) {
Some(TextureInner::Surface { raw, .. }) => raw.as_ref(),
_ => unreachable!(),
};
submit_surface_textures.push(raw);
}
// this will register the new submission to the life time tracker self.lock_life()
.track_submission(submit_index, active_executions);
drop(pending_writes);
// This will schedule destruction of all resources that are no longer needed // by the user but used in the command stream, among other things. let fence_guard = RwLockWriteGuard::downgrade(fence); let (closures, _) = matchself
.device
.maintain(fence_guard, wgt::Maintain::Poll, snatch_guard)
{
Ok(closures) => closures,
Err(WaitIdleError::Device(err)) => { break'error Err(QueueSubmitError::Queue(err))
}
Err(WaitIdleError::WrongSubmissionIndex(..)) => unreachable!(),
};
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();
api_log!("Queue::submit returned submit index {submit_index}");
//TODO: flush pending writes 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
}
}
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.