diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
commit | 8dd16259287f58f9273002717ec4d27e97127719 (patch) | |
tree | 3863e62a53829a84037444beab3abd4ed9dfc7d0 /third_party/rust/wgpu-core/src/device | |
parent | Releasing progress-linux version 126.0.1-1~progress7.99u1. (diff) | |
download | firefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz firefox-8dd16259287f58f9273002717ec4d27e97127719.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/wgpu-core/src/device')
-rw-r--r-- | third_party/rust/wgpu-core/src/device/any_device.rs | 2 | ||||
-rw-r--r-- | third_party/rust/wgpu-core/src/device/bgl.rs | 2 | ||||
-rw-r--r-- | third_party/rust/wgpu-core/src/device/global.rs | 77 | ||||
-rw-r--r-- | third_party/rust/wgpu-core/src/device/life.rs | 123 | ||||
-rw-r--r-- | third_party/rust/wgpu-core/src/device/mod.rs | 37 | ||||
-rw-r--r-- | third_party/rust/wgpu-core/src/device/queue.rs | 169 | ||||
-rw-r--r-- | third_party/rust/wgpu-core/src/device/resource.rs | 544 |
7 files changed, 500 insertions, 454 deletions
diff --git a/third_party/rust/wgpu-core/src/device/any_device.rs b/third_party/rust/wgpu-core/src/device/any_device.rs index 693155a753..9e459c1a94 100644 --- a/third_party/rust/wgpu-core/src/device/any_device.rs +++ b/third_party/rust/wgpu-core/src/device/any_device.rs @@ -34,7 +34,7 @@ impl AnyDevice { unsafe fn drop_glue<A: HalApi>(ptr: *mut ()) { // Drop the arc this instance is holding. unsafe { - _ = Arc::from_raw(ptr.cast::<A::Surface>()); + _ = Arc::from_raw(ptr.cast::<A::Device>()); } } diff --git a/third_party/rust/wgpu-core/src/device/bgl.rs b/third_party/rust/wgpu-core/src/device/bgl.rs index d606f049a3..911ac8a435 100644 --- a/third_party/rust/wgpu-core/src/device/bgl.rs +++ b/third_party/rust/wgpu-core/src/device/bgl.rs @@ -58,7 +58,7 @@ impl EntryMap { assert!(self.sorted); } - /// Create a new [`BindGroupLayoutEntryMap`] from a slice of [`wgt::BindGroupLayoutEntry`]s. + /// Create a new [`EntryMap`] from a slice of [`wgt::BindGroupLayoutEntry`]s. /// /// Errors if there are duplicate bindings or if any binding index is greater than /// the device's limits. diff --git a/third_party/rust/wgpu-core/src/device/global.rs b/third_party/rust/wgpu-core/src/device/global.rs index 0c97e1b504..be524840b8 100644 --- a/third_party/rust/wgpu-core/src/device/global.rs +++ b/third_party/rust/wgpu-core/src/device/global.rs @@ -11,6 +11,7 @@ use crate::{ id::{self, AdapterId, DeviceId, QueueId, SurfaceId}, init_tracker::TextureInitTracker, instance::{self, Adapter, Surface}, + lock::{rank, RwLock}, pipeline, present, resource::{self, BufferAccessResult}, resource::{BufferAccessError, BufferMapOperation, CreateBufferError, Resource}, @@ -20,7 +21,6 @@ use crate::{ use arrayvec::ArrayVec; use hal::Device as _; -use parking_lot::RwLock; use wgt::{BufferAddress, TextureFormat}; @@ -257,7 +257,7 @@ impl Global { hal::BufferUses::COPY_DST }; - let (id, resource) = fid.assign(buffer); + let (id, resource) = fid.assign(Arc::new(buffer)); api_log!("Device::create_buffer({desc:?}) -> {id:?}"); device @@ -572,7 +572,7 @@ impl Global { Err(error) => break error, }; - let (id, resource) = fid.assign(texture); + let (id, resource) = fid.assign(Arc::new(texture)); api_log!("Device::create_texture({desc:?}) -> {id:?}"); device @@ -643,10 +643,12 @@ impl Global { texture.hal_usage |= hal::TextureUses::COPY_DST; } - texture.initialization_status = - RwLock::new(TextureInitTracker::new(desc.mip_level_count, 0)); + texture.initialization_status = RwLock::new( + rank::TEXTURE_INITIALIZATION_STATUS, + TextureInitTracker::new(desc.mip_level_count, 0), + ); - let (id, resource) = fid.assign(texture); + let (id, resource) = fid.assign(Arc::new(texture)); api_log!("Device::create_texture({desc:?}) -> {id:?}"); device @@ -699,7 +701,7 @@ impl Global { let buffer = device.create_buffer_from_hal(hal_buffer, desc); - let (id, buffer) = fid.assign(buffer); + let (id, buffer) = fid.assign(Arc::new(buffer)); api_log!("Device::create_buffer -> {id:?}"); device @@ -818,7 +820,7 @@ impl Global { Err(e) => break e, }; - let (id, resource) = fid.assign(view); + let (id, resource) = fid.assign(Arc::new(view)); { let mut views = texture.views.lock(); @@ -900,7 +902,7 @@ impl Global { Err(e) => break e, }; - let (id, resource) = fid.assign(sampler); + let (id, resource) = fid.assign(Arc::new(sampler)); api_log!("Device::create_sampler -> {id:?}"); device.trackers.lock().samplers.insert_single(resource); @@ -982,7 +984,7 @@ impl Global { let bgl = device.create_bind_group_layout(&desc.label, entry_map, bgl::Origin::Pool)?; - let (id_inner, arc) = fid.take().unwrap().assign(bgl); + let (id_inner, arc) = fid.take().unwrap().assign(Arc::new(bgl)); id = Some(id_inner); Ok(arc) @@ -1063,7 +1065,7 @@ impl Global { Err(e) => break e, }; - let (id, _) = fid.assign(layout); + let (id, _) = fid.assign(Arc::new(layout)); api_log!("Device::create_pipeline_layout -> {id:?}"); return (id, None); }; @@ -1130,7 +1132,7 @@ impl Global { Err(e) => break e, }; - let (id, resource) = fid.assign(bind_group); + let (id, resource) = fid.assign(Arc::new(bind_group)); let weak_ref = Arc::downgrade(&resource); for range in &resource.used_texture_ranges { @@ -1170,6 +1172,20 @@ impl Global { } } + /// Create a shader module with the given `source`. + /// + /// <div class="warning"> + // NOTE: Keep this in sync with `naga::front::wgsl::parse_str`! + // NOTE: Keep this in sync with `wgpu::Device::create_shader_module`! + /// + /// This function may consume a lot of stack space. Compiler-enforced limits for parsing + /// recursion exist; if shader compilation runs into them, it will return an error gracefully. + /// However, on some build profiles and platforms, the default stack size for a thread may be + /// exceeded before this limit is reached during parsing. Callers should ensure that there is + /// enough stack space for this, particularly if calls to this method are exposed to user + /// input. + /// + /// </div> pub fn device_create_shader_module<A: HalApi>( &self, device_id: DeviceId, @@ -1231,7 +1247,7 @@ impl Global { Err(e) => break e, }; - let (id, _) = fid.assign(shader); + let (id, _) = fid.assign(Arc::new(shader)); api_log!("Device::create_shader_module -> {id:?}"); return (id, None); }; @@ -1288,7 +1304,7 @@ impl Global { Ok(shader) => shader, Err(e) => break e, }; - let (id, _) = fid.assign(shader); + let (id, _) = fid.assign(Arc::new(shader)); api_log!("Device::create_shader_module_spirv -> {id:?}"); return (id, None); }; @@ -1320,7 +1336,9 @@ impl Global { profiling::scope!("Device::create_command_encoder"); let hub = A::hub(self); - let fid = hub.command_buffers.prepare(id_in.map(|id| id.transmute())); + let fid = hub + .command_buffers + .prepare(id_in.map(|id| id.into_command_buffer_id())); let error = loop { let device = match hub.devices.get(device_id) { @@ -1335,9 +1353,6 @@ impl Global { }; let encoder = match device .command_allocator - .lock() - .as_mut() - .unwrap() .acquire_encoder(device.raw(), queue.raw.as_ref().unwrap()) { Ok(raw) => raw, @@ -1353,13 +1368,13 @@ impl Global { .map(|s| s.to_string()), ); - let (id, _) = fid.assign(command_buffer); + let (id, _) = fid.assign(Arc::new(command_buffer)); api_log!("Device::create_command_encoder -> {id:?}"); - return (id.transmute(), None); + return (id.into_command_encoder_id(), None); }; let id = fid.assign_error(desc.label.borrow_or_default()); - (id.transmute(), Some(error)) + (id.into_command_encoder_id(), Some(error)) } pub fn command_buffer_label<A: HalApi>(&self, id: id::CommandBufferId) -> String { @@ -1374,7 +1389,7 @@ impl Global { if let Some(cmd_buf) = hub .command_buffers - .unregister(command_encoder_id.transmute()) + .unregister(command_encoder_id.into_command_buffer_id()) { cmd_buf.data.lock().as_mut().unwrap().encoder.discard(); cmd_buf @@ -1386,7 +1401,7 @@ impl Global { pub fn command_buffer_drop<A: HalApi>(&self, command_buffer_id: id::CommandBufferId) { profiling::scope!("CommandBuffer::drop"); api_log!("CommandBuffer::drop {command_buffer_id:?}"); - self.command_encoder_drop::<A>(command_buffer_id.transmute()) + self.command_encoder_drop::<A>(command_buffer_id.into_command_encoder_id()) } pub fn device_create_render_bundle_encoder( @@ -1446,7 +1461,7 @@ impl Global { Err(e) => break e, }; - let (id, resource) = fid.assign(render_bundle); + let (id, resource) = fid.assign(Arc::new(render_bundle)); api_log!("RenderBundleEncoder::finish -> {id:?}"); device.trackers.lock().bundles.insert_single(resource); return (id, None); @@ -1509,7 +1524,7 @@ impl Global { Err(err) => break err, }; - let (id, resource) = fid.assign(query_set); + let (id, resource) = fid.assign(Arc::new(query_set)); api_log!("Device::create_query_set -> {id:?}"); device.trackers.lock().query_sets.insert_single(resource); @@ -1587,7 +1602,7 @@ impl Global { Err(e) => break e, }; - let (id, resource) = fid.assign(pipeline); + let (id, resource) = fid.assign(Arc::new(pipeline)); api_log!("Device::create_render_pipeline -> {id:?}"); device @@ -1720,7 +1735,7 @@ impl Global { Err(e) => break e, }; - let (id, resource) = fid.assign(pipeline); + let (id, resource) = fid.assign(Arc::new(pipeline)); api_log!("Device::create_compute_pipeline -> {id:?}"); device @@ -1956,7 +1971,7 @@ impl Global { }; let caps = unsafe { - let suf = A::get_surface(surface); + let suf = A::surface_as_hal(surface); let adapter = &device.adapter; match adapter.raw.adapter.surface_capabilities(suf.unwrap()) { Some(caps) => caps, @@ -2018,7 +2033,6 @@ impl Global { // Wait for all work to finish before configuring the surface. let snatch_guard = device.snatchable_lock.read(); let fence = device.fence.read(); - let fence = fence.as_ref().unwrap(); match device.maintain(fence, wgt::Maintain::Wait, snatch_guard) { Ok((closures, _)) => { user_callbacks = closures; @@ -2042,7 +2056,7 @@ impl Global { // https://github.com/gfx-rs/wgpu/issues/4105 match unsafe { - A::get_surface(surface) + A::surface_as_hal(surface) .unwrap() .configure(device.raw(), &hal_config) } { @@ -2107,7 +2121,7 @@ impl Global { .map_err(|_| DeviceError::Invalid)?; if let wgt::Maintain::WaitForSubmissionIndex(submission_index) = maintain { - if submission_index.queue_id != device_id.transmute() { + if submission_index.queue_id != device_id.into_queue_id() { return Err(WaitIdleError::WrongSubmissionIndex( submission_index.queue_id, device_id, @@ -2131,7 +2145,6 @@ impl Global { ) -> Result<DevicePoll, WaitIdleError> { let snatch_guard = device.snatchable_lock.read(); let fence = device.fence.read(); - let fence = fence.as_ref().unwrap(); let (closures, queue_empty) = device.maintain(fence, maintain, snatch_guard)?; // Some deferred destroys are scheduled in maintain so run this right after diff --git a/third_party/rust/wgpu-core/src/device/life.rs b/third_party/rust/wgpu-core/src/device/life.rs index af345015df..0df580e6e6 100644 --- a/third_party/rust/wgpu-core/src/device/life.rs +++ b/third_party/rust/wgpu-core/src/device/life.rs @@ -7,6 +7,7 @@ use crate::{ }, hal_api::HalApi, id, + lock::Mutex, pipeline::{ComputePipeline, RenderPipeline}, resource::{ self, Buffer, DestroyedBuffer, DestroyedTexture, QuerySet, Resource, Sampler, @@ -18,12 +19,10 @@ use crate::{ }; use smallvec::SmallVec; -use parking_lot::Mutex; use std::sync::Arc; use thiserror::Error; /// A struct that keeps lists of resources that are no longer needed by the user. -#[derive(Default)] pub(crate) struct ResourceMaps<A: HalApi> { pub buffers: FastHashMap<TrackerIndex, Arc<Buffer<A>>>, pub staging_buffers: FastHashMap<TrackerIndex, Arc<StagingBuffer<A>>>, @@ -94,7 +93,7 @@ impl<A: HalApi> ResourceMaps<A> { destroyed_textures.clear(); } - pub(crate) fn extend(&mut self, mut other: Self) { + pub(crate) fn extend(&mut self, other: &mut Self) { let ResourceMaps { buffers, staging_buffers, @@ -128,7 +127,37 @@ impl<A: HalApi> ResourceMaps<A> { } } -/// Resources used by a queue submission, and work to be done once it completes. +/// A command submitted to the GPU for execution. +/// +/// ## Keeping resources alive while the GPU is using them +/// +/// [`wgpu_hal`] requires that, when a command is submitted to a queue, all the +/// resources it uses must remain alive until it has finished executing. +/// +/// The natural way to satisfy this would be for `ActiveSubmission` to hold +/// strong references to all the resources used by its commands. However, that +/// would entail dropping those strong references every time a queue submission +/// finishes, adjusting the reference counts of all the resources it used. This +/// is usually needless work: it's rare for the active submission queue to be +/// the final reference to an object. Usually the user is still holding on to +/// it. +/// +/// To avoid this, an `ActiveSubmission` does not initially hold any strong +/// references to its commands' resources. Instead, each resource tracks the +/// most recent submission index at which it has been used in +/// [`ResourceInfo::submission_index`]. When the user drops a resource, if the +/// submission in which it was last used is still present in the device's queue, +/// we add the resource to [`ActiveSubmission::last_resources`]. Finally, when +/// this `ActiveSubmission` is dequeued and dropped in +/// [`LifetimeTracker::triage_submissions`], we drop `last_resources` along with +/// it. Thus, unless a resource is dropped by the user, it doesn't need to be +/// touched at all when processing completed work. +/// +/// However, it's not clear that this is effective. See [#5560]. +/// +/// [`wgpu_hal`]: hal +/// [`ResourceInfo::submission_index`]: crate::resource::ResourceInfo +/// [#5560]: https://github.com/gfx-rs/wgpu/issues/5560 struct ActiveSubmission<A: HalApi> { /// The index of the submission we track. /// @@ -150,6 +179,18 @@ struct ActiveSubmission<A: HalApi> { /// Buffers to be mapped once this submission has completed. mapped: Vec<Arc<Buffer<A>>>, + /// Command buffers used by this submission, and the encoder that owns them. + /// + /// [`wgpu_hal::Queue::submit`] requires the submitted command buffers to + /// remain alive until the submission has completed execution. Command + /// encoders double as allocation pools for command buffers, so holding them + /// here and cleaning them up in [`LifetimeTracker::triage_submissions`] + /// satisfies that requirement. + /// + /// Once this submission has completed, the command buffers are reset and + /// the command encoder is recycled. + /// + /// [`wgpu_hal::Queue::submit`]: hal::Queue::submit encoders: Vec<EncoderInFlight<A>>, /// List of queue "on_submitted_work_done" closures to be called once this @@ -330,28 +371,25 @@ impl<A: HalApi> LifetimeTracker<A> { /// /// Assume that all submissions up through `last_done` have completed. /// - /// - Buffers used by those submissions are now ready to map, if - /// requested. Add any buffers in the submission's [`mapped`] list to - /// [`self.ready_to_map`], where [`LifetimeTracker::handle_mapping`] will find - /// them. + /// - Buffers used by those submissions are now ready to map, if requested. + /// Add any buffers in the submission's [`mapped`] list to + /// [`self.ready_to_map`], where [`LifetimeTracker::handle_mapping`] + /// will find them. /// /// - Resources whose final use was in those submissions are now ready to - /// free. Add any resources in the submission's [`last_resources`] table - /// to [`self.free_resources`], where [`LifetimeTracker::cleanup`] will find - /// them. + /// free. Dropping the submission's [`last_resources`] table does so. /// /// Return a list of [`SubmittedWorkDoneClosure`]s to run. /// /// [`mapped`]: ActiveSubmission::mapped /// [`self.ready_to_map`]: LifetimeTracker::ready_to_map /// [`last_resources`]: ActiveSubmission::last_resources - /// [`self.free_resources`]: LifetimeTracker::free_resources /// [`SubmittedWorkDoneClosure`]: crate::device::queue::SubmittedWorkDoneClosure #[must_use] pub fn triage_submissions( &mut self, last_done: SubmissionIndex, - command_allocator: &mut super::CommandAllocator<A>, + command_allocator: &crate::command::CommandAllocator<A>, ) -> SmallVec<[SubmittedWorkDoneClosure; 1]> { profiling::scope!("triage_submissions"); @@ -558,6 +596,18 @@ impl<A: HalApi> LifetimeTracker<A> { &mut trackers.textures, |maps| &mut maps.textures, ); + + // We may have been suspected because a texture view or bind group + // referring to us was dropped. Remove stale weak references, so that + // the backlink table doesn't grow without bound. + for texture in self.suspected_resources.textures.values() { + texture.views.lock().retain(|view| view.strong_count() > 0); + texture + .bind_groups + .lock() + .retain(|bg| bg.strong_count() > 0); + } + self } @@ -583,6 +633,13 @@ impl<A: HalApi> LifetimeTracker<A> { |maps| &mut maps.buffers, ); + // We may have been suspected because a bind group referring to us was + // dropped. Remove stale weak references, so that the backlink table + // doesn't grow without bound. + for buffer in self.suspected_resources.buffers.values() { + buffer.bind_groups.lock().retain(|bg| bg.strong_count() > 0); + } + self } @@ -693,13 +750,10 @@ impl<A: HalApi> LifetimeTracker<A> { /// Identify resources to free, according to `trackers` and `self.suspected_resources`. /// - /// Given `trackers`, the [`Tracker`] belonging to same [`Device`] as - /// `self`, and `hub`, the [`Hub`] to which that `Device` belongs: - /// - /// Remove from `trackers` each resource mentioned in - /// [`self.suspected_resources`]. If `trackers` held the final reference to - /// that resource, add it to the appropriate free list, to be destroyed by - /// the hal: + /// Remove from `trackers`, the [`Tracker`] belonging to same [`Device`] as + /// `self`, each resource mentioned in [`self.suspected_resources`]. If + /// `trackers` held the final reference to that resource, add it to the + /// appropriate free list, to be destroyed by the hal: /// /// - Add resources used by queue submissions still in flight to the /// [`last_resources`] table of the last such submission's entry in @@ -799,29 +853,33 @@ impl<A: HalApi> LifetimeTracker<A> { *buffer.map_state.lock() = resource::BufferMapState::Idle; log::trace!("Buffer ready to map {tracker_index:?} is not tracked anymore"); } else { - let mapping = match std::mem::replace( + // 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 = std::mem::replace( &mut *buffer.map_state.lock(), resource::BufferMapState::Idle, - ) { + ); + let pending_mapping = match mapping { resource::BufferMapState::Waiting(pending_mapping) => pending_mapping, // Mapping cancelled resource::BufferMapState::Idle => continue, // Mapping queued at least twice by map -> unmap -> map // and was already successfully mapped below - active @ resource::BufferMapState::Active { .. } => { - *buffer.map_state.lock() = active; + resource::BufferMapState::Active { .. } => { + *buffer.map_state.lock() = mapping; continue; } _ => panic!("No pending mapping."), }; - let status = if mapping.range.start != mapping.range.end { + let status = if pending_mapping.range.start != pending_mapping.range.end { log::debug!("Buffer {tracker_index:?} map state -> Active"); - let host = mapping.op.host; - let size = mapping.range.end - mapping.range.start; + let host = pending_mapping.op.host; + let size = pending_mapping.range.end - pending_mapping.range.start; match super::map_buffer( raw, &buffer, - mapping.range.start, + pending_mapping.range.start, size, host, snatch_guard, @@ -829,7 +887,8 @@ impl<A: HalApi> LifetimeTracker<A> { Ok(ptr) => { *buffer.map_state.lock() = resource::BufferMapState::Active { ptr, - range: mapping.range.start..mapping.range.start + size, + range: pending_mapping.range.start + ..pending_mapping.range.start + size, host, }; Ok(()) @@ -842,12 +901,12 @@ impl<A: HalApi> LifetimeTracker<A> { } else { *buffer.map_state.lock() = resource::BufferMapState::Active { ptr: std::ptr::NonNull::dangling(), - range: mapping.range, - host: mapping.op.host, + range: pending_mapping.range, + host: pending_mapping.op.host, }; Ok(()) }; - pending_callbacks.push((mapping.op, status)); + pending_callbacks.push((pending_mapping.op, status)); } } pending_callbacks diff --git a/third_party/rust/wgpu-core/src/device/mod.rs b/third_party/rust/wgpu-core/src/device/mod.rs index e2ab6c2690..e9da11b7a8 100644 --- a/third_party/rust/wgpu-core/src/device/mod.rs +++ b/third_party/rust/wgpu-core/src/device/mod.rs @@ -4,7 +4,6 @@ use crate::{ hub::Hub, id::{BindGroupLayoutId, PipelineLayoutId}, resource::{Buffer, BufferAccessError, BufferAccessResult, BufferMapOperation}, - resource_log, snatch::SnatchGuard, Label, DOWNLEVEL_ERROR_MESSAGE, }; @@ -377,42 +376,6 @@ fn map_buffer<A: HalApi>( Ok(mapping.ptr) } -pub(crate) struct CommandAllocator<A: HalApi> { - free_encoders: Vec<A::CommandEncoder>, -} - -impl<A: HalApi> CommandAllocator<A> { - fn acquire_encoder( - &mut self, - device: &A::Device, - queue: &A::Queue, - ) -> Result<A::CommandEncoder, hal::DeviceError> { - match self.free_encoders.pop() { - Some(encoder) => Ok(encoder), - None => unsafe { - let hal_desc = hal::CommandEncoderDescriptor { label: None, queue }; - device.create_command_encoder(&hal_desc) - }, - } - } - - fn release_encoder(&mut self, encoder: A::CommandEncoder) { - self.free_encoders.push(encoder); - } - - fn dispose(self, device: &A::Device) { - resource_log!( - "CommandAllocator::dispose encoders {}", - self.free_encoders.len() - ); - for cmd_encoder in self.free_encoders { - unsafe { - device.destroy_command_encoder(cmd_encoder); - } - } - } -} - #[derive(Clone, Debug, Error)] #[error("Device is invalid")] pub struct InvalidDevice; diff --git a/third_party/rust/wgpu-core/src/device/queue.rs b/third_party/rust/wgpu-core/src/device/queue.rs index 3cb5f695a7..f0db961ffc 100644 --- a/third_party/rust/wgpu-core/src/device/queue.rs +++ b/third_party/rust/wgpu-core/src/device/queue.rs @@ -4,16 +4,17 @@ use crate::{ api_log, command::{ extract_texture_selector, validate_linear_texture_data, validate_texture_copy_range, - ClearError, CommandBuffer, CopySide, ImageCopyTexture, TransferError, + ClearError, CommandAllocator, CommandBuffer, CopySide, ImageCopyTexture, TransferError, }, conv, - device::{life::ResourceMaps, DeviceError, WaitIdleError}, + device::{DeviceError, WaitIdleError}, get_lowest_common_denom, global::Global, hal_api::HalApi, hal_label, id::{self, DeviceId, QueueId}, init_tracker::{has_copy_partial_init_tracker_coverage, TextureInitRange}, + lock::{rank, Mutex, RwLockWriteGuard}, resource::{ Buffer, BufferAccessError, BufferMapState, DestroyedBuffer, DestroyedTexture, Resource, ResourceInfo, ResourceType, StagingBuffer, Texture, TextureInner, @@ -22,7 +23,6 @@ use crate::{ }; use hal::{CommandEncoder as _, Device as _, Queue as _}; -use parking_lot::Mutex; use smallvec::SmallVec; use std::{ @@ -34,9 +34,9 @@ use thiserror::Error; use super::Device; pub struct Queue<A: HalApi> { - pub device: Option<Arc<Device<A>>>, - pub raw: Option<A::Queue>, - pub info: ResourceInfo<Queue<A>>, + pub(crate) device: Option<Arc<Device<A>>>, + pub(crate) raw: Option<A::Queue>, + pub(crate) info: ResourceInfo<Queue<A>>, } impl<A: HalApi> Resource for Queue<A> { @@ -152,13 +152,21 @@ pub enum TempResource<A: HalApi> { Texture(Arc<Texture<A>>), } -/// A queue execution for a particular command encoder. +/// 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<A: HalApi> { raw: A::CommandEncoder, cmd_buffers: Vec<A::CommandBuffer>, } impl<A: HalApi> EncoderInFlight<A> { + /// Free all of our command buffers. + /// + /// Return the command encoder, fully reset and ready to be + /// reused. pub(crate) unsafe fn land(mut self) -> A::CommandEncoder { unsafe { self.raw.reset_all(self.cmd_buffers.into_iter()) }; self.raw @@ -192,6 +200,8 @@ pub(crate) struct PendingWrites<A: HalApi> { /// 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, pub temp_resources: Vec<TempResource<A>>, @@ -253,7 +263,7 @@ impl<A: HalApi> PendingWrites<A> { #[must_use] fn post_submit( &mut self, - command_allocator: &mut super::CommandAllocator<A>, + command_allocator: &CommandAllocator<A>, device: &A::Device, queue: &A::Queue, ) -> Option<EncoderInFlight<A>> { @@ -307,7 +317,7 @@ fn prepare_staging_buffer<A: HalApi>( let mapping = unsafe { device.raw().map_buffer(&buffer, 0..size) }?; let staging_buffer = StagingBuffer { - raw: Mutex::new(Some(buffer)), + raw: Mutex::new(rank::STAGING_BUFFER_RAW, Some(buffer)), device: device.clone(), size, info: ResourceInfo::new( @@ -490,7 +500,7 @@ impl Global { prepare_staging_buffer(device, buffer_size.get(), device.instance_flags)?; let fid = hub.staging_buffers.prepare(id_in); - let (id, _) = fid.assign(staging_buffer); + let (id, _) = fid.assign(Arc::new(staging_buffer)); resource_log!("Queue::create_staging_buffer {id:?}"); Ok((id, staging_buffer_ptr)) @@ -707,7 +717,7 @@ impl Global { .get(destination.texture) .map_err(|_| TransferError::InvalidTexture(destination.texture))?; - if dst.device.as_info().id() != queue_id.transmute() { + if dst.device.as_info().id().into_queue_id() != queue_id { return Err(DeviceError::WrongDevice.into()); } @@ -1152,8 +1162,8 @@ impl Global { let snatch_guard = device.snatchable_lock.read(); // Fence lock must be acquired after the snatch lock everywhere to avoid deadlocks. - let mut fence = device.fence.write(); - let fence = fence.as_mut().unwrap(); + let mut fence_guard = device.fence.write(); + let fence = fence_guard.as_mut().unwrap(); let submit_index = device .active_submission_index .fetch_add(1, Ordering::Relaxed) @@ -1173,11 +1183,6 @@ impl Global { //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. - let mut temp_suspected = device.temp_suspected.lock(); - { - let mut suspected = temp_suspected.replace(ResourceMaps::new()).unwrap(); - suspected.clear(); - } // finish all the command buffers first for &cmb_id in command_buffer_ids { @@ -1191,7 +1196,7 @@ impl Global { Err(_) => continue, }; - if cmdbuf.device.as_info().id() != queue_id.transmute() { + if cmdbuf.device.as_info().id().into_queue_id() != queue_id { return Err(DeviceError::WrongDevice.into()); } @@ -1210,13 +1215,10 @@ impl Global { )); } if !cmdbuf.is_finished() { - if let Some(cmdbuf) = Arc::into_inner(cmdbuf) { - device.destroy_command_buffer(cmdbuf); - } else { - panic!( - "Command buffer cannot be destroyed because is still in use" - ); - } + let cmdbuf = Arc::into_inner(cmdbuf).expect( + "Command buffer cannot be destroyed because is still in use", + ); + device.destroy_command_buffer(cmdbuf); continue; } @@ -1228,41 +1230,23 @@ impl Global { // update submission IDs for buffer in cmd_buf_trackers.buffers.used_resources() { - let tracker_index = buffer.info.tracker_index(); - let raw_buf = match buffer.raw.get(&snatch_guard) { - Some(raw) => raw, - None => { - return Err(QueueSubmitError::DestroyedBuffer( - buffer.info.id(), - )); - } - }; + if buffer.raw.get(&snatch_guard).is_none() { + return Err(QueueSubmitError::DestroyedBuffer( + buffer.info.id(), + )); + } buffer.info.use_at(submit_index); - if buffer.is_unique() { - if let BufferMapState::Active { .. } = *buffer.map_state.lock() - { - log::warn!("Dropped buffer has a pending mapping."); - unsafe { device.raw().unmap_buffer(raw_buf) } - .map_err(DeviceError::from)?; - } - temp_suspected - .as_mut() - .unwrap() - .buffers - .insert(tracker_index, buffer.clone()); - } else { - match *buffer.map_state.lock() { - BufferMapState::Idle => (), - _ => { - return Err(QueueSubmitError::BufferStillMapped( - buffer.info.id(), - )) - } + + match *buffer.map_state.lock() { + BufferMapState::Idle => (), + _ => { + return Err(QueueSubmitError::BufferStillMapped( + buffer.info.id(), + )) } } } for texture in cmd_buf_trackers.textures.used_resources() { - let tracker_index = texture.info.tracker_index(); let should_extend = match texture.inner.get(&snatch_guard) { None => { return Err(QueueSubmitError::DestroyedTexture( @@ -1279,13 +1263,6 @@ impl Global { } }; texture.info.use_at(submit_index); - if texture.is_unique() { - temp_suspected - .as_mut() - .unwrap() - .textures - .insert(tracker_index, texture.clone()); - } if should_extend { unsafe { used_surface_textures @@ -1296,12 +1273,6 @@ impl Global { } for texture_view in cmd_buf_trackers.views.used_resources() { texture_view.info.use_at(submit_index); - if texture_view.is_unique() { - temp_suspected.as_mut().unwrap().texture_views.insert( - texture_view.as_info().tracker_index(), - texture_view.clone(), - ); - } } { for bg in cmd_buf_trackers.bind_groups.used_resources() { @@ -1315,13 +1286,6 @@ impl Global { for sampler in bg.used.samplers.used_resources() { sampler.info.use_at(submit_index); } - if bg.is_unique() { - temp_suspected - .as_mut() - .unwrap() - .bind_groups - .insert(bg.as_info().tracker_index(), bg.clone()); - } } } // assert!(cmd_buf_trackers.samplers.is_empty()); @@ -1329,32 +1293,14 @@ impl Global { cmd_buf_trackers.compute_pipelines.used_resources() { compute_pipeline.info.use_at(submit_index); - if compute_pipeline.is_unique() { - temp_suspected.as_mut().unwrap().compute_pipelines.insert( - compute_pipeline.as_info().tracker_index(), - compute_pipeline.clone(), - ); - } } for render_pipeline in cmd_buf_trackers.render_pipelines.used_resources() { render_pipeline.info.use_at(submit_index); - if render_pipeline.is_unique() { - temp_suspected.as_mut().unwrap().render_pipelines.insert( - render_pipeline.as_info().tracker_index(), - render_pipeline.clone(), - ); - } } for query_set in cmd_buf_trackers.query_sets.used_resources() { query_set.info.use_at(submit_index); - if query_set.is_unique() { - temp_suspected.as_mut().unwrap().query_sets.insert( - query_set.as_info().tracker_index(), - query_set.clone(), - ); - } } for bundle in cmd_buf_trackers.bundles.used_resources() { bundle.info.use_at(submit_index); @@ -1369,13 +1315,6 @@ impl Global { for query_set in bundle.used.query_sets.read().used_resources() { query_set.info.use_at(submit_index); } - if bundle.is_unique() { - temp_suspected - .as_mut() - .unwrap() - .render_bundles - .insert(bundle.as_info().tracker_index(), bundle.clone()); - } } } let mut baked = cmdbuf.from_arc_into_baked(); @@ -1452,8 +1391,8 @@ impl Global { } } - let mut pending_writes = device.pending_writes.lock(); - let pending_writes = pending_writes.as_mut().unwrap(); + let mut pending_writes_guard = device.pending_writes.lock(); + let pending_writes = pending_writes_guard.as_mut().unwrap(); { used_surface_textures.set_size(hub.textures.read().len()); @@ -1528,7 +1467,7 @@ impl Global { profiling::scope!("cleanup"); if let Some(pending_execution) = pending_writes.post_submit( - device.command_allocator.lock().as_mut().unwrap(), + &device.command_allocator, device.raw(), queue.raw.as_ref().unwrap(), ) { @@ -1543,18 +1482,22 @@ impl Global { active_executions, ); - // 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 (closures, _) = match device.maintain(fence, wgt::Maintain::Poll, snatch_guard) { - Ok(closures) => closures, - Err(WaitIdleError::Device(err)) => return Err(QueueSubmitError::Queue(err)), - Err(WaitIdleError::StuckGpu) => return Err(QueueSubmitError::StuckGpu), - Err(WaitIdleError::WrongSubmissionIndex(..)) => unreachable!(), - }; - // pending_write_resources has been drained, so it's empty, but we // want to retain its heap allocation. pending_writes.temp_resources = pending_write_resources; + drop(pending_writes_guard); + + // 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_guard); + let (closures, _) = + match device.maintain(fence_guard, wgt::Maintain::Poll, snatch_guard) { + Ok(closures) => closures, + Err(WaitIdleError::Device(err)) => return Err(QueueSubmitError::Queue(err)), + Err(WaitIdleError::StuckGpu) => return Err(QueueSubmitError::StuckGpu), + Err(WaitIdleError::WrongSubmissionIndex(..)) => unreachable!(), + }; + device.lock_life().post_submit(); (submit_index, closures) diff --git a/third_party/rust/wgpu-core/src/device/resource.rs b/third_party/rust/wgpu-core/src/device/resource.rs index 4892aecb75..2541af7c70 100644 --- a/third_party/rust/wgpu-core/src/device/resource.rs +++ b/third_party/rust/wgpu-core/src/device/resource.rs @@ -7,18 +7,20 @@ use crate::{ bgl, life::{LifetimeTracker, WaitIdleError}, queue::PendingWrites, - AttachmentData, CommandAllocator, DeviceLostInvocation, MissingDownlevelFlags, - MissingFeatures, RenderPassContext, CLEANUP_WAIT_MS, + AttachmentData, DeviceLostInvocation, MissingDownlevelFlags, MissingFeatures, + RenderPassContext, CLEANUP_WAIT_MS, }, hal_api::HalApi, hal_label, hub::Hub, + id, init_tracker::{ BufferInitTracker, BufferInitTrackerAction, MemoryInitKind, TextureInitRange, TextureInitTracker, TextureInitTrackerAction, }, instance::Adapter, - pipeline, + lock::{rank, Mutex, MutexGuard, RwLock}, + pipeline::{self}, pool::ResourcePool, registry::Registry, resource::{ @@ -41,7 +43,6 @@ use crate::{ use arrayvec::ArrayVec; use hal::{CommandEncoder as _, Device as _}; use once_cell::sync::OnceCell; -use parking_lot::{Mutex, MutexGuard, RwLock}; use smallvec::SmallVec; use thiserror::Error; @@ -97,7 +98,7 @@ pub struct Device<A: HalApi> { pub(crate) zero_buffer: Option<A::Buffer>, pub(crate) info: ResourceInfo<Device<A>>, - pub(crate) command_allocator: Mutex<Option<CommandAllocator<A>>>, + pub(crate) command_allocator: command::CommandAllocator<A>, //Note: The submission index here corresponds to the last submission that is done. pub(crate) active_submission_index: AtomicU64, //SubmissionIndex, // NOTE: if both are needed, the `snatchable_lock` must be consistently acquired before the @@ -126,9 +127,6 @@ pub struct Device<A: HalApi> { pub(crate) tracker_indices: TrackerIndexAllocators, // Life tracker should be locked right after the device and before anything else. life_tracker: Mutex<LifetimeTracker<A>>, - /// Temporary storage for resource management functions. Cleared at the end - /// of every call (unless an error occurs). - pub(crate) temp_suspected: Mutex<Option<ResourceMaps<A>>>, /// Pool of bind group layouts, allowing deduplication. pub(crate) bgl_pool: ResourcePool<bgl::EntryMap, BindGroupLayout<A>>, pub(crate) alignments: hal::Alignments, @@ -141,6 +139,10 @@ pub struct Device<A: HalApi> { #[cfg(feature = "trace")] pub(crate) trace: Mutex<Option<trace::Trace>>, pub(crate) usage_scopes: UsageScopePool<A>, + + /// Temporary storage, cleared at the start of every call, + /// retained only to save allocations. + temp_suspected: Mutex<Option<ResourceMaps<A>>>, } pub(crate) enum DeferredDestroy<A: HalApi> { @@ -165,7 +167,7 @@ impl<A: HalApi> Drop for Device<A> { let raw = self.raw.take().unwrap(); let pending_writes = self.pending_writes.lock().take().unwrap(); pending_writes.dispose(&raw); - self.command_allocator.lock().take().unwrap().dispose(&raw); + self.command_allocator.dispose(&raw); unsafe { raw.destroy_buffer(self.zero_buffer.take().unwrap()); raw.destroy_fence(self.fence.write().take().unwrap()); @@ -223,10 +225,8 @@ impl<A: HalApi> Device<A> { let fence = unsafe { raw_device.create_fence() }.map_err(|_| CreateDeviceError::OutOfMemory)?; - let mut com_alloc = CommandAllocator { - free_encoders: Vec::new(), - }; - let pending_encoder = com_alloc + let command_allocator = command::CommandAllocator::new(); + let pending_encoder = command_allocator .acquire_encoder(&raw_device, raw_queue) .map_err(|_| CreateDeviceError::OutOfMemory)?; let mut pending_writes = queue::PendingWrites::<A>::new(pending_encoder); @@ -271,38 +271,44 @@ impl<A: HalApi> Device<A> { queue_to_drop: OnceCell::new(), zero_buffer: Some(zero_buffer), info: ResourceInfo::new("<device>", None), - command_allocator: Mutex::new(Some(com_alloc)), + command_allocator, active_submission_index: AtomicU64::new(0), - fence: RwLock::new(Some(fence)), - snatchable_lock: unsafe { SnatchLock::new() }, + fence: RwLock::new(rank::DEVICE_FENCE, Some(fence)), + snatchable_lock: unsafe { SnatchLock::new(rank::DEVICE_SNATCHABLE_LOCK) }, valid: AtomicBool::new(true), - trackers: Mutex::new(Tracker::new()), + trackers: Mutex::new(rank::DEVICE_TRACKERS, Tracker::new()), tracker_indices: TrackerIndexAllocators::new(), - life_tracker: Mutex::new(life::LifetimeTracker::new()), - temp_suspected: Mutex::new(Some(life::ResourceMaps::new())), + life_tracker: Mutex::new(rank::DEVICE_LIFE_TRACKER, life::LifetimeTracker::new()), + temp_suspected: Mutex::new( + rank::DEVICE_TEMP_SUSPECTED, + Some(life::ResourceMaps::new()), + ), bgl_pool: ResourcePool::new(), #[cfg(feature = "trace")] - trace: Mutex::new(trace_path.and_then(|path| match trace::Trace::new(path) { - Ok(mut trace) => { - trace.add(trace::Action::Init { - desc: desc.clone(), - backend: A::VARIANT, - }); - Some(trace) - } - Err(e) => { - log::error!("Unable to start a trace in '{path:?}': {e}"); - None - } - })), + trace: Mutex::new( + rank::DEVICE_TRACE, + trace_path.and_then(|path| match trace::Trace::new(path) { + Ok(mut trace) => { + trace.add(trace::Action::Init { + desc: desc.clone(), + backend: A::VARIANT, + }); + Some(trace) + } + Err(e) => { + log::error!("Unable to start a trace in '{path:?}': {e}"); + None + } + }), + ), alignments, limits: desc.required_limits.clone(), features: desc.required_features, downlevel, instance_flags, - pending_writes: Mutex::new(Some(pending_writes)), - deferred_destroy: Mutex::new(Vec::new()), - usage_scopes: Default::default(), + pending_writes: Mutex::new(rank::DEVICE_PENDING_WRITES, Some(pending_writes)), + deferred_destroy: Mutex::new(rank::DEVICE_DEFERRED_DESTROY, Vec::new()), + usage_scopes: Mutex::new(rank::DEVICE_USAGE_SCOPES, Default::default()), }) } @@ -379,7 +385,7 @@ impl<A: HalApi> Device<A> { /// Check this device for completed commands. /// - /// The `maintain` argument tells how the maintence function should behave, either + /// The `maintain` argument tells how the maintenance function should behave, either /// blocking or just polling the current state of the gpu. /// /// Return a pair `(closures, queue_empty)`, where: @@ -392,11 +398,12 @@ impl<A: HalApi> Device<A> { /// return it to our callers.) pub(crate) fn maintain<'this>( &'this self, - fence: &A::Fence, + fence_guard: crate::lock::RwLockReadGuard<Option<A::Fence>>, maintain: wgt::Maintain<queue::WrappedSubmissionIndex>, snatch_guard: SnatchGuard, ) -> Result<(UserClosures, bool), WaitIdleError> { profiling::scope!("Device::maintain"); + let fence = fence_guard.as_ref().unwrap(); let last_done_index = if maintain.is_wait() { let index_to_wait_for = match maintain { wgt::Maintain::WaitForSubmissionIndex(submission_index) => { @@ -425,28 +432,12 @@ impl<A: HalApi> Device<A> { }; let mut life_tracker = self.lock_life(); - let submission_closures = life_tracker.triage_submissions( - last_done_index, - self.command_allocator.lock().as_mut().unwrap(), - ); - - { - // Normally, `temp_suspected` exists only to save heap - // allocations: it's cleared at the start of the function - // call, and cleared by the end. But `Global::queue_submit` is - // fallible; if it exits early, it may leave some resources in - // `temp_suspected`. - let temp_suspected = self - .temp_suspected - .lock() - .replace(ResourceMaps::new()) - .unwrap(); + let submission_closures = + life_tracker.triage_submissions(last_done_index, &self.command_allocator); - life_tracker.suspected_resources.extend(temp_suspected); + life_tracker.triage_suspected(&self.trackers); - life_tracker.triage_suspected(&self.trackers); - life_tracker.triage_mapped(); - } + life_tracker.triage_mapped(); let mapping_closures = life_tracker.handle_mapping(self.raw(), &self.trackers, &snatch_guard); @@ -478,6 +469,7 @@ impl<A: HalApi> Device<A> { // Don't hold the locks while calling release_gpu_resources. drop(life_tracker); + drop(fence_guard); drop(snatch_guard); if should_release_gpu_resource { @@ -493,12 +485,14 @@ impl<A: HalApi> Device<A> { } pub(crate) fn untrack(&self, trackers: &Tracker<A>) { + // If we have a previously allocated `ResourceMap`, just use that. let mut temp_suspected = self .temp_suspected .lock() - .replace(ResourceMaps::new()) - .unwrap(); + .take() + .unwrap_or_else(|| ResourceMaps::new()); temp_suspected.clear(); + // As the tracker is cleared/dropped, we need to consider all the resources // that it references for destruction in the next GC pass. { @@ -559,7 +553,11 @@ impl<A: HalApi> Device<A> { } } } - self.lock_life().suspected_resources.extend(temp_suspected); + self.lock_life() + .suspected_resources + .extend(&mut temp_suspected); + // Save this resource map for later reuse. + *self.temp_suspected.lock() = Some(temp_suspected); } pub(crate) fn create_buffer( @@ -653,14 +651,17 @@ impl<A: HalApi> Device<A> { device: self.clone(), usage: desc.usage, size: desc.size, - initialization_status: RwLock::new(BufferInitTracker::new(aligned_size)), - sync_mapped_writes: Mutex::new(None), - map_state: Mutex::new(resource::BufferMapState::Idle), + initialization_status: RwLock::new( + rank::BUFFER_INITIALIZATION_STATUS, + BufferInitTracker::new(aligned_size), + ), + sync_mapped_writes: Mutex::new(rank::BUFFER_SYNC_MAPPED_WRITES, None), + map_state: Mutex::new(rank::BUFFER_MAP_STATE, resource::BufferMapState::Idle), info: ResourceInfo::new( desc.label.borrow_or_default(), Some(self.tracker_indices.buffers.clone()), ), - bind_groups: Mutex::new(Vec::new()), + bind_groups: Mutex::new(rank::BUFFER_BIND_GROUPS, Vec::new()), }) } @@ -680,10 +681,10 @@ impl<A: HalApi> Device<A> { desc: desc.map_label(|_| ()), hal_usage, format_features, - initialization_status: RwLock::new(TextureInitTracker::new( - desc.mip_level_count, - desc.array_layer_count(), - )), + initialization_status: RwLock::new( + rank::TEXTURE_INITIALIZATION_STATUS, + TextureInitTracker::new(desc.mip_level_count, desc.array_layer_count()), + ), full_range: TextureSelector { mips: 0..desc.mip_level_count, layers: 0..desc.array_layer_count(), @@ -692,9 +693,9 @@ impl<A: HalApi> Device<A> { desc.label.borrow_or_default(), Some(self.tracker_indices.textures.clone()), ), - clear_mode: RwLock::new(clear_mode), - views: Mutex::new(Vec::new()), - bind_groups: Mutex::new(Vec::new()), + clear_mode: RwLock::new(rank::TEXTURE_CLEAR_MODE, clear_mode), + views: Mutex::new(rank::TEXTURE_VIEWS, Vec::new()), + bind_groups: Mutex::new(rank::TEXTURE_BIND_GROUPS, Vec::new()), } } @@ -710,14 +711,17 @@ impl<A: HalApi> Device<A> { device: self.clone(), usage: desc.usage, size: desc.size, - initialization_status: RwLock::new(BufferInitTracker::new(0)), - sync_mapped_writes: Mutex::new(None), - map_state: Mutex::new(resource::BufferMapState::Idle), + initialization_status: RwLock::new( + rank::BUFFER_INITIALIZATION_STATUS, + BufferInitTracker::new(0), + ), + sync_mapped_writes: Mutex::new(rank::BUFFER_SYNC_MAPPED_WRITES, None), + map_state: Mutex::new(rank::BUFFER_MAP_STATE, resource::BufferMapState::Idle), info: ResourceInfo::new( desc.label.borrow_or_default(), Some(self.tracker_indices.buffers.clone()), ), - bind_groups: Mutex::new(Vec::new()), + bind_groups: Mutex::new(rank::BUFFER_BIND_GROUPS, Vec::new()), } } @@ -1421,7 +1425,7 @@ impl<A: HalApi> Device<A> { pipeline::ShaderModuleSource::Wgsl(code) => { profiling::scope!("naga::front::wgsl::parse_str"); let module = naga::front::wgsl::parse_str(&code).map_err(|inner| { - pipeline::CreateShaderModuleError::Parsing(pipeline::ShaderError { + pipeline::CreateShaderModuleError::Parsing(naga::error::ShaderError { source: code.to_string(), label: desc.label.as_ref().map(|l| l.to_string()), inner: Box::new(inner), @@ -1434,7 +1438,7 @@ impl<A: HalApi> Device<A> { let parser = naga::front::spv::Frontend::new(spv.iter().cloned(), &options); profiling::scope!("naga::front::spv::Frontend"); let module = parser.parse().map_err(|inner| { - pipeline::CreateShaderModuleError::ParsingSpirV(pipeline::ShaderError { + pipeline::CreateShaderModuleError::ParsingSpirV(naga::error::ShaderError { source: String::new(), label: desc.label.as_ref().map(|l| l.to_string()), inner: Box::new(inner), @@ -1447,7 +1451,7 @@ impl<A: HalApi> Device<A> { let mut parser = naga::front::glsl::Frontend::default(); profiling::scope!("naga::front::glsl::Frontend.parse"); let module = parser.parse(&options, &code).map_err(|inner| { - pipeline::CreateShaderModuleError::ParsingGlsl(pipeline::ShaderError { + pipeline::CreateShaderModuleError::ParsingGlsl(naga::error::ShaderError { source: code.to_string(), label: desc.label.as_ref().map(|l| l.to_string()), inner: Box::new(inner), @@ -1471,9 +1475,78 @@ impl<A: HalApi> Device<A> { }; } - use naga::valid::Capabilities as Caps; profiling::scope!("naga::validate"); + let debug_source = + if self.instance_flags.contains(wgt::InstanceFlags::DEBUG) && !source.is_empty() { + Some(hal::DebugSource { + file_name: Cow::Owned( + desc.label + .as_ref() + .map_or("shader".to_string(), |l| l.to_string()), + ), + source_code: Cow::Owned(source.clone()), + }) + } else { + None + }; + + let info = self + .create_validator(naga::valid::ValidationFlags::all()) + .validate(&module) + .map_err(|inner| { + pipeline::CreateShaderModuleError::Validation(naga::error::ShaderError { + source, + label: desc.label.as_ref().map(|l| l.to_string()), + inner: Box::new(inner), + }) + })?; + + let interface = + validation::Interface::new(&module, &info, self.limits.clone(), self.features); + let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { + module, + info, + debug_source, + }); + let hal_desc = hal::ShaderModuleDescriptor { + label: desc.label.to_hal(self.instance_flags), + runtime_checks: desc.shader_bound_checks.runtime_checks(), + }; + let raw = match unsafe { + self.raw + .as_ref() + .unwrap() + .create_shader_module(&hal_desc, hal_shader) + } { + Ok(raw) => raw, + Err(error) => { + return Err(match error { + hal::ShaderError::Device(error) => { + pipeline::CreateShaderModuleError::Device(error.into()) + } + hal::ShaderError::Compilation(ref msg) => { + log::error!("Shader error: {}", msg); + pipeline::CreateShaderModuleError::Generation + } + }) + } + }; + + Ok(pipeline::ShaderModule { + raw: Some(raw), + device: self.clone(), + interface: Some(interface), + info: ResourceInfo::new(desc.label.borrow_or_default(), None), + label: desc.label.borrow_or_default().to_string(), + }) + } + /// Create a validator with the given validation flags. + pub fn create_validator( + self: &Arc<Self>, + flags: naga::valid::ValidationFlags, + ) -> naga::valid::Validator { + use naga::valid::Capabilities as Caps; let mut caps = Caps::empty(); caps.set( Caps::PUSH_CONSTANT, @@ -1541,69 +1614,36 @@ impl<A: HalApi> Device<A> { .flags .contains(wgt::DownlevelFlags::CUBE_ARRAY_TEXTURES), ); + caps.set( + Caps::SUBGROUP, + self.features + .intersects(wgt::Features::SUBGROUP | wgt::Features::SUBGROUP_VERTEX), + ); + caps.set( + Caps::SUBGROUP_BARRIER, + self.features.intersects(wgt::Features::SUBGROUP_BARRIER), + ); - let debug_source = - if self.instance_flags.contains(wgt::InstanceFlags::DEBUG) && !source.is_empty() { - Some(hal::DebugSource { - file_name: Cow::Owned( - desc.label - .as_ref() - .map_or("shader".to_string(), |l| l.to_string()), - ), - source_code: Cow::Owned(source.clone()), - }) - } else { - None - }; - - let info = naga::valid::Validator::new(naga::valid::ValidationFlags::all(), caps) - .validate(&module) - .map_err(|inner| { - pipeline::CreateShaderModuleError::Validation(pipeline::ShaderError { - source, - label: desc.label.as_ref().map(|l| l.to_string()), - inner: Box::new(inner), - }) - })?; + let mut subgroup_stages = naga::valid::ShaderStages::empty(); + subgroup_stages.set( + naga::valid::ShaderStages::COMPUTE | naga::valid::ShaderStages::FRAGMENT, + self.features.contains(wgt::Features::SUBGROUP), + ); + subgroup_stages.set( + naga::valid::ShaderStages::VERTEX, + self.features.contains(wgt::Features::SUBGROUP_VERTEX), + ); - let interface = - validation::Interface::new(&module, &info, self.limits.clone(), self.features); - let hal_shader = hal::ShaderInput::Naga(hal::NagaShader { - module, - info, - debug_source, - }); - let hal_desc = hal::ShaderModuleDescriptor { - label: desc.label.to_hal(self.instance_flags), - runtime_checks: desc.shader_bound_checks.runtime_checks(), - }; - let raw = match unsafe { - self.raw - .as_ref() - .unwrap() - .create_shader_module(&hal_desc, hal_shader) - } { - Ok(raw) => raw, - Err(error) => { - return Err(match error { - hal::ShaderError::Device(error) => { - pipeline::CreateShaderModuleError::Device(error.into()) - } - hal::ShaderError::Compilation(ref msg) => { - log::error!("Shader error: {}", msg); - pipeline::CreateShaderModuleError::Generation - } - }) - } + let subgroup_operations = if caps.contains(Caps::SUBGROUP) { + use naga::valid::SubgroupOperationSet as S; + S::BASIC | S::VOTE | S::ARITHMETIC | S::BALLOT | S::SHUFFLE | S::SHUFFLE_RELATIVE + } else { + naga::valid::SubgroupOperationSet::empty() }; - - Ok(pipeline::ShaderModule { - raw: Some(raw), - device: self.clone(), - interface: Some(interface), - info: ResourceInfo::new(desc.label.borrow_or_default(), None), - label: desc.label.borrow_or_default().to_string(), - }) + let mut validator = naga::valid::Validator::new(flags, caps); + validator.subgroup_stages(subgroup_stages); + validator.subgroup_operations(subgroup_operations); + validator } #[allow(unused_unsafe)] @@ -1913,6 +1953,7 @@ impl<A: HalApi> Device<A> { used: &mut BindGroupStates<A>, storage: &'a Storage<Buffer<A>>, limits: &wgt::Limits, + device_id: id::Id<id::markers::Device>, snatch_guard: &'a SnatchGuard<'a>, ) -> Result<hal::BufferBinding<'a, A>, binding_model::CreateBindGroupError> { use crate::binding_model::CreateBindGroupError as Error; @@ -1931,6 +1972,7 @@ impl<A: HalApi> Device<A> { }) } }; + let (pub_usage, internal_use, range_limit) = match binding_ty { wgt::BufferBindingType::Uniform => ( wgt::BufferUsages::UNIFORM, @@ -1963,6 +2005,10 @@ impl<A: HalApi> Device<A> { .add_single(storage, bb.buffer_id, internal_use) .ok_or(Error::InvalidBuffer(bb.buffer_id))?; + if buffer.device.as_info().id() != device_id { + return Err(DeviceError::WrongDevice.into()); + } + check_buffer_usage(bb.buffer_id, buffer.usage, pub_usage)?; let raw_buffer = buffer .raw @@ -2041,13 +2087,53 @@ impl<A: HalApi> Device<A> { }) } - pub(crate) fn create_texture_binding( - view: &TextureView<A>, - internal_use: hal::TextureUses, - pub_usage: wgt::TextureUsages, + fn create_sampler_binding<'a>( + used: &BindGroupStates<A>, + storage: &'a Storage<Sampler<A>>, + id: id::Id<id::markers::Sampler>, + device_id: id::Id<id::markers::Device>, + ) -> Result<&'a Sampler<A>, binding_model::CreateBindGroupError> { + use crate::binding_model::CreateBindGroupError as Error; + + let sampler = used + .samplers + .add_single(storage, id) + .ok_or(Error::InvalidSampler(id))?; + + if sampler.device.as_info().id() != device_id { + return Err(DeviceError::WrongDevice.into()); + } + + Ok(sampler) + } + + pub(crate) fn create_texture_binding<'a>( + self: &Arc<Self>, + binding: u32, + decl: &wgt::BindGroupLayoutEntry, + storage: &'a Storage<TextureView<A>>, + id: id::Id<id::markers::TextureView>, used: &mut BindGroupStates<A>, used_texture_ranges: &mut Vec<TextureInitTrackerAction<A>>, - ) -> Result<(), binding_model::CreateBindGroupError> { + snatch_guard: &'a SnatchGuard<'a>, + ) -> Result<hal::TextureBinding<'a, A>, binding_model::CreateBindGroupError> { + use crate::binding_model::CreateBindGroupError as Error; + + let view = used + .views + .add_single(storage, id) + .ok_or(Error::InvalidTextureView(id))?; + + if view.device.as_info().id() != self.as_info().id() { + return Err(DeviceError::WrongDevice.into()); + } + + let (pub_usage, internal_use) = self.texture_use_parameters( + binding, + decl, + view, + "SampledTexture, ReadonlyStorageTexture or WriteonlyStorageTexture", + )?; let texture = &view.parent; let texture_id = texture.as_info().id(); // Careful here: the texture may no longer have its own ref count, @@ -2077,7 +2163,12 @@ impl<A: HalApi> Device<A> { kind: MemoryInitKind::NeedsInitializedMemory, }); - Ok(()) + Ok(hal::TextureBinding { + view: view + .raw(snatch_guard) + .ok_or(Error::InvalidTextureView(id))?, + usage: internal_use, + }) } // This function expects the provided bind group layout to be resolved @@ -2139,6 +2230,7 @@ impl<A: HalApi> Device<A> { &mut used, &*buffer_guard, &self.limits, + self.as_info().id(), &snatch_guard, )?; @@ -2162,105 +2254,86 @@ impl<A: HalApi> Device<A> { &mut used, &*buffer_guard, &self.limits, + self.as_info().id(), &snatch_guard, )?; hal_buffers.push(bb); } (res_index, num_bindings) } - Br::Sampler(id) => { - match decl.ty { - wgt::BindingType::Sampler(ty) => { - let sampler = used - .samplers - .add_single(&*sampler_guard, id) - .ok_or(Error::InvalidSampler(id))?; - - if sampler.device.as_info().id() != self.as_info().id() { - return Err(DeviceError::WrongDevice.into()); - } - - // Allowed sampler values for filtering and comparison - let (allowed_filtering, allowed_comparison) = match ty { - wgt::SamplerBindingType::Filtering => (None, false), - wgt::SamplerBindingType::NonFiltering => (Some(false), false), - wgt::SamplerBindingType::Comparison => (None, true), - }; - - if let Some(allowed_filtering) = allowed_filtering { - if allowed_filtering != sampler.filtering { - return Err(Error::WrongSamplerFiltering { - binding, - layout_flt: allowed_filtering, - sampler_flt: sampler.filtering, - }); - } - } + Br::Sampler(id) => match decl.ty { + wgt::BindingType::Sampler(ty) => { + let sampler = Self::create_sampler_binding( + &used, + &sampler_guard, + id, + self.as_info().id(), + )?; - if allowed_comparison != sampler.comparison { - return Err(Error::WrongSamplerComparison { + let (allowed_filtering, allowed_comparison) = match ty { + wgt::SamplerBindingType::Filtering => (None, false), + wgt::SamplerBindingType::NonFiltering => (Some(false), false), + wgt::SamplerBindingType::Comparison => (None, true), + }; + if let Some(allowed_filtering) = allowed_filtering { + if allowed_filtering != sampler.filtering { + return Err(Error::WrongSamplerFiltering { binding, - layout_cmp: allowed_comparison, - sampler_cmp: sampler.comparison, + layout_flt: allowed_filtering, + sampler_flt: sampler.filtering, }); } - - let res_index = hal_samplers.len(); - hal_samplers.push(sampler.raw()); - (res_index, 1) } - _ => { - return Err(Error::WrongBindingType { + if allowed_comparison != sampler.comparison { + return Err(Error::WrongSamplerComparison { binding, - actual: decl.ty, - expected: "Sampler", - }) + layout_cmp: allowed_comparison, + sampler_cmp: sampler.comparison, + }); } + + let res_index = hal_samplers.len(); + hal_samplers.push(sampler.raw()); + (res_index, 1) } - } + _ => { + return Err(Error::WrongBindingType { + binding, + actual: decl.ty, + expected: "Sampler", + }) + } + }, Br::SamplerArray(ref bindings_array) => { let num_bindings = bindings_array.len(); Self::check_array_binding(self.features, decl.count, num_bindings)?; let res_index = hal_samplers.len(); for &id in bindings_array.iter() { - let sampler = used - .samplers - .add_single(&*sampler_guard, id) - .ok_or(Error::InvalidSampler(id))?; - if sampler.device.as_info().id() != self.as_info().id() { - return Err(DeviceError::WrongDevice.into()); - } + let sampler = Self::create_sampler_binding( + &used, + &sampler_guard, + id, + self.as_info().id(), + )?; + hal_samplers.push(sampler.raw()); } (res_index, num_bindings) } Br::TextureView(id) => { - let view = used - .views - .add_single(&*texture_view_guard, id) - .ok_or(Error::InvalidTextureView(id))?; - let (pub_usage, internal_use) = self.texture_use_parameters( + let tb = self.create_texture_binding( binding, decl, - view, - "SampledTexture, ReadonlyStorageTexture or WriteonlyStorageTexture", - )?; - Self::create_texture_binding( - view, - internal_use, - pub_usage, + &texture_view_guard, + id, &mut used, &mut used_texture_ranges, + &snatch_guard, )?; let res_index = hal_textures.len(); - hal_textures.push(hal::TextureBinding { - view: view - .raw(&snatch_guard) - .ok_or(Error::InvalidTextureView(id))?, - usage: internal_use, - }); + hal_textures.push(tb); (res_index, 1) } Br::TextureViewArray(ref bindings_array) => { @@ -2269,26 +2342,17 @@ impl<A: HalApi> Device<A> { let res_index = hal_textures.len(); for &id in bindings_array.iter() { - let view = used - .views - .add_single(&*texture_view_guard, id) - .ok_or(Error::InvalidTextureView(id))?; - let (pub_usage, internal_use) = - self.texture_use_parameters(binding, decl, view, - "SampledTextureArray, ReadonlyStorageTextureArray or WriteonlyStorageTextureArray")?; - Self::create_texture_binding( - view, - internal_use, - pub_usage, + let tb = self.create_texture_binding( + binding, + decl, + &texture_view_guard, + id, &mut used, &mut used_texture_ranges, + &snatch_guard, )?; - hal_textures.push(hal::TextureBinding { - view: view - .raw(&snatch_guard) - .ok_or(Error::InvalidTextureView(id))?, - usage: internal_use, - }); + + hal_textures.push(tb); } (res_index, num_bindings) @@ -2762,8 +2826,10 @@ impl<A: HalApi> Device<A> { label: desc.label.to_hal(self.instance_flags), layout: pipeline_layout.raw(), stage: hal::ProgrammableStage { - entry_point: final_entry_point_name.as_ref(), module: shader_module.raw(), + entry_point: final_entry_point_name.as_ref(), + constants: desc.stage.constants.as_ref(), + zero_initialize_workgroup_memory: desc.stage.zero_initialize_workgroup_memory, }, }; @@ -3178,6 +3244,8 @@ impl<A: HalApi> Device<A> { hal::ProgrammableStage { module: vertex_shader_module.raw(), entry_point: &vertex_entry_point_name, + constants: stage_desc.constants.as_ref(), + zero_initialize_workgroup_memory: stage_desc.zero_initialize_workgroup_memory, } }; @@ -3237,6 +3305,10 @@ impl<A: HalApi> Device<A> { Some(hal::ProgrammableStage { module: shader_module.raw(), entry_point: &fragment_entry_point_name, + constants: fragment_state.stage.constants.as_ref(), + zero_initialize_workgroup_memory: fragment_state + .stage + .zero_initialize_workgroup_memory, }) } None => None, @@ -3482,10 +3554,9 @@ impl<A: HalApi> Device<A> { .map_err(DeviceError::from)? }; drop(guard); - let closures = self.lock_life().triage_submissions( - submission_index, - self.command_allocator.lock().as_mut().unwrap(), - ); + let closures = self + .lock_life() + .triage_submissions(submission_index, &self.command_allocator); assert!( closures.is_empty(), "wait_for_submit is not expected to work with closures" @@ -3613,10 +3684,7 @@ impl<A: HalApi> Device<A> { log::error!("failed to wait for the device: {error}"); } let mut life_tracker = self.lock_life(); - let _ = life_tracker.triage_submissions( - current_index, - self.command_allocator.lock().as_mut().unwrap(), - ); + let _ = life_tracker.triage_submissions(current_index, &self.command_allocator); if let Some(device_lost_closure) = life_tracker.device_lost_closure.take() { // It's important to not hold the lock while calling the closure. drop(life_tracker); |