use alloc::{borrow::Cow, borrow::ToOwned as _, boxed::Box, string::String, sync::Arc, vec::Vec}; use core::{
borrow::Borrow,
fmt,
mem::{self, size_of, ManuallyDrop},
num::NonZeroU64,
ops::Range,
ptr::NonNull,
}; use smallvec::SmallVec; use thiserror::Error; use wgt::{
error::{ErrorType, WebGpuError},
TextureSelector,
};
/// Information about the wgpu-core resource. /// /// Each type representing a `wgpu-core` resource, like [`Device`], /// [`Buffer`], etc., contains a `ResourceInfo` which contains /// its latest submission index and label. /// /// A resource may need to be retained for any of several reasons: /// and any lifetime logic will be handled by `Arc<Resource>` refcount /// /// - The user may hold a reference to it (via a `wgpu::Buffer`, say). /// /// - Other resources may depend on it (a texture view's backing /// texture, for example). /// /// - It may be used by commands sent to the GPU that have not yet /// finished execution. /// /// [`Device`]: crate::device::resource::Device /// [`Buffer`]: crate::resource::Buffer #[derive(Debug)] pub(crate) struct TrackingData {
tracker_index: TrackerIndex,
tracker_indices: Arc<SharedTrackerIndexAllocator>,
}
impl Drop for TrackingData { fn drop(&mutself) { self.tracker_indices.free(self.tracker_index);
}
}
/// Allow access to the hal resource as guarded by the `SnatchGuard`. pubtrait RawResourceAccess: ParentDevice { type DynResource: hal::DynResource + ?Sized;
/// Get access to the raw resource if it is not destroyed. /// /// Returns `None` if the resource has been destroyed. This method /// does not allocate in either case. fn raw<'a>(&'a self, guard: &'a SnatchGuard) -> Option<&'a Self::DynResource>;
/// Get access to the raw resource if it is not destroyed. /// /// Returns a full error if the resource has been destroyed. This /// method allocates a label in the error case. fn try_raw<'a>(
&'a self,
guard: &'a SnatchGuard,
) -> Result<&'a Self::DynResource, DestroyedResourceError> { self.raw(guard)
.ok_or_else(|| DestroyedResourceError(self.error_ident()))
}
}
pubtrait Labeled: ResourceType { /// Returns a string identifying this resource for logging and errors. /// /// It may be a user-provided string or it may be a placeholder from wgpu. /// /// It is non-empty unless the user-provided string was empty. fn label(&self) -> &str;
#[derive(Debug)] pub(crate) struct BufferPendingMapping { pub(crate) range: Range<wgt::BufferAddress>, pub(crate) op: BufferMapOperation, // hold the parent alive while the mapping is active pub(crate) _parent_buffer: Arc<Buffer>,
}
/// Checks that the given buffer usage contains the required buffer usage, /// returns an error otherwise. pub(crate) fn check_usage(
&self,
expected: wgt::BufferUsages,
) -> Result<(), MissingBufferUsageError> { ifself.usage.contains(expected) {
Ok(())
} else {
Err(MissingBufferUsageError {
res: self.error_ident(),
actual: self.usage,
expected,
})
}
}
/// Resolve the size of a binding for buffer with `offset` and `size`. /// /// If `size` is `None`, then the remainder of the buffer starting from /// `offset` is used. /// /// If the binding would overflow the buffer, then an error is returned. /// /// Zero-size bindings are permitted here for historical reasons. Although /// zero-size bindings are permitted by WebGPU, they are not permitted by /// some backends. See [`Buffer::binding`] and /// [#3170](https://github.com/gfx-rs/wgpu/issues/3170). pubfn resolve_binding_size(
&self,
offset: wgt::BufferAddress,
binding_size: Option<wgt::BufferSize>,
) -> Result<u64, BindingError> { let buffer_size = self.size;
/// Create a new [`hal::BufferBinding`] for the buffer with `offset` and /// `binding_size`. /// /// If `binding_size` is `None`, then the remainder of the buffer starting /// from `offset` is used. /// /// If the binding would overflow the buffer, then an error is returned. /// /// A zero-size binding at the end of the buffer is permitted here for historical reasons. Although /// zero-size bindings are permitted by WebGPU, they are not permitted by /// some backends. The zero-size binding need to be quashed or remapped to a /// non-zero size, either universally in wgpu-core, or in specific backends /// that do not support them. See /// [#3170](https://github.com/gfx-rs/wgpu/issues/3170). /// /// Although it seems like it would be simpler and safer to use the resolved /// size in the returned [`hal::BufferBinding`], doing this (and removing /// redundant logic in backends to resolve the implicit size) was observed /// to cause problems in certain CTS tests, so an implicit size /// specification is preserved in the output. pubfn binding<'a>(
&'a self,
offset: wgt::BufferAddress,
binding_size: Option<wgt::BufferSize>,
snatch_guard: &'a SnatchGuard,
) -> Result<(hal::BufferBinding<'a, dyn hal::DynBuffer>, u64), BindingError> { let buf_raw = self.try_raw(snatch_guard)?; let resolved_size = self.resolve_binding_size(offset, binding_size)?; // SAFETY: The offset and size passed to hal::BufferBinding::new_unchecked must // define a binding contained within the buffer.
Ok((
hal::BufferBinding::new_unchecked(buf_raw, offset, binding_size),
resolved_size,
))
}
/// Schedule buffer mapping. /// /// `op.callback` is guaranteed to be called, regardless of the outcome. pubfn map_async( self: &Arc<Self>,
offset: wgt::BufferAddress,
size: Option<wgt::BufferAddress>,
op: BufferMapOperation,
) -> Result<SubmissionIndex, BufferAccessError> { self.try_map_async(offset, size, op)
.map_err(|(mut operation, err)| { iflet Some(callback) = operation.callback.take() {
callback(Err(err.clone()));
}
err
})
}
/// Try to schedule buffer mapping. /// /// The outcome of this function is one of the following: /// - If there is a queue, and nothing pending in the queue that uses the /// buffer in question, the buffer is added to `Queue::ready_to_map`, and /// will be mapped the next time `Device::maintain` is called. The /// queue assumes responsibility for calling the callback, and this /// function returns `Ok(0)`, but the buffer has not yet been mapped. /// - If there is a queue, and something is pending in the queue that uses /// the buffer in question, the buffer is scheduled for mapping after that /// submission completes. The queue assumes responsibility for calling the /// callback, and this function returns `Ok(index)` with the index of the /// submission that must complete. The buffer has not yet been mapped. /// - If there is no queue, the buffer is mapped and the callback is called /// immediately. The return value is `Ok(0)`. /// - Regardless of the queue state, if there is an error that terminates /// the buffer mapping attempt, this function returns the callback along /// with the error, and the caller is responsible for calling the /// callback. /// /// A return value of `Ok(0)` means that mapping does not need to wait on the queue, but /// it does not mean that the buffer has already been mapped. fn try_map_async( self: &Arc<Self>,
offset: wgt::BufferAddress,
size: Option<wgt::BufferAddress>,
op: BufferMapOperation,
) -> Result<SubmissionIndex, (BufferMapOperation, BufferAccessError)> { let range_size = iflet Some(size) = size {
size
} else { self.size.saturating_sub(offset)
};
if !offset.is_multiple_of(wgt::MAP_ALIGNMENT) { return Err((op, BufferAccessError::UnalignedOffset { offset }));
} if !range_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { return Err((op, BufferAccessError::UnalignedRangeSize { range_size }));
}
if offset > self.size { return Err((
op,
BufferAccessError::MapStartOffsetOverrun {
offset,
buffer_size: self.size,
},
));
} // NOTE: Should never underflow because of our earlier check. if range_size > self.size - offset { return Err((
op,
BufferAccessError::MapEndOffsetOverrun {
offset,
size: range_size,
buffer_size: self.size,
},
));
} let end_offset = offset + range_size;
if !offset.is_multiple_of(wgt::MAP_ALIGNMENT)
|| !end_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT)
{ return Err((op, BufferAccessError::UnalignedRange));
}
let (pub_usage, internal_use) = match op.host {
HostMap::Read => (wgt::BufferUsages::MAP_READ, wgt::BufferUses::MAP_READ),
HostMap::Write => (wgt::BufferUsages::MAP_WRITE, wgt::BufferUses::MAP_WRITE),
};
iflet Some(queue) = device.get_queue().as_ref() { match queue.flush_writes_for_buffer(self, snatch_guard) {
Err(err) => { let state = mem::replace(&mut *self.map_state.lock(), BufferMapState::Idle); let BufferMapState::Waiting(BufferPendingMapping { op, .. }) = state else {
unreachable!();
}; return Err((op, err));
}
Ok(()) => { // Schedule the buffer map in the lifetime tracker. // // This call searches for use of the buffer by pending submissions. // If we just flushed pending writes, that search is redundant; we // already know that mapping needs to wait for the latest submission // and could implement a special case to directly attach it to that // submission. However, the queue is searched in reverse, so finding // that the buffer is used by the latest submission will be fast.
Some(queue.lock_life().map(self).unwrap_or(0))
}
}
} else {
None
}
};
// At this point, `submit_index` is: // - `Some(index)`, if there is a submission the mapping operation must wait for. // - `Some(0)`, if we have a queue and there is no submission to wait for. // - `None`, if we don't have a queue. // // TODO(https://github.com/gfx-rs/wgpu/issues/9306): we are ignoring the transition // here, I think we need to add a barrier at the end of the submission
device
.trackers
.lock()
.buffers
.set_single(self, internal_use);
iflet Some(index) = submit_index {
Ok(index)
} else { // We don't have a queue, so go ahead and map the buffer. // We can safely unwrap below since we just set the `map_state` to `BufferMapState::Waiting`. let (mut operation, status) = self.map(&device.snatchable_lock.read()).unwrap(); iflet Some(callback) = operation.callback.take() {
callback(status);
}
Ok(0)
}
}
if !offset.is_multiple_of(wgt::MAP_ALIGNMENT) { return Err(BufferAccessError::UnalignedOffset { offset });
} if !range_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { return Err(BufferAccessError::UnalignedRangeSize { range_size });
} let map_state = &*self.map_state.lock(); match *map_state {
BufferMapState::Init { ref staging_buffer } => { if offset > self.size { return Err(BufferAccessError::MapStartOffsetOverrun {
offset,
buffer_size: self.size,
});
} // NOTE: Should never underflow because of our earlier check. if range_size > self.size - offset { return Err(BufferAccessError::MapEndOffsetOverrun {
offset,
size: range_size,
buffer_size: self.size,
});
} let ptr = unsafe { staging_buffer.ptr() }; let ptr = unsafe { NonNull::new_unchecked(ptr.as_ptr().offset(offset as isize)) };
Ok((ptr, range_size))
}
BufferMapState::Active { ref mapping, ref range,
..
} => { if offset > range.end { return Err(BufferAccessError::OutOfBoundsStartOffsetOverrun {
index: offset,
max: range.end,
});
} if offset < range.start { return Err(BufferAccessError::OutOfBoundsStartOffsetUnderrun {
index: offset,
min: range.start,
});
} if range_size > range.end - offset { return Err(BufferAccessError::OutOfBoundsEndOffsetOverrun {
index: offset,
size: range_size,
max: range.end,
});
} // ptr points to the beginning of the range we mapped in map_async // rather than the beginning of the buffer. let relative_offset = (offset - range.start) as isize; unsafe {
Ok((
NonNull::new_unchecked(mapping.ptr.as_ptr().offset(relative_offset)),
range_size,
))
}
}
BufferMapState::Idle | BufferMapState::Waiting(_) => Err(BufferAccessError::NotMapped),
}
} /// This function returns [`None`] only if [`Self::map_state`] is not [`BufferMapState::Waiting`]. /// Other errors are returned within `BufferMapPendingClosure`. #[must_use] pub(crate) fn map(&self, snatch_guard: &SnatchGuard) -> Option<BufferMapPendingClosure> { // This _cannot_ be inlined into the match. If it is, the lock will be held // open through the whole match, resulting in a deadlock when we try to re-lock // the buffer back to active. let mapping = mem::replace(&mut *self.map_state.lock(), BufferMapState::Idle); let pending_mapping = match mapping {
BufferMapState::Waiting(pending_mapping) => pending_mapping, // Mapping cancelled
BufferMapState::Idle => return None, // Mapping queued at least twice by map -> unmap -> map // and was already successfully mapped below
BufferMapState::Active { .. } => {
*self.map_state.lock() = mapping; return None;
}
_ => panic!("No pending mapping."),
}; let status = if pending_mapping.range.start != pending_mapping.range.end { let host = pending_mapping.op.host; let size = pending_mapping.range.end - pending_mapping.range.start; matchcrate::device::map_buffer( self,
pending_mapping.range.start,
size,
host,
snatch_guard,
) {
Ok(mapping) => {
*self.map_state.lock() = BufferMapState::Active {
mapping,
range: pending_mapping.range.clone(),
host,
};
Ok(())
}
Err(e) => Err(e),
}
} else {
*self.map_state.lock() = BufferMapState::Active {
mapping: hal::BufferMapping {
ptr: NonNull::dangling(),
is_coherent: true,
},
range: pending_mapping.range,
host: pending_mapping.op.host,
};
Ok(())
};
Some((pending_mapping.op, status))
}
// Note: This must not be called while holding a lock. pubfn unmap(self: &Arc<Self>) -> Result<(), BufferAccessError> { iflet Some((mut operation, status)) = self.unmap_inner()? { iflet Some(callback) = operation.callback.take() {
callback(status);
}
}
Ok(())
}
fn unmap_inner(self: &Arc<Self>) -> Result<Option<BufferMapPendingClosure>, BufferAccessError> { let device = &self.device; let snatch_guard = device.snatchable_lock.read(); let raw_buf = self.try_raw(&snatch_guard)?; let map_state = mem::replace(&mut *self.map_state.lock(), BufferMapState::Idle); match map_state {
BufferMapState::Init { staging_buffer } => { #[cfg(feature = "trace")] iflet Some(refmut trace) = *device.trace.lock() { usecrate::device::trace::{DataKind, IntoTrace};
let data = trace.make_binary(DataKind::Bin, staging_buffer.get_data());
trace.add(trace::Action::WriteBuffer {
id: self.to_trace(),
data, // NOTE: `self.size` here corresponds to `data`'s actual length.
offset: 0,
size: self.size,
queued: true,
});
}
let size = range.end - range.start; let data = trace.make_binary(DataKind::Bin, unsafe {
core::slice::from_raw_parts(mapping.ptr.as_ptr(), size as usize)
});
trace.add(trace::Action::WriteBuffer {
id: self.to_trace(),
data,
offset: range.start,
size,
queued: false,
});
} if !mapping.is_coherent { unsafe { device.raw().flush_mapped_ranges(raw_buf, &[range]) };
}
} unsafe { device.raw().unmap_buffer(raw_buf) };
}
}
Ok(None)
}
pubfn destroy(self: &Arc<Self>) { let device = &self.device;
let temp = { letmut snatch_guard = device.snatchable_lock.write();
let raw = matchself.raw.snatch(&mut snatch_guard) {
Some(raw) => raw,
None => { // Per spec, it is valid to call `destroy` multiple times. return;
}
};
let timestamp_normalization_bind_group = self
.timestamp_normalization_bind_group
.snatch(&mut snatch_guard);
let indirect_validation_bind_groups = self
.indirect_validation_bind_groups
.snatch(&mut snatch_guard);
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreateBufferError { #[error(transparent)]
Device(#[from] DeviceError), #[error("Failed to map buffer while creating: {0}")]
AccessError(#[from] BufferAccessError), #[error("Buffers that are mapped at creation have to be aligned to `COPY_BUFFER_ALIGNMENT`")]
UnalignedSize, #[error("Invalid usage flags {0:?}")]
InvalidUsage(wgt::BufferUsages), #[error("`MAP` usage can only be combined with the opposite `COPY`, requested {0:?}")]
UsageMismatch(wgt::BufferUsages), #[error("Buffer size {requested} is greater than the maximum buffer size ({maximum})")]
MaxBufferSize { requested: u64, maximum: u64 }, #[error(transparent)]
MissingDownlevelFlags(#[from] MissingDownlevelFlags), #[error(transparent)]
MissingFeatures(#[from] MissingFeatures), #[error("Failed to create bind group for indirect buffer validation: {0}")]
IndirectValidationBindGroup(DeviceError),
}
/// A buffer that has been marked as destroyed and is staged for actual deletion soon. #[derive(Debug)] pubstruct DestroyedBuffer {
raw: ManuallyDrop<Box<dyn hal::DynBuffer>>,
device: Arc<Device>,
label: String,
bind_groups: WeakVec<BindGroup>,
timestamp_normalization_bind_group: Option<TimestampNormalizationBindGroup>,
indirect_validation_bind_groups: Option<crate::indirect_validation::BindGroups>,
}
resource_log!("Destroy raw Buffer (destroyed) {:?}", self.label()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mutself.raw) }; unsafe {
hal::DynDevice::destroy_buffer(self.device.raw(), raw);
}
}
}
#[cfg(send_sync)] unsafeimpl Send for StagingBuffer {} #[cfg(send_sync)] unsafeimpl Sync for StagingBuffer {}
/// A temporary buffer, consumed by the command that uses it. /// /// A [`StagingBuffer`] is designed for one-shot uploads of data to the GPU. It /// is always created mapped, and the command that uses it destroys the buffer /// when it is done. /// /// [`StagingBuffer`]s can be created with [`queue_create_staging_buffer`] and /// used with [`queue_write_staging_buffer`]. They are also used internally by /// operations like [`queue_write_texture`] that need to upload data to the GPU, /// but that don't belong to any particular wgpu command buffer. /// /// Used `StagingBuffer`s are accumulated in [`Device::pending_writes`], to be /// freed once their associated operation's queue submission has finished /// execution. /// /// [`queue_create_staging_buffer`]: crate::global::Global::queue_create_staging_buffer /// [`queue_write_staging_buffer`]: crate::global::Global::queue_write_staging_buffer /// [`queue_write_texture`]: crate::global::Global::queue_write_texture /// [`Device::pending_writes`]: crate::device::Device #[derive(Debug)] pubstruct StagingBuffer {
raw: Box<dyn hal::DynBuffer>,
device: Arc<Device>, pub(crate) size: wgt::BufferSize,
is_coherent: bool,
ptr: NonNull<u8>,
}
/// SAFETY: You must not call any functions of `self` /// until you stopped using the returned pointer. pub(crate) unsafefn ptr(&self) -> NonNull<u8> { self.ptr
}
pub(crate) fn write(&mutself, data: &[u8]) {
assert!(data.len() >= self.size.get() as usize); // SAFETY: With the assert above, all of `copy_nonoverlapping`'s // requirements are satisfied. unsafe {
core::ptr::copy_nonoverlapping(
data.as_ptr(), self.ptr.as_ptr(), self.size.get() as usize,
);
}
}
/// SAFETY: The offsets and size must be in-bounds. pub(crate) unsafefn write_with_offset(
&mutself,
data: &[u8],
src_offset: isize,
dst_offset: isize,
size: usize,
) { unsafe {
debug_assert!(
(src_offset + size as isize) as usize <= data.len(), "src_offset + size must be in-bounds: src_offset = {}, size = {}, data.len() = {}",
src_offset,
size,
data.len()
);
core::ptr::copy_nonoverlapping(
data.as_ptr().offset(src_offset), self.ptr.as_ptr().offset(dst_offset),
size,
);
}
}
impl Drop for FlushedStagingBuffer { fn drop(&mutself) {
resource_log!("Destroy raw StagingBuffer"); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mutself.raw) }; unsafe { self.device.raw().destroy_buffer(raw) };
}
}
#[derive(Debug)] pubenum TextureClearMode {
BufferCopy, // View for clear via RenderPass for every subsurface (mip/layer/slice)
RenderPass {
clear_views: SmallVec<[ManuallyDrop<Box<dyn hal::DynTextureView>>; 1]>,
is_color: bool,
},
Surface {
clear_view: ManuallyDrop<Box<dyn hal::DynTextureView>>,
}, // Texture can't be cleared, attempting to do so will cause panic. // (either because it is impossible for the type of texture or it is being destroyed)
None,
}
#[derive(Debug)] pubstruct Texture { pub(crate) inner: Snatchable<TextureInner>, pub(crate) device: Arc<Device>, pub(crate) desc: wgt::TextureDescriptor<(), Vec<wgt::TextureFormat>>, pub(crate) _hal_usage: wgt::TextureUses, pub(crate) format_features: wgt::TextureFormatFeatures, pub(crate) initialization_status: RwLock<TextureInitTracker>, pub(crate) full_range: TextureSelector, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) clear_mode: RwLock<TextureClearMode>, pub(crate) views: Mutex<WeakVec<TextureView>>, // Bind groups that reference this texture. May contain duplicates. pub(crate) bind_groups: Mutex<WeakVec<BindGroup>>,
}
/// Checks that the given texture usage contains the required texture usage, /// returns an error otherwise. pub(crate) fn check_usage(
&self,
expected: wgt::TextureUsages,
) -> Result<(), MissingTextureUsageError> { ifself.desc.usage.contains(expected) {
Ok(())
} else {
Err(MissingTextureUsageError {
res: self.error_ident(),
actual: self.desc.usage,
expected,
})
}
}
}
impl Drop for Texture { fn drop(&mutself) { match *self.clear_mode.write() {
TextureClearMode::Surface { refmut clear_view, ..
} => { // SAFETY: We are in the Drop impl and we don't use clear_view anymore after this point. let raw = unsafe { ManuallyDrop::take(clear_view) }; unsafe { self.device.raw().destroy_texture_view(raw);
}
}
TextureClearMode::RenderPass { refmut clear_views,
..
} => {
clear_views.iter_mut().for_each(|clear_view| { // SAFETY: We are in the Drop impl and we don't use clear_view anymore after this point. let raw = unsafe { ManuallyDrop::take(clear_view) }; unsafe { self.device.raw().destroy_texture_view(raw);
}
});
}
_ => {}
};
iflet Some(TextureInner::Native { raw }) = self.inner.take() {
resource_log!("Destroy raw {}", self.error_ident()); unsafe { self.device.raw().destroy_texture(raw);
}
}
}
}
impl RawResourceAccess for Texture { type DynResource = dyn hal::DynTexture;
/// A texture that has been marked as destroyed and is staged for actual deletion soon. #[derive(Debug)] pubstruct DestroyedTexture {
raw: ManuallyDrop<Box<dyn hal::DynTexture>>,
views: WeakVec<TextureView>,
clear_mode: TextureClearMode,
bind_groups: WeakVec<BindGroup>,
device: Arc<Device>,
label: String,
}
match mem::replace(&mutself.clear_mode, TextureClearMode::None) {
TextureClearMode::RenderPass { clear_views, .. } => { for clear_view in clear_views { let raw = ManuallyDrop::into_inner(clear_view); unsafe { self.device.raw().destroy_texture_view(raw) };
}
}
TextureClearMode::Surface { clear_view } => { let raw = ManuallyDrop::into_inner(clear_view); unsafe { self.device.raw().destroy_texture_view(raw) };
}
_ => (),
}
resource_log!("Destroy raw Texture (destroyed) {:?}", self.label()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mutself.raw) }; unsafe { self.device.raw().destroy_texture(raw);
}
}
}
#[derive(Clone, Copy, Debug)] pubenum TextureErrorDimension {
X,
Y,
Z,
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum TextureDimensionError { #[error("Dimension {0:?} is zero")]
Zero(TextureErrorDimension), #[error("Dimension {dim:?} value {given} exceeds the limit of {limit}")]
LimitExceeded {
dim: TextureErrorDimension,
given: u32,
limit: u32,
}, #[error("Sample count {0} is invalid")]
InvalidSampleCount(u32), #[error("Width {width} is not a multiple of {format:?}'s block width ({block_width})")]
NotMultipleOfBlockWidth {
width: u32,
block_width: u32,
format: wgt::TextureFormat,
}, #[error("Height {height} is not a multiple of {format:?}'s block height ({block_height})")]
NotMultipleOfBlockHeight {
height: u32,
block_height: u32,
format: wgt::TextureFormat,
}, #[error( "Width {width} is not a multiple of {format:?}'s width multiple requirement ({multiple})"
)]
WidthNotMultipleOf {
width: u32,
multiple: u32,
format: wgt::TextureFormat,
}, #[error("Height {height} is not a multiple of {format:?}'s height multiple requirement ({multiple})")]
HeightNotMultipleOf {
height: u32,
multiple: u32,
format: wgt::TextureFormat,
}, #[error("Multisampled texture depth or array layers must be 1, got {0}")]
MultisampledDepthOrArrayLayer(u32),
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreateTextureError { #[error(transparent)]
Device(#[from] DeviceError), #[error(transparent)]
CreateTextureView(#[from] CreateTextureViewError), #[error("Invalid usage flags {0:?}")]
InvalidUsage(wgt::TextureUsages), #[error("Texture usage {0:?} is not compatible with texture usage {1:?}")]
IncompatibleUsage(wgt::TextureUsages, wgt::TextureUsages), #[error(transparent)]
InvalidDimension(#[from] TextureDimensionError), #[error("Depth texture ({1:?}) can't be created as {0:?}")]
InvalidDepthDimension(wgt::TextureDimension, wgt::TextureFormat), #[error("Compressed texture ({1:?}) can't be created as {0:?}")]
InvalidCompressedDimension(wgt::TextureDimension, wgt::TextureFormat), #[error( "Texture descriptor mip level count {requested} is invalid, maximum allowed is {maximum}"
)]
InvalidMipLevelCount { requested: u32, maximum: u32 }, #[error( "Texture usages {0:?} are not allowed on a texture of type {1:?}{downlevel_suffix}",
downlevel_suffix = if *.2 { " due to downlevel restrictions" } else { "" }
)]
InvalidFormatUsages(wgt::TextureUsages, wgt::TextureFormat, bool), #[error("The view format {0:?} is not compatible with texture format {1:?}, only changing srgb-ness is allowed.")]
InvalidViewFormat(wgt::TextureFormat, wgt::TextureFormat), #[error("Texture usages {0:?} are not allowed on a texture of dimensions {1:?}")]
InvalidDimensionUsages(wgt::TextureUsages, wgt::TextureDimension), #[error("Texture usage STORAGE_BINDING is not allowed for multisampled textures")]
InvalidMultisampledStorageBinding, #[error("Format {0:?} does not support multisampling")]
InvalidMultisampledFormat(wgt::TextureFormat), #[error("Sample count {0} is not supported by format {1:?} on this device. The WebGPU spec guarantees {2:?} samples are supported by this format. With the TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES feature your device supports {3:?}.")]
InvalidSampleCount(u32, wgt::TextureFormat, Vec<u32>, Vec<u32>), #[error("Multisampled textures must have RENDER_ATTACHMENT usage")]
MultisampledNotRenderAttachment, #[error("Texture format {0:?} can't be used due to missing features")]
MissingFeatures(wgt::TextureFormat, #[source] MissingFeatures), #[error(transparent)]
MissingDownlevelFlags(#[from] MissingDownlevelFlags),
}
/// Describes a [`TextureView`]. #[derive(Clone, Debug, Default, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(default))] pubstruct TextureViewDescriptor<'a> { /// Debug label of the texture view. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// Format of the texture view, or `None` for the same format as the texture /// itself. /// /// At this time, it must be the same the underlying format of the texture. pub format: Option<wgt::TextureFormat>, /// The dimension of the texture view. /// /// - For 1D textures, this must be `D1`. /// - For 2D textures it must be one of `D2`, `D2Array`, `Cube`, or `CubeArray`. /// - For 3D textures it must be `D3`. pub dimension: Option<wgt::TextureViewDimension>, /// The allowed usage(s) for the texture view. Must be a subset of the usage flags of the texture. /// If not provided, defaults to the full set of usage flags of the texture. pub usage: Option<wgt::TextureUsages>, /// Range within the texture that is accessible via this view. pub range: wgt::ImageSubresourceRange,
}
#[derive(Debug, Copy, Clone, Error)] pubenum TextureViewNotRenderableReason { #[error("The texture this view references doesn't include the RENDER_ATTACHMENT usage. Provided usages: {0:?}")]
Usage(wgt::TextureUsages), #[error("The dimension of this texture view is not 2D. View dimension: {0:?}")]
Dimension(wgt::TextureViewDimension), #[error("This texture view has more than one mipmap level. View mipmap levels: {0:?}")]
MipLevelCount(u32), #[error("This texture view has more than one array layer. View array layers: {0:?}")]
ArrayLayerCount(u32), #[error( "The aspects of this texture view are a subset of the aspects in the original texture. Aspects: {0:?}"
)]
Aspects(hal::FormatAspects),
}
#[derive(Debug)] pubstruct TextureView { pub(crate) raw: Snatchable<Box<dyn hal::DynTextureView>>, // if it's a surface texture - it's none pub(crate) parent: Arc<Texture>, pub(crate) device: Arc<Device>, pub(crate) desc: HalTextureViewDescriptor, pub(crate) format_features: wgt::TextureFormatFeatures, /// This is `Err` only if the texture view is not renderable pub(crate) render_extent: Result<wgt::Extent3d, TextureViewNotRenderableReason>, pub(crate) samples: u32, pub(crate) selector: TextureSelector, /// The `label` from the descriptor used to create the resource. pub(crate) label: String,
}
impl Drop for TextureView { fn drop(&mutself) { iflet Some(raw) = self.raw.take() {
resource_log!("Destroy raw {}", self.error_ident()); unsafe { self.device.raw().destroy_texture_view(raw);
}
}
}
}
impl RawResourceAccess for TextureView { type DynResource = dyn hal::DynTextureView;
impl TextureView { /// Checks that the given texture usage contains the required texture usage, /// returns an error otherwise. pub(crate) fn check_usage(
&self,
expected: wgt::TextureUsages,
) -> Result<(), MissingTextureUsageError> { ifself.desc.usage.contains(expected) {
Ok(())
} else {
Err(MissingTextureUsageError {
res: self.error_ident(),
actual: self.desc.usage,
expected,
})
}
}
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreateTextureViewError { #[error(transparent)]
Device(#[from] DeviceError), #[error(transparent)]
DestroyedResource(#[from] DestroyedResourceError), #[error("Invalid texture view dimension `{view:?}` with texture of dimension `{texture:?}`")]
InvalidTextureViewDimension {
view: wgt::TextureViewDimension,
texture: wgt::TextureDimension,
}, #[error("Texture view format `{0:?}` cannot be used as a render attachment. Make sure the format supports RENDER_ATTACHMENT usage and required device features are enabled.")]
TextureViewFormatNotRenderable(wgt::TextureFormat), #[error("Texture view format `{0:?}` cannot be used as a storage binding. Make sure the format supports STORAGE usage and required device features are enabled.")]
TextureViewFormatNotStorage(wgt::TextureFormat), #[error("Texture view usages (`{view:?}`) must be a subset of the texture's original usages (`{texture:?}`)")]
InvalidTextureViewUsage {
view: wgt::TextureUsages,
texture: wgt::TextureUsages,
}, #[error("Texture view dimension `{0:?}` cannot be used with a multisampled texture")]
InvalidMultisampledTextureViewDimension(wgt::TextureViewDimension), #[error( "TextureView has an arrayLayerCount of {depth}. Views of type `Cube` must have arrayLayerCount of 6."
)]
InvalidCubemapTextureDepth { depth: u32 }, #[error("TextureView has an arrayLayerCount of {depth}. Views of type `CubeArray` must have an arrayLayerCount that is a multiple of 6.")]
InvalidCubemapArrayTextureDepth { depth: u32 }, #[error("Source texture width and height must be equal for a texture view of dimension `Cube`/`CubeArray`")]
InvalidCubeTextureViewSize, #[error("Mip level count is 0")]
ZeroMipLevelCount, #[error("Array layer count is 0")]
ZeroArrayLayerCount, #[error( "`TextureView` starts at mip level {base_mip_level} and spans {mip_level_count} mip \
levels, but the texture view only has {total} total mip level(s)"
)]
TooManyMipLevels {
base_mip_level: u32,
mip_level_count: u32,
total: u32,
}, #[error( "`TextureView` starts at array layer {base_array_layer} and spans {array_layer_count}) \
array layers, but the texture view only has {total} total layer(s)"
)]
TooManyArrayLayers {
base_array_layer: u32,
array_layer_count: u32,
total: u32,
}, #[error("Requested array layer count {requested} is not valid for the target view dimension {dim:?}")]
InvalidArrayLayerCount {
requested: u32,
dim: wgt::TextureViewDimension,
}, #[error( "Aspect {requested_aspect:?} is not a valid aspect of the source texture format {texture_format:?}"
)]
InvalidAspect {
texture_format: wgt::TextureFormat,
requested_aspect: wgt::TextureAspect,
}, #[error( "Trying to create a view of format {view:?} of a texture with format {texture:?}, \
but this view format is not present in the texture's viewFormat array"
)]
FormatReinterpretation {
texture: wgt::TextureFormat,
view: wgt::TextureFormat,
}, #[error(transparent)]
InvalidResource(#[from] InvalidResourceError), #[error(transparent)]
MissingFeatures(#[from] MissingFeatures),
}
#[derive(Debug)] pubstruct ExternalTexture { pub(crate) device: Arc<Device>, /// Between 1 and 3 (inclusive) planes of texture data. pub(crate) planes: arrayvec::ArrayVec<Arc<TextureView>, 3>, /// Buffer containing a [`crate::device::resource::ExternalTextureParams`] /// describing the external texture. pub(crate) params: Arc<Buffer>, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData,
}
impl Drop for ExternalTexture { fn drop(&mutself) {
resource_log!("Destroy raw {}", self.error_ident());
}
}
/// Describes a [`Sampler`] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pubstruct SamplerDescriptor<'a> { /// Debug label of the sampler. /// /// This will show up in graphics debuggers for easy identification. pub label: Label<'a>, /// How to deal with out of bounds accesses in the u (i.e. x) direction pub address_modes: [wgt::AddressMode; 3], /// How to filter the texture when it needs to be magnified (made larger) pub mag_filter: wgt::FilterMode, /// How to filter the texture when it needs to be minified (made smaller) pub min_filter: wgt::FilterMode, /// How to filter between mip map levels pub mipmap_filter: wgt::MipmapFilterMode, /// Minimum level of detail (i.e. mip level) to use pub lod_min_clamp: f32, /// Maximum level of detail (i.e. mip level) to use pub lod_max_clamp: f32, /// If this is enabled, this is a comparison sampler using the given comparison function. pub compare: Option<wgt::CompareFunction>, /// Must be at least 1. If this is not 1, all filter modes must be linear. pub anisotropy_clamp: u16, /// Border color to use when address_mode is /// [`AddressMode::ClampToBorder`](wgt::AddressMode::ClampToBorder) pub border_color: Option<wgt::SamplerBorderColor>,
}
#[derive(Debug)] pubstruct Sampler { pub(crate) raw: ManuallyDrop<Box<dyn hal::DynSampler>>, pub(crate) device: Arc<Device>, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, /// `true` if this is a comparison sampler pub(crate) comparison: bool, /// `true` if this is a filtering sampler pub(crate) filtering: bool,
}
impl Drop for Sampler { fn drop(&mutself) {
resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mutself.raw) }; unsafe { self.device.raw().destroy_sampler(raw);
}
}
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreateSamplerError { #[error(transparent)]
Device(#[from] DeviceError), #[error("Invalid lodMinClamp: {0}. Must be greater or equal to 0.0")]
InvalidLodMinClamp(f32), #[error("Invalid lodMaxClamp: {lod_max_clamp}. Must be greater or equal to lodMinClamp (which is {lod_min_clamp}).")]
InvalidLodMaxClamp {
lod_min_clamp: f32,
lod_max_clamp: f32,
}, #[error("Invalid anisotropic clamp: {0}. Must be at least 1.")]
InvalidAnisotropy(u16), #[error("Invalid filter mode for {filter_type:?}: {filter_mode:?}. When anistropic clamp is not 1 (it is {anisotropic_clamp}), all filter modes must be linear.")]
InvalidFilterModeWithAnisotropy {
filter_type: SamplerFilterErrorType,
filter_mode: wgt::FilterMode,
anisotropic_clamp: u16,
}, #[error("Invalid filter mode for {filter_type:?}: {filter_mode:?}. When anistropic clamp is not 1 (it is {anisotropic_clamp}), all filter modes must be linear.")]
InvalidMipmapFilterModeWithAnisotropy {
filter_type: SamplerFilterErrorType,
filter_mode: wgt::MipmapFilterMode,
anisotropic_clamp: u16,
}, #[error(transparent)]
MissingFeatures(#[from] MissingFeatures),
}
#[derive(Clone, Debug, Error)] #[non_exhaustive] pubenum CreateQuerySetError { #[error(transparent)]
Device(#[from] DeviceError), #[error("QuerySets cannot be made with zero queries")]
ZeroCount, #[error("{count} is too many queries for a single QuerySet. QuerySets cannot be made more than {maximum} queries.")]
TooManyQueries { count: u32, maximum: u32 }, #[error(transparent)]
MissingFeatures(#[from] MissingFeatures),
}
#[derive(Debug)] pubstruct QuerySet { pub(crate) raw: ManuallyDrop<Box<dyn hal::DynQuerySet>>, pub(crate) device: Arc<Device>, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) desc: wgt::QuerySetDescriptor<()>, pub(crate) initialized_slots: Mutex<bit_vec::BitVec>,
}
impl Drop for QuerySet { fn drop(&mutself) {
resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl and we don't use self.raw anymore after this point. let raw = unsafe { ManuallyDrop::take(&mutself.raw) }; unsafe { self.device.raw().destroy_query_set(raw);
}
}
}
pub(crate) struct BlasPendingCompact { pub(crate) op: Option<BlasCompactCallback>, // hold the parent alive while the mapping is active pub(crate) _parent_blas: Arc<Blas>,
}
#[derive(Debug)] pub(crate) enum BlasCompactState { /// Created from a compact operation.
Compacted, /// Waiting for GPU to be done before mapping to get compacted size
Waiting(BlasPendingCompact), /// Ready to be compacted
Ready { size: wgt::BufferAddress }, /// Ready to prepare to compact.
Idle,
}
#[cfg(send_sync)] unsafeimpl Send for BlasCompactState {} #[cfg(send_sync)] unsafeimpl Sync for BlasCompactState {}
#[derive(Debug)] pubstruct Blas { pub(crate) raw: Snatchable<Box<dyn hal::DynAccelerationStructure>>, pub(crate) device: Arc<Device>, pub(crate) size_info: hal::AccelerationStructureBuildSizes, pub(crate) sizes: wgt::BlasGeometrySizeDescriptors, pub(crate) flags: wgt::AccelerationStructureFlags, pub(crate) update_mode: wgt::AccelerationStructureUpdateMode, pub(crate) built_index: RwLock<Option<NonZeroU64>>, pub(crate) handle: u64, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, pub(crate) tracking_data: TrackingData, pub(crate) compaction_buffer: Option<ManuallyDrop<Box<dyn hal::DynBuffer>>>, pub(crate) compacted_state: Mutex<BlasCompactState>,
}
impl Drop for Blas { fn drop(&mutself) {
resource_log!("Destroy raw {}", self.error_ident()); // SAFETY: We are in the Drop impl, and we don't use self.raw or self.compaction_buffer anymore after this point. iflet Some(raw) = self.raw.take() { unsafe { self.device.raw().destroy_acceleration_structure(raw);
}
} iflet Some(mut raw) = self.compaction_buffer.take() { unsafe { self.device
.raw()
.destroy_buffer(ManuallyDrop::take(&mut raw))
}
}
}
}
impl RawResourceAccess for Blas { type DynResource = dyn hal::DynAccelerationStructure;
let submit_index = iflet Some(queue) = device.get_queue() {
queue.lock_life().prepare_compact(self).unwrap_or(0) // '0' means no wait is necessary
} else { // We can safely unwrap below since we just set the `compacted_state` to `BlasCompactState::Waiting`. let (mut callback, status) = self.read_back_compact_size().unwrap(); iflet Some(callback) = callback.take() {
callback(status);
} 0
};
Ok(submit_index)
}
/// This function returns [`None`] only if [`Self::compacted_state`] is not [`BlasCompactState::Waiting`]. #[must_use] pub(crate) fn read_back_compact_size(&self) -> Option<BlasCompactReadyPendingClosure> { letmut state = self.compacted_state.lock(); let pending_compact = match mem::replace(&mut *state, BlasCompactState::Idle) {
BlasCompactState::Waiting(pending_mapping) => pending_mapping, // Compaction cancelled e.g. by rebuild
BlasCompactState::Idle => return None,
BlasCompactState::Ready { .. } => {
unreachable!("This should be validated out by `prepare_for_compaction`")
}
_ => panic!("No pending mapping."),
}; let status = { let compaction_buffer = self.compaction_buffer.as_ref().unwrap().as_ref(); unsafe { let map_res = self.device.raw().map_buffer(
compaction_buffer, 0..size_of::<wgpu_types::BufferAddress>() as wgt::BufferAddress,
); match map_res {
Ok(mapping) => { if !mapping.is_coherent { #[expect(clippy::single_range_in_vec_init, reason = "intentional")] self.device.raw().invalidate_mapped_ranges(
compaction_buffer,
&[0..size_of::<wgpu_types::BufferAddress>() as wgt::BufferAddress],
);
} let size = core::ptr::read_unaligned(
mapping.ptr.as_ptr().cast::<wgt::BufferAddress>(),
); self.device.raw().unmap_buffer(compaction_buffer); ifself.size_info.acceleration_structure_size != 0 {
debug_assert_ne!(size, 0);
}
*state = BlasCompactState::Ready { size };
Ok(())
}
Err(err) => Err(BlasPrepareCompactError::from(DeviceError::from_hal(err))),
}
}
};
Some((pending_compact.op, status))
}
}
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.