[core] Use internal locking in CommandAllocator.

Move the `Mutex` in `Device::command_allocator` inside the
`CommandAllocator` type itself, allowing it to be passed by shared
reference instead of mutable reference.

Passing `CommandAllocator` to functions like
`PendingWrites::post_submit` by mutable reference requires the caller
to acquire and hold the mutex for the entire time the callee runs, but
`CommandAllocator` is just a recycling pool, with very simple
invariants; there's no reason to hold the lock for a long time.
This commit is contained in:
Jim Blandy 2024-04-15 12:52:19 -07:00 committed by Erich Gubler
parent b9781ee6e2
commit bab6f53e86
5 changed files with 27 additions and 34 deletions

View File

@ -2,6 +2,8 @@ use crate::hal_api::HalApi;
use crate::resource_log;
use hal::Device as _;
use parking_lot::Mutex;
/// A pool of free [`wgpu_hal::CommandEncoder`]s, owned by a `Device`.
///
/// Each encoder in this list is in the "closed" state.
@ -12,13 +14,13 @@ use hal::Device as _;
/// [ce]: wgpu_hal::CommandEncoder
/// [cb]: wgpu_hal::Api::CommandBuffer
pub(crate) struct CommandAllocator<A: HalApi> {
free_encoders: Vec<A::CommandEncoder>,
free_encoders: Mutex<Vec<A::CommandEncoder>>,
}
impl<A: HalApi> CommandAllocator<A> {
pub(crate) fn new() -> Self {
Self {
free_encoders: Vec::new(),
free_encoders: Mutex::new(Vec::new()),
}
}
@ -27,11 +29,12 @@ impl<A: HalApi> CommandAllocator<A> {
/// If we have free encoders in the pool, take one of those. Otherwise,
/// create a new one on `device`.
pub(crate) fn acquire_encoder(
&mut self,
&self,
device: &A::Device,
queue: &A::Queue,
) -> Result<A::CommandEncoder, hal::DeviceError> {
match self.free_encoders.pop() {
let mut free_encoders = self.free_encoders.lock();
match free_encoders.pop() {
Some(encoder) => Ok(encoder),
None => unsafe {
let hal_desc = hal::CommandEncoderDescriptor { label: None, queue };
@ -41,19 +44,18 @@ impl<A: HalApi> CommandAllocator<A> {
}
/// Add `encoder` back to the free pool.
pub(crate) fn release_encoder(&mut self, encoder: A::CommandEncoder) {
self.free_encoders.push(encoder);
pub(crate) fn release_encoder(&self, encoder: A::CommandEncoder) {
let mut free_encoders = self.free_encoders.lock();
free_encoders.push(encoder);
}
/// Free the pool of command encoders.
///
/// This is only called when the `Device` is dropped.
pub(crate) fn dispose(self, device: &A::Device) {
resource_log!(
"CommandAllocator::dispose encoders {}",
self.free_encoders.len()
);
for cmd_encoder in self.free_encoders {
pub(crate) fn dispose(&self, device: &A::Device) {
let mut free_encoders = self.free_encoders.lock();
resource_log!("CommandAllocator::dispose encoders {}", free_encoders.len());
for cmd_encoder in free_encoders.drain(..) {
unsafe {
device.destroy_command_encoder(cmd_encoder);
}

View File

@ -1351,9 +1351,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,

View File

@ -361,7 +361,7 @@ impl<A: HalApi> LifetimeTracker<A> {
pub fn triage_submissions(
&mut self,
last_done: SubmissionIndex,
command_allocator: &mut crate::command::CommandAllocator<A>,
command_allocator: &crate::command::CommandAllocator<A>,
) -> SmallVec<[SubmittedWorkDoneClosure; 1]> {
profiling::scope!("triage_submissions");

View File

@ -258,7 +258,7 @@ impl<A: HalApi> PendingWrites<A> {
#[must_use]
fn post_submit(
&mut self,
command_allocator: &mut CommandAllocator<A>,
command_allocator: &CommandAllocator<A>,
device: &A::Device,
queue: &A::Queue,
) -> Option<EncoderInFlight<A>> {
@ -1530,7 +1530,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(),
) {

View File

@ -97,7 +97,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<command::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
@ -165,7 +165,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,7 +223,7 @@ impl<A: HalApi> Device<A> {
let fence =
unsafe { raw_device.create_fence() }.map_err(|_| CreateDeviceError::OutOfMemory)?;
let mut com_alloc = command::CommandAllocator::new();
let com_alloc = command::CommandAllocator::new();
let pending_encoder = com_alloc
.acquire_encoder(&raw_device, raw_queue)
.map_err(|_| CreateDeviceError::OutOfMemory)?;
@ -269,7 +269,7 @@ 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: com_alloc,
active_submission_index: AtomicU64::new(0),
fence: RwLock::new(Some(fence)),
snatchable_lock: unsafe { SnatchLock::new() },
@ -423,10 +423,8 @@ 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(),
);
let submission_closures =
life_tracker.triage_submissions(last_done_index, &self.command_allocator);
{
// Normally, `temp_suspected` exists only to save heap
@ -3483,10 +3481,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"
@ -3614,10 +3611,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);