#[cfg(feature = "trace")] use crate::device::trace; use crate::{ binding_model::BindGroup, device::{ queue, resource::DeferredDestroy, BufferMapPendingClosure, Device, DeviceError, HostMap, MissingDownlevelFlags, MissingFeatures, }, global::Global, hal_api::HalApi, id::{AdapterId, BufferId, DeviceId, Id, Marker, SurfaceId, TextureId}, init_tracker::{BufferInitTracker, TextureInitTracker}, resource, resource_log, snatch::{ExclusiveSnatchGuard, SnatchGuard, Snatchable}, track::{SharedTrackerIndexAllocator, TextureSelector, TrackerIndex}, validation::MissingBufferUsageError, Label, SubmissionIndex, }; use hal::CommandEncoder; use parking_lot::{Mutex, RwLock}; use smallvec::SmallVec; use thiserror::Error; use wgt::WasmNotSendSync; use std::{ borrow::Borrow, fmt::Debug, iter, mem, ops::Range, ptr::NonNull, sync::{ atomic::{AtomicUsize, Ordering}, Arc, Weak, }, }; /// 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` 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 struct ResourceInfo { id: Option>, tracker_index: TrackerIndex, tracker_indices: Option>, /// The index of the last queue submission in which the resource /// was used. /// /// Each queue submission is fenced and assigned an index number /// sequentially. Thus, when a queue submission completes, we know any /// resources used in that submission and any lower-numbered submissions are /// no longer in use by the GPU. submission_index: AtomicUsize, /// The `label` from the descriptor used to create the resource. pub(crate) label: String, } impl Drop for ResourceInfo { fn drop(&mut self) { if let Some(indices) = &self.tracker_indices { indices.free(self.tracker_index); } } } impl ResourceInfo { #[allow(unused_variables)] pub(crate) fn new( label: &str, tracker_indices: Option>, ) -> Self { let tracker_index = tracker_indices .as_ref() .map(|indices| indices.alloc()) .unwrap_or(TrackerIndex::INVALID); Self { id: None, tracker_index, tracker_indices, submission_index: AtomicUsize::new(0), label: label.to_string(), } } pub(crate) fn label(&self) -> &dyn Debug where Id: Debug, { if !self.label.is_empty() { return &self.label; } if let Some(id) = &self.id { return id; } &"" } pub(crate) fn id(&self) -> Id { self.id.unwrap() } pub(crate) fn tracker_index(&self) -> TrackerIndex { debug_assert!(self.tracker_index != TrackerIndex::INVALID); self.tracker_index } pub(crate) fn set_id(&mut self, id: Id) { self.id = Some(id); } /// Record that this resource will be used by the queue submission with the /// given index. pub(crate) fn use_at(&self, submit_index: SubmissionIndex) { self.submission_index .store(submit_index as _, Ordering::Release); } pub(crate) fn submission_index(&self) -> SubmissionIndex { self.submission_index.load(Ordering::Acquire) as _ } } pub(crate) type ResourceType = &'static str; pub trait Resource: 'static + Sized + WasmNotSendSync { type Marker: Marker; const TYPE: ResourceType; fn as_info(&self) -> &ResourceInfo; fn as_info_mut(&mut self) -> &mut ResourceInfo; fn label(&self) -> String { self.as_info().label.clone() } fn ref_count(self: &Arc) -> usize { Arc::strong_count(self) } fn is_unique(self: &Arc) -> bool { self.ref_count() == 1 } fn is_equal(&self, other: &Self) -> bool { self.as_info().id().unzip() == other.as_info().id().unzip() } } /// The status code provided to the buffer mapping callback. /// /// This is very similar to `BufferAccessResult`, except that this is FFI-friendly. #[repr(C)] #[derive(Debug)] pub enum BufferMapAsyncStatus { /// The Buffer is successfully mapped, `get_mapped_range` can be called. /// /// All other variants of this enum represent failures to map the buffer. Success, /// The buffer is already mapped. /// /// While this is treated as an error, it does not prevent mapped range from being accessed. AlreadyMapped, /// Mapping was already requested. MapAlreadyPending, /// An unknown error. Error, /// Mapping was aborted (by unmapping or destroying the buffer before mapping /// happened). Aborted, /// The context is Lost. ContextLost, /// The buffer is in an invalid state. Invalid, /// The range isn't fully contained in the buffer. InvalidRange, /// The range isn't properly aligned. InvalidAlignment, /// Incompatible usage flags. InvalidUsageFlags, } #[derive(Debug)] pub(crate) enum BufferMapState { /// Mapped at creation. Init { ptr: NonNull, stage_buffer: Arc>, needs_flush: bool, }, /// Waiting for GPU to be done before mapping Waiting(BufferPendingMapping), /// Mapped Active { ptr: NonNull, range: hal::MemoryRange, host: HostMap, }, /// Not mapped Idle, } #[cfg(send_sync)] unsafe impl Send for BufferMapState {} #[cfg(send_sync)] unsafe impl Sync for BufferMapState {} #[repr(C)] pub struct BufferMapCallbackC { pub callback: unsafe extern "C" fn(status: BufferMapAsyncStatus, user_data: *mut u8), pub user_data: *mut u8, } #[cfg(send_sync)] unsafe impl Send for BufferMapCallbackC {} #[derive(Debug)] pub struct BufferMapCallback { // We wrap this so creating the enum in the C variant can be unsafe, // allowing our call function to be safe. inner: BufferMapCallbackInner, } #[cfg(send_sync)] type BufferMapCallbackCallback = Box; #[cfg(not(send_sync))] type BufferMapCallbackCallback = Box; enum BufferMapCallbackInner { Rust { callback: BufferMapCallbackCallback }, C { inner: BufferMapCallbackC }, } impl Debug for BufferMapCallbackInner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { BufferMapCallbackInner::Rust { callback: _ } => f.debug_struct("Rust").finish(), BufferMapCallbackInner::C { inner: _ } => f.debug_struct("C").finish(), } } } impl BufferMapCallback { pub fn from_rust(callback: BufferMapCallbackCallback) -> Self { Self { inner: BufferMapCallbackInner::Rust { callback }, } } /// # Safety /// /// - The callback pointer must be valid to call with the provided user_data /// pointer. /// /// - Both pointers must point to valid memory until the callback is /// invoked, which may happen at an unspecified time. pub unsafe fn from_c(inner: BufferMapCallbackC) -> Self { Self { inner: BufferMapCallbackInner::C { inner }, } } pub(crate) fn call(self, result: BufferAccessResult) { match self.inner { BufferMapCallbackInner::Rust { callback } => { callback(result); } // SAFETY: the contract of the call to from_c says that this unsafe is sound. BufferMapCallbackInner::C { inner } => unsafe { let status = match result { Ok(()) => BufferMapAsyncStatus::Success, Err(BufferAccessError::Device(_)) => BufferMapAsyncStatus::ContextLost, Err(BufferAccessError::Invalid) | Err(BufferAccessError::Destroyed) => { BufferMapAsyncStatus::Invalid } Err(BufferAccessError::AlreadyMapped) => BufferMapAsyncStatus::AlreadyMapped, Err(BufferAccessError::MapAlreadyPending) => { BufferMapAsyncStatus::MapAlreadyPending } Err(BufferAccessError::MissingBufferUsage(_)) => { BufferMapAsyncStatus::InvalidUsageFlags } Err(BufferAccessError::UnalignedRange) | Err(BufferAccessError::UnalignedRangeSize { .. }) | Err(BufferAccessError::UnalignedOffset { .. }) => { BufferMapAsyncStatus::InvalidAlignment } Err(BufferAccessError::OutOfBoundsUnderrun { .. }) | Err(BufferAccessError::OutOfBoundsOverrun { .. }) | Err(BufferAccessError::NegativeRange { .. }) => { BufferMapAsyncStatus::InvalidRange } Err(_) => BufferMapAsyncStatus::Error, }; (inner.callback)(status, inner.user_data); }, } } } #[derive(Debug)] pub struct BufferMapOperation { pub host: HostMap, pub callback: Option, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum BufferAccessError { #[error(transparent)] Device(#[from] DeviceError), #[error("Buffer map failed")] Failed, #[error("Buffer is invalid")] Invalid, #[error("Buffer is destroyed")] Destroyed, #[error("Buffer is already mapped")] AlreadyMapped, #[error("Buffer map is pending")] MapAlreadyPending, #[error(transparent)] MissingBufferUsage(#[from] MissingBufferUsageError), #[error("Buffer is not mapped")] NotMapped, #[error( "Buffer map range must start aligned to `MAP_ALIGNMENT` and end to `COPY_BUFFER_ALIGNMENT`" )] UnalignedRange, #[error("Buffer offset invalid: offset {offset} must be multiple of 8")] UnalignedOffset { offset: wgt::BufferAddress }, #[error("Buffer range size invalid: range_size {range_size} must be multiple of 4")] UnalignedRangeSize { range_size: wgt::BufferAddress }, #[error("Buffer access out of bounds: index {index} would underrun the buffer (limit: {min})")] OutOfBoundsUnderrun { index: wgt::BufferAddress, min: wgt::BufferAddress, }, #[error( "Buffer access out of bounds: last index {index} would overrun the buffer (limit: {max})" )] OutOfBoundsOverrun { index: wgt::BufferAddress, max: wgt::BufferAddress, }, #[error("Buffer map range start {start} is greater than end {end}")] NegativeRange { start: wgt::BufferAddress, end: wgt::BufferAddress, }, #[error("Buffer map aborted")] MapAborted, } pub type BufferAccessResult = Result<(), BufferAccessError>; #[derive(Debug)] pub(crate) struct BufferPendingMapping { pub range: Range, pub op: BufferMapOperation, // hold the parent alive while the mapping is active pub _parent_buffer: Arc>, } pub type BufferDescriptor<'a> = wgt::BufferDescriptor>; #[derive(Debug)] pub struct Buffer { pub(crate) raw: Snatchable, pub(crate) device: Arc>, pub(crate) usage: wgt::BufferUsages, pub(crate) size: wgt::BufferAddress, pub(crate) initialization_status: RwLock, pub(crate) sync_mapped_writes: Mutex>, pub(crate) info: ResourceInfo>, pub(crate) map_state: Mutex>, pub(crate) bind_groups: Mutex>>>, } impl Drop for Buffer { fn drop(&mut self) { if let Some(raw) = self.raw.take() { resource_log!("Destroy raw Buffer (dropped) {:?}", self.info.label()); #[cfg(feature = "trace")] if let Some(t) = self.device.trace.lock().as_mut() { t.add(trace::Action::DestroyBuffer(self.info.id())); } unsafe { use hal::Device; self.device.raw().destroy_buffer(raw); } } } } impl Buffer { pub(crate) fn raw(&self, guard: &SnatchGuard) -> Option<&A::Buffer> { self.raw.get(guard) } pub(crate) fn is_destroyed(&self, guard: &SnatchGuard) -> bool { self.raw.get(guard).is_none() } // Note: This must not be called while holding a lock. pub(crate) fn unmap(self: &Arc) -> Result<(), BufferAccessError> { if let Some((mut operation, status)) = self.unmap_inner()? { if let Some(callback) = operation.callback.take() { callback.call(status); } } Ok(()) } fn unmap_inner(self: &Arc) -> Result, BufferAccessError> { use hal::Device; let device = &self.device; let snatch_guard = device.snatchable_lock.read(); let raw_buf = self .raw(&snatch_guard) .ok_or(BufferAccessError::Destroyed)?; let buffer_id = self.info.id(); log::debug!("Buffer {:?} map state -> Idle", buffer_id); match mem::replace(&mut *self.map_state.lock(), resource::BufferMapState::Idle) { resource::BufferMapState::Init { ptr, stage_buffer, needs_flush, } => { #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { let data = trace.make_binary("bin", unsafe { std::slice::from_raw_parts(ptr.as_ptr(), self.size as usize) }); trace.add(trace::Action::WriteBuffer { id: buffer_id, data, range: 0..self.size, queued: true, }); } let _ = ptr; if needs_flush { unsafe { device.raw().flush_mapped_ranges( stage_buffer.raw(&snatch_guard).unwrap(), iter::once(0..self.size), ); } } self.info .use_at(device.active_submission_index.load(Ordering::Relaxed) + 1); let region = wgt::BufferSize::new(self.size).map(|size| hal::BufferCopy { src_offset: 0, dst_offset: 0, size, }); let transition_src = hal::BufferBarrier { buffer: stage_buffer.raw(&snatch_guard).unwrap(), usage: hal::BufferUses::MAP_WRITE..hal::BufferUses::COPY_SRC, }; let transition_dst = hal::BufferBarrier { buffer: raw_buf, usage: hal::BufferUses::empty()..hal::BufferUses::COPY_DST, }; let mut pending_writes = device.pending_writes.lock(); let pending_writes = pending_writes.as_mut().unwrap(); let encoder = pending_writes.activate(); unsafe { encoder.transition_buffers( iter::once(transition_src).chain(iter::once(transition_dst)), ); if self.size > 0 { encoder.copy_buffer_to_buffer( stage_buffer.raw(&snatch_guard).unwrap(), raw_buf, region.into_iter(), ); } } pending_writes.consume_temp(queue::TempResource::Buffer(stage_buffer)); pending_writes.dst_buffers.insert(buffer_id, self.clone()); } resource::BufferMapState::Idle => { return Err(BufferAccessError::NotMapped); } resource::BufferMapState::Waiting(pending) => { return Ok(Some((pending.op, Err(BufferAccessError::MapAborted)))); } resource::BufferMapState::Active { ptr, range, host } => { if host == HostMap::Write { #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { let size = range.end - range.start; let data = trace.make_binary("bin", unsafe { std::slice::from_raw_parts(ptr.as_ptr(), size as usize) }); trace.add(trace::Action::WriteBuffer { id: buffer_id, data, range: range.clone(), queued: false, }); } let _ = (ptr, range); } unsafe { device .raw() .unmap_buffer(raw_buf) .map_err(DeviceError::from)? }; } } Ok(None) } pub(crate) fn destroy(self: &Arc) -> Result<(), DestroyError> { let device = &self.device; let buffer_id = self.info.id(); #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::FreeBuffer(buffer_id)); } let temp = { let snatch_guard = device.snatchable_lock.write(); let raw = match self.raw.snatch(snatch_guard) { Some(raw) => raw, None => { return Err(resource::DestroyError::AlreadyDestroyed); } }; let bind_groups = { let mut guard = self.bind_groups.lock(); std::mem::take(&mut *guard) }; queue::TempResource::DestroyedBuffer(Arc::new(DestroyedBuffer { raw: Some(raw), device: Arc::clone(&self.device), submission_index: self.info.submission_index(), id: self.info.id.unwrap(), tracker_index: self.info.tracker_index(), label: self.info.label.clone(), bind_groups, })) }; let mut pending_writes = device.pending_writes.lock(); let pending_writes = pending_writes.as_mut().unwrap(); if pending_writes.dst_buffers.contains_key(&buffer_id) { pending_writes.temp_resources.push(temp); } else { let last_submit_index = self.info.submission_index(); device .lock_life() .schedule_resource_destruction(temp, last_submit_index); } Ok(()) } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum 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), } impl Resource for Buffer { const TYPE: ResourceType = "Buffer"; type Marker = crate::id::markers::Buffer; fn as_info(&self) -> &ResourceInfo { &self.info } fn as_info_mut(&mut self) -> &mut ResourceInfo { &mut self.info } } /// A buffer that has been marked as destroyed and is staged for actual deletion soon. #[derive(Debug)] pub struct DestroyedBuffer { raw: Option, device: Arc>, label: String, pub(crate) id: BufferId, pub(crate) tracker_index: TrackerIndex, pub(crate) submission_index: u64, bind_groups: Vec>>, } impl DestroyedBuffer { pub fn label(&self) -> &dyn Debug { if !self.label.is_empty() { return &self.label; } &self.id } } impl Drop for DestroyedBuffer { fn drop(&mut self) { let mut deferred = self.device.deferred_destroy.lock(); for bind_group in self.bind_groups.drain(..) { deferred.push(DeferredDestroy::BindGroup(bind_group)); } drop(deferred); if let Some(raw) = self.raw.take() { resource_log!("Destroy raw Buffer (destroyed) {:?}", self.label()); #[cfg(feature = "trace")] if let Some(t) = self.device.trace.lock().as_mut() { t.add(trace::Action::DestroyBuffer(self.id)); } unsafe { use hal::Device; self.device.raw().destroy_buffer(raw); } } } } /// 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`]: Global::queue_create_staging_buffer /// [`queue_write_staging_buffer`]: Global::queue_write_staging_buffer /// [`queue_write_texture`]: Global::queue_write_texture /// [`Device::pending_writes`]: crate::device::Device #[derive(Debug)] pub struct StagingBuffer { pub(crate) raw: Mutex>, pub(crate) device: Arc>, pub(crate) size: wgt::BufferAddress, pub(crate) is_coherent: bool, pub(crate) info: ResourceInfo>, } impl Drop for StagingBuffer { fn drop(&mut self) { if let Some(raw) = self.raw.lock().take() { resource_log!("Destroy raw StagingBuffer {:?}", self.info.label()); unsafe { use hal::Device; self.device.raw().destroy_buffer(raw); } } } } impl Resource for StagingBuffer { const TYPE: ResourceType = "StagingBuffer"; type Marker = crate::id::markers::StagingBuffer; fn as_info(&self) -> &ResourceInfo { &self.info } fn as_info_mut(&mut self) -> &mut ResourceInfo { &mut self.info } fn label(&self) -> String { String::from("") } } pub type TextureDescriptor<'a> = wgt::TextureDescriptor, Vec>; #[derive(Debug)] pub(crate) enum TextureInner { Native { raw: A::Texture, }, Surface { raw: Option, parent_id: SurfaceId, }, } impl TextureInner { pub fn raw(&self) -> Option<&A::Texture> { match self { Self::Native { raw } => Some(raw), Self::Surface { raw: Some(tex), .. } => Some(tex.borrow()), _ => None, } } } #[derive(Debug)] pub enum TextureClearMode { BufferCopy, // View for clear via RenderPass for every subsurface (mip/layer/slice) RenderPass { clear_views: SmallVec<[Option; 1]>, is_color: bool, }, Surface { clear_view: Option, }, // 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)] pub struct Texture { pub(crate) inner: Snatchable>, pub(crate) device: Arc>, pub(crate) desc: wgt::TextureDescriptor<(), Vec>, pub(crate) hal_usage: hal::TextureUses, pub(crate) format_features: wgt::TextureFormatFeatures, pub(crate) initialization_status: RwLock, pub(crate) full_range: TextureSelector, pub(crate) info: ResourceInfo>, pub(crate) clear_mode: RwLock>, pub(crate) views: Mutex>>>, pub(crate) bind_groups: Mutex>>>, } impl Drop for Texture { fn drop(&mut self) { resource_log!("Destroy raw Texture {:?}", self.info.label()); use hal::Device; let mut clear_mode = self.clear_mode.write(); let clear_mode = &mut *clear_mode; match *clear_mode { TextureClearMode::Surface { ref mut clear_view, .. } => { if let Some(view) = clear_view.take() { unsafe { self.device.raw().destroy_texture_view(view); } } } TextureClearMode::RenderPass { ref mut clear_views, .. } => { clear_views.iter_mut().for_each(|clear_view| { if let Some(view) = clear_view.take() { unsafe { self.device.raw().destroy_texture_view(view); } } }); } _ => {} }; if let Some(TextureInner::Native { raw }) = self.inner.take() { #[cfg(feature = "trace")] if let Some(t) = self.device.trace.lock().as_mut() { t.add(trace::Action::DestroyTexture(self.info.id())); } unsafe { self.device.raw().destroy_texture(raw); } } } } impl Texture { pub(crate) fn raw<'a>(&'a self, snatch_guard: &'a SnatchGuard) -> Option<&'a A::Texture> { self.inner.get(snatch_guard)?.raw() } pub(crate) fn is_destroyed(&self, guard: &SnatchGuard) -> bool { self.inner.get(guard).is_none() } pub(crate) fn inner_mut<'a>( &'a self, guard: &mut ExclusiveSnatchGuard, ) -> Option<&'a mut TextureInner> { self.inner.get_mut(guard) } pub(crate) fn get_clear_view<'a>( clear_mode: &'a TextureClearMode, desc: &'a wgt::TextureDescriptor<(), Vec>, mip_level: u32, depth_or_layer: u32, ) -> &'a A::TextureView { match *clear_mode { TextureClearMode::BufferCopy => { panic!("Given texture is cleared with buffer copies, not render passes") } TextureClearMode::None => { panic!("Given texture can't be cleared") } TextureClearMode::Surface { ref clear_view, .. } => clear_view.as_ref().unwrap(), TextureClearMode::RenderPass { ref clear_views, .. } => { let index = if desc.dimension == wgt::TextureDimension::D3 { (0..mip_level).fold(0, |acc, mip| { acc + (desc.size.depth_or_array_layers >> mip).max(1) }) } else { mip_level * desc.size.depth_or_array_layers } + depth_or_layer; clear_views[index as usize].as_ref().unwrap() } } } pub(crate) fn destroy(self: &Arc) -> Result<(), DestroyError> { let device = &self.device; let texture_id = self.info.id(); #[cfg(feature = "trace")] if let Some(ref mut trace) = *device.trace.lock() { trace.add(trace::Action::FreeTexture(texture_id)); } let temp = { let snatch_guard = device.snatchable_lock.write(); let raw = match self.inner.snatch(snatch_guard) { Some(TextureInner::Native { raw }) => raw, Some(TextureInner::Surface { .. }) => { return Ok(()); } None => { return Err(resource::DestroyError::AlreadyDestroyed); } }; let views = { let mut guard = self.views.lock(); std::mem::take(&mut *guard) }; let bind_groups = { let mut guard = self.bind_groups.lock(); std::mem::take(&mut *guard) }; queue::TempResource::DestroyedTexture(Arc::new(DestroyedTexture { raw: Some(raw), views, bind_groups, device: Arc::clone(&self.device), tracker_index: self.info.tracker_index(), submission_index: self.info.submission_index(), id: self.info.id.unwrap(), label: self.info.label.clone(), })) }; let mut pending_writes = device.pending_writes.lock(); let pending_writes = pending_writes.as_mut().unwrap(); if pending_writes.dst_textures.contains_key(&texture_id) { pending_writes.temp_resources.push(temp); } else { let last_submit_index = self.info.submission_index(); device .lock_life() .schedule_resource_destruction(temp, last_submit_index); } Ok(()) } } impl Global { /// # Safety /// /// - The raw texture handle must not be manually destroyed pub unsafe fn texture_as_hal)>( &self, id: TextureId, hal_texture_callback: F, ) { profiling::scope!("Texture::as_hal"); let hub = A::hub(self); let texture_opt = { hub.textures.try_get(id).ok().flatten() }; let texture = texture_opt.as_ref().unwrap(); let snatch_guard = texture.device.snatchable_lock.read(); let hal_texture = texture.raw(&snatch_guard); hal_texture_callback(hal_texture); } /// # Safety /// /// - The raw adapter handle must not be manually destroyed pub unsafe fn adapter_as_hal) -> R, R>( &self, id: AdapterId, hal_adapter_callback: F, ) -> R { profiling::scope!("Adapter::as_hal"); let hub = A::hub(self); let adapter = hub.adapters.try_get(id).ok().flatten(); let hal_adapter = adapter.as_ref().map(|adapter| &adapter.raw.adapter); hal_adapter_callback(hal_adapter) } /// # Safety /// /// - The raw device handle must not be manually destroyed pub unsafe fn device_as_hal) -> R, R>( &self, id: DeviceId, hal_device_callback: F, ) -> R { profiling::scope!("Device::as_hal"); let hub = A::hub(self); let device = hub.devices.try_get(id).ok().flatten(); let hal_device = device.as_ref().map(|device| device.raw()); hal_device_callback(hal_device) } /// # Safety /// /// - The raw fence handle must not be manually destroyed pub unsafe fn device_fence_as_hal) -> R, R>( &self, id: DeviceId, hal_fence_callback: F, ) -> R { profiling::scope!("Device::fence_as_hal"); let hub = A::hub(self); let device = hub.devices.try_get(id).ok().flatten(); let hal_fence = device.as_ref().map(|device| device.fence.read()); hal_fence_callback(hal_fence.as_deref().unwrap().as_ref()) } /// # Safety /// - The raw surface handle must not be manually destroyed pub unsafe fn surface_as_hal) -> R, R>( &self, id: SurfaceId, hal_surface_callback: F, ) -> R { profiling::scope!("Surface::as_hal"); let surface = self.surfaces.get(id).ok(); let hal_surface = surface.as_ref().and_then(|surface| A::get_surface(surface)); hal_surface_callback(hal_surface) } } /// A texture that has been marked as destroyed and is staged for actual deletion soon. #[derive(Debug)] pub struct DestroyedTexture { raw: Option, views: Vec>>, bind_groups: Vec>>, device: Arc>, label: String, pub(crate) id: TextureId, pub(crate) tracker_index: TrackerIndex, pub(crate) submission_index: u64, } impl DestroyedTexture { pub fn label(&self) -> &dyn Debug { if !self.label.is_empty() { return &self.label; } &self.id } } impl Drop for DestroyedTexture { fn drop(&mut self) { let device = &self.device; let mut deferred = device.deferred_destroy.lock(); for view in self.views.drain(..) { deferred.push(DeferredDestroy::TextureView(view)); } for bind_group in self.bind_groups.drain(..) { deferred.push(DeferredDestroy::BindGroup(bind_group)); } drop(deferred); if let Some(raw) = self.raw.take() { resource_log!("Destroy raw Texture (destroyed) {:?}", self.label()); #[cfg(feature = "trace")] if let Some(t) = self.device.trace.lock().as_mut() { t.add(trace::Action::DestroyTexture(self.id)); } unsafe { use hal::Device; self.device.raw().destroy_texture(raw); } } } } #[derive(Clone, Copy, Debug)] pub enum TextureErrorDimension { X, Y, Z, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum 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] pub enum CreateTextureError { #[error(transparent)] Device(#[from] DeviceError), #[error(transparent)] CreateTextureView(#[from] CreateTextureViewError), #[error("Invalid usage flags {0:?}")] InvalidUsage(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:?}{}", 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, Vec), #[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), } impl Resource for Texture { const TYPE: ResourceType = "Texture"; type Marker = crate::id::markers::Texture; fn as_info(&self) -> &ResourceInfo { &self.info } fn as_info_mut(&mut self) -> &mut ResourceInfo { &mut self.info } } impl Borrow for Texture { fn borrow(&self) -> &TextureSelector { &self.full_range } } /// Describes a [`TextureView`]. #[derive(Clone, Debug, Default, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct 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, /// 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, /// Range within the texture that is accessible via this view. pub range: wgt::ImageSubresourceRange, } #[derive(Debug)] pub(crate) struct HalTextureViewDescriptor { pub texture_format: wgt::TextureFormat, pub format: wgt::TextureFormat, pub dimension: wgt::TextureViewDimension, pub range: wgt::ImageSubresourceRange, } impl HalTextureViewDescriptor { pub fn aspects(&self) -> hal::FormatAspects { hal::FormatAspects::new(self.texture_format, self.range.aspect) } } #[derive(Debug, Copy, Clone, Error)] pub enum 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)] pub struct TextureView { pub(crate) raw: Snatchable, // if it's a surface texture - it's none pub(crate) parent: Arc>, pub(crate) device: Arc>, //TODO: store device_id for quick access? 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, pub(crate) samples: u32, pub(crate) selector: TextureSelector, pub(crate) info: ResourceInfo>, } impl Drop for TextureView { fn drop(&mut self) { if let Some(raw) = self.raw.take() { resource_log!("Destroy raw TextureView {:?}", self.info.label()); #[cfg(feature = "trace")] if let Some(t) = self.device.trace.lock().as_mut() { t.add(trace::Action::DestroyTextureView(self.info.id())); } unsafe { use hal::Device; self.device.raw().destroy_texture_view(raw); } } } } impl TextureView { pub(crate) fn raw<'a>(&'a self, snatch_guard: &'a SnatchGuard) -> Option<&'a A::TextureView> { self.raw.get(snatch_guard) } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateTextureViewError { #[error("Parent texture is invalid or destroyed")] InvalidTexture, #[error("Not enough memory left to create texture view")] OutOfMemory, #[error("Invalid texture view dimension `{view:?}` with texture of dimension `{texture:?}`")] InvalidTextureViewDimension { view: wgt::TextureViewDimension, texture: wgt::TextureDimension, }, #[error("Invalid texture view dimension `{0:?}` of a multisampled texture")] InvalidMultisampledTextureViewDimension(wgt::TextureViewDimension), #[error("Invalid texture depth `{depth}` for texture view of dimension `Cubemap`. Cubemap views must use images of size 6.")] InvalidCubemapTextureDepth { depth: u32 }, #[error("Invalid texture depth `{depth}` for texture view of dimension `CubemapArray`. Cubemap views must use images with sizes which are 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 mip level count + base mip level {requested} must be <= Texture mip level count {total}" )] TooManyMipLevels { requested: u32, total: u32 }, #[error("TextureView array layer count + base array layer {requested} must be <= Texture depth/array layer count {total}")] TooManyArrayLayers { requested: 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 in the source texture format {texture_format:?}")] InvalidAspect { texture_format: wgt::TextureFormat, requested_aspect: wgt::TextureAspect, }, #[error("Unable to view texture {texture:?} as {view:?}")] FormatReinterpretation { texture: wgt::TextureFormat, view: wgt::TextureFormat, }, } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum TextureViewDestroyError {} impl Resource for TextureView { const TYPE: ResourceType = "TextureView"; type Marker = crate::id::markers::TextureView; fn as_info(&self) -> &ResourceInfo { &self.info } fn as_info_mut(&mut self) -> &mut ResourceInfo { &mut self.info } } /// Describes a [`Sampler`] #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct 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::FilterMode, /// 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, /// 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, } #[derive(Debug)] pub struct Sampler { pub(crate) raw: Option, pub(crate) device: Arc>, pub(crate) info: ResourceInfo, /// `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(&mut self) { resource_log!("Destroy raw Sampler {:?}", self.info.label()); if let Some(raw) = self.raw.take() { #[cfg(feature = "trace")] if let Some(t) = self.device.trace.lock().as_mut() { t.add(trace::Action::DestroySampler(self.info.id())); } unsafe { use hal::Device; self.device.raw().destroy_sampler(raw); } } } } impl Sampler { pub(crate) fn raw(&self) -> &A::Sampler { self.raw.as_ref().unwrap() } } #[derive(Copy, Clone)] pub enum SamplerFilterErrorType { MagFilter, MinFilter, MipmapFilter, } impl Debug for SamplerFilterErrorType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { SamplerFilterErrorType::MagFilter => write!(f, "magFilter"), SamplerFilterErrorType::MinFilter => write!(f, "minFilter"), SamplerFilterErrorType::MipmapFilter => write!(f, "mipmapFilter"), } } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum 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("Cannot create any more samplers")] TooManyObjects, /// AddressMode::ClampToBorder requires feature ADDRESS_MODE_CLAMP_TO_BORDER. #[error(transparent)] MissingFeatures(#[from] MissingFeatures), } impl Resource for Sampler { const TYPE: ResourceType = "Sampler"; type Marker = crate::id::markers::Sampler; fn as_info(&self) -> &ResourceInfo { &self.info } fn as_info_mut(&mut self) -> &mut ResourceInfo { &mut self.info } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum 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), } pub type QuerySetDescriptor<'a> = wgt::QuerySetDescriptor>; #[derive(Debug)] pub struct QuerySet { pub(crate) raw: Option, pub(crate) device: Arc>, pub(crate) info: ResourceInfo, pub(crate) desc: wgt::QuerySetDescriptor<()>, } impl Drop for QuerySet { fn drop(&mut self) { resource_log!("Destroy raw QuerySet {:?}", self.info.label()); if let Some(raw) = self.raw.take() { #[cfg(feature = "trace")] if let Some(t) = self.device.trace.lock().as_mut() { t.add(trace::Action::DestroyQuerySet(self.info.id())); } unsafe { use hal::Device; self.device.raw().destroy_query_set(raw); } } } } impl Resource for QuerySet { const TYPE: ResourceType = "QuerySet"; type Marker = crate::id::markers::QuerySet; fn as_info(&self) -> &ResourceInfo { &self.info } fn as_info_mut(&mut self) -> &mut ResourceInfo { &mut self.info } } impl QuerySet { pub(crate) fn raw(&self) -> &A::QuerySet { self.raw.as_ref().unwrap() } } #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum DestroyError { #[error("Resource is invalid")] Invalid, #[error("Resource is already destroyed")] AlreadyDestroyed, }