From 9eea31a4aef38a4805266bb6b80297ab16bfc368 Mon Sep 17 00:00:00 2001 From: Brad Werth Date: Wed, 20 Dec 2023 14:43:43 -0800 Subject: [PATCH] Eagerly release GPU resources when we lose the device. (#4851) Co-authored-by: Connor Fitzgerald --- CHANGELOG.md | 1 + wgpu-core/src/device/life.rs | 45 +++++++++++++++++++++++++++++++- wgpu-core/src/device/resource.rs | 31 +++++++++++++++------- 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d82159e0d..8847f8edb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Wgpu now exposes backend feature for the Direct3D 12 (`dx12`) and Metal (`metal` - Added `DownlevelFlags::VERTEX_AND_INSTANCE_INDEX_RESPECTS_RESPECTIVE_FIRST_VALUE_IN_INDIRECT_DRAW` to know if `@builtin(vertex_index)` and `@builtin(instance_index)` will respect the `first_vertex` / `first_instance` in indirect calls. If this is not present, both will always start counting from 0. Currently enabled on all backends except DX12. By @cwfitzgerald in [#4722](https://github.com/gfx-rs/wgpu/pull/4722) - No longer validate surfaces against their allowed extent range on configure. This caused warnings that were almost impossible to avoid. As before, the resulting behavior depends on the compositor. By @wumpf in [#4796](https://github.com/gfx-rs/wgpu/pull/4796) - Added support for the float32-filterable feature. By @almarklein in [#4759](https://github.com/gfx-rs/wgpu/pull/4759) +- GPU buffer memory is released during "lose the device". By @bradwerth in [#4851](https://github.com/gfx-rs/wgpu/pull/4851) - wgpu and wgpu-core features are now documented on docs.rs. By @wumpf in [#4886](https://github.com/gfx-rs/wgpu/pull/4886) - DeviceLostClosure is guaranteed to be invoked exactly once. By @bradwerth in [#4862](https://github.com/gfx-rs/wgpu/pull/4862) diff --git a/wgpu-core/src/device/life.rs b/wgpu-core/src/device/life.rs index 5fb3ad225..08e75ce00 100644 --- a/wgpu-core/src/device/life.rs +++ b/wgpu-core/src/device/life.rs @@ -241,7 +241,7 @@ pub(crate) struct LifetimeTracker { ready_to_map: Vec>>, /// Queue "on_submitted_work_done" closures that were initiated for while there is no - /// currently pending submissions. These cannot be immeidately invoked as they + /// currently pending submissions. These cannot be immediately invoked as they /// must happen _after_ all mapped buffer callbacks are mapped, so we defer them /// here until the next time the device is maintained. work_done_closures: SmallVec<[SubmittedWorkDoneClosure; 1]>, @@ -1017,4 +1017,47 @@ impl LifetimeTracker { } pending_callbacks } + + pub(crate) fn release_gpu_resources(&mut self) { + // This is called when the device is lost, which makes every associated + // resource invalid and unusable. This is an opportunity to release all of + // the underlying gpu resources, even though the objects remain visible to + // the user agent. We purge this memory naturally when resources have been + // moved into the appropriate buckets, so this function just needs to + // initiate movement into those buckets, and it can do that by calling + // "destroy" on all the resources we know about which aren't already marked + // for cleanup. + + // During these iterations, we discard all errors. We don't care! + + // Destroy all the mapped buffers. + for buffer in &self.mapped { + let _ = buffer.destroy(); + } + + // Destroy all the unmapped buffers. + for buffer in &self.ready_to_map { + let _ = buffer.destroy(); + } + + // Destroy all the future_suspected_buffers. + for buffer in &self.future_suspected_buffers { + let _ = buffer.destroy(); + } + + // Destroy the buffers in all active submissions. + for submission in &self.active { + for buffer in &submission.mapped { + let _ = buffer.destroy(); + } + } + + // Destroy all the future_suspected_textures. + // TODO: texture.destroy is not implemented + /* + for texture in &self.future_suspected_textures { + let _ = texture.destroy(); + } + */ + } } diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index cee8d6e94..b89ad9abf 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -381,15 +381,19 @@ impl Device { // our caller. This will complete the steps for both destroy and for // "lose the device". let mut device_lost_invocations = SmallVec::new(); - if !self.is_valid() - && life_tracker.queue_empty() - && life_tracker.device_lost_closure.is_some() - { - device_lost_invocations.push(DeviceLostInvocation { - closure: life_tracker.device_lost_closure.take().unwrap(), - reason: DeviceLostReason::Destroyed, - message: String::new(), - }); + if !self.is_valid() && life_tracker.queue_empty() { + // We can release gpu resources associated with this device. + life_tracker.release_gpu_resources(); + + // If we have a DeviceLostClosure, build an invocation with the + // reason DeviceLostReason::Destroyed and no message. + if life_tracker.device_lost_closure.is_some() { + device_lost_invocations.push(DeviceLostInvocation { + closure: life_tracker.device_lost_closure.take().unwrap(), + reason: DeviceLostReason::Destroyed, + message: String::new(), + }); + } } let closures = UserClosures { @@ -3362,9 +3366,13 @@ impl Device { self.valid.store(false, Ordering::Release); // 1) Resolve the GPUDevice device.lost promise. - let closure = self.lock_life().device_lost_closure.take(); + let mut life_lock = self.lock_life(); + let closure = life_lock.device_lost_closure.take(); if let Some(device_lost_closure) = closure { + // It's important to not hold the lock while calling the closure. + drop(life_lock); device_lost_closure.call(DeviceLostReason::Unknown, message.to_string()); + life_lock = self.lock_life(); } // 2) Complete any outstanding mapAsync() steps. @@ -3374,6 +3382,9 @@ impl Device { // since that will prevent any new work from being added to the queues. // Future calls to poll_devices will continue to check the work queues // until they are cleared, and then drop the device. + + // Eagerly release GPU resources. + life_lock.release_gpu_resources(); } }