diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf index c7b4cbace88..6e10ffc6a3e 100644 --- a/components/script/dom/bindings/codegen/Bindings.conf +++ b/components/script/dom/bindings/codegen/Bindings.conf @@ -169,6 +169,7 @@ DOMInterfaces = { }, 'GPUDevice': { + 'weakReferenceable': True, # for usage in GlobalScope https://github.com/servo/servo/issues/32519 'inRealms': ['PopErrorScope', 'CreateComputePipelineAsync', 'CreateRenderPipelineAsync'], } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index ca4f5e7cd76..27138a15d84 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -322,7 +322,7 @@ pub struct GlobalScope { gpu_id_hub: Arc>, /// WebGPU devices - gpu_devices: DomRefCell>>, + gpu_devices: DomRefCell>>, // https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute #[ignore_malloc_size_of = "mozjs"] @@ -3091,7 +3091,16 @@ impl GlobalScope { pub fn add_gpu_device(&self, device: &GPUDevice) { self.gpu_devices .borrow_mut() - .insert(device.id(), Dom::from_ref(device)); + .insert(device.id(), WeakRef::new(device)); + } + + pub fn remove_gpu_device(&self, device: WebGPUDevice) { + let device = self + .gpu_devices + .borrow_mut() + .remove(&device) + .expect("GPUDevice should still be in devices hashmap"); + assert!(device.root().is_none()) } pub fn gpu_device_lost(&self, device: WebGPUDevice, reason: DeviceLostReason, msg: String) { @@ -3100,15 +3109,24 @@ impl GlobalScope { DeviceLostReason::Destroyed => GPUDeviceLostReason::Destroyed, }; let _ac = enter_realm(&*self); - self.gpu_devices + if let Some(device) = self + .gpu_devices .borrow_mut() - .remove(&device) - .expect("GPUDevice should still exists") - .lose(reason, msg); + .get_mut(&device) + .expect("GPUDevice should still be in devices hashmap") + .root() + { + device.lose(reason, msg); + } } pub fn handle_uncaptured_gpu_error(&self, device: WebGPUDevice, error: webgpu::Error) { - if let Some(gpu_device) = self.gpu_devices.borrow().get(&device) { + if let Some(gpu_device) = self + .gpu_devices + .borrow() + .get(&device) + .and_then(|device| device.root()) + { gpu_device.fire_uncaptured_error(error); } else { warn!("Recived error for lost GPUDevice!") diff --git a/components/script/dom/gpudevice.rs b/components/script/dom/gpudevice.rs index 9e7c8c76b09..da1432e99b6 100644 --- a/components/script/dom/gpudevice.rs +++ b/components/script/dom/gpudevice.rs @@ -1031,13 +1031,6 @@ impl AsyncWGPUListener for GPUDevice { impl Drop for GPUDevice { fn drop(&mut self) { - if let Err(e) = self - .channel - .0 - .send(WebGPURequest::DestroyDevice(self.device.0)) - { - warn!("Failed to send DestroyDevice ({:?}) ({})", self.device.0, e); - } if let Err(e) = self .channel .0 diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 1d3fb58766a..6fe8f8f4ead 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -92,7 +92,7 @@ use style::dom::OpaqueNode; use style::thread_state::{self, ThreadState}; use time::precise_time_ns; use url::Position; -use webgpu::WebGPUMsg; +use webgpu::{WebGPUDevice, WebGPUMsg}; use webrender_api::DocumentId; use webrender_traits::WebRenderScriptApi; @@ -2423,7 +2423,14 @@ impl ScriptThread { fn handle_msg_from_webgpu_server(&self, msg: WebGPUMsg) { match msg { WebGPUMsg::FreeAdapter(id) => self.gpu_id_hub.lock().kill_adapter_id(id), - WebGPUMsg::FreeDevice(id) => self.gpu_id_hub.lock().kill_device_id(id), + WebGPUMsg::FreeDevice { + device_id, + pipeline_id, + } => { + self.gpu_id_hub.lock().kill_device_id(device_id); + let global = self.documents.borrow().find_global(pipeline_id).unwrap(); + global.remove_gpu_device(WebGPUDevice(device_id)); + }, WebGPUMsg::FreeBuffer(id) => self.gpu_id_hub.lock().kill_buffer_id(id), WebGPUMsg::FreePipelineLayout(id) => self.gpu_id_hub.lock().kill_pipeline_layout_id(id), WebGPUMsg::FreeComputePipeline(id) => { diff --git a/components/webgpu/script_messages.rs b/components/webgpu/script_messages.rs index 083f813901e..15fe198386d 100644 --- a/components/webgpu/script_messages.rs +++ b/components/webgpu/script_messages.rs @@ -25,7 +25,10 @@ pub enum DeviceLostReason { #[derive(Clone, Debug, Deserialize, Serialize)] pub enum WebGPUMsg { FreeAdapter(AdapterId), - FreeDevice(DeviceId), + FreeDevice { + device_id: DeviceId, + pipeline_id: PipelineId, + }, FreeBuffer(BufferId), FreePipelineLayout(PipelineLayoutId), FreeComputePipeline(ComputePipelineId), diff --git a/components/webgpu/wgpu_thread.rs b/components/webgpu/wgpu_thread.rs index 499d4532ea2..f98fd2869a3 100644 --- a/components/webgpu/wgpu_thread.rs +++ b/components/webgpu/wgpu_thread.rs @@ -43,7 +43,9 @@ pub(crate) struct DeviceScope { pub device_id: DeviceId, pub pipeline_id: PipelineId, /// - pub error_scope_stack: Vec, + /// + /// Is `None` if device is lost + pub error_scope_stack: Option>, // TODO: // Queue for this device (to remove transmutes) // queue_id: QueueId, @@ -56,7 +58,7 @@ impl DeviceScope { Self { device_id, pipeline_id, - error_scope_stack: Vec::new(), + error_scope_stack: Some(Vec::new()), } } } @@ -583,9 +585,17 @@ impl WGPU { }, WebGPURequest::DropDevice(device_id) => { let global = &self.global; - // device_drop also calls device lost callback gfx_select!(device_id => global.device_drop(device_id)); - if let Err(e) = self.script_sender.send(WebGPUMsg::FreeDevice(device_id)) { + let device_scope = self + .devices + .lock() + .unwrap() + .remove(&device_id) + .expect("Device should not be dropped by this point"); + if let Err(e) = self.script_sender.send(WebGPUMsg::FreeDevice { + device_id, + pipeline_id: device_scope.pipeline_id, + }) { warn!("Unable to send FreeDevice({:?}) ({:?})", device_id, e); }; }, @@ -691,31 +701,36 @@ impl WGPU { let devices = Arc::clone(&self.devices); let callback = DeviceLostClosure::from_rust(Box::from(move |reason, msg| { - let _ = devices.lock().unwrap().remove(&device_id); let reason = match reason { wgt::DeviceLostReason::Unknown => { - Some(crate::DeviceLostReason::Unknown) + crate::DeviceLostReason::Unknown }, wgt::DeviceLostReason::Destroyed => { - Some(crate::DeviceLostReason::Destroyed) + crate::DeviceLostReason::Destroyed }, - wgt::DeviceLostReason::Dropped => None, + wgt::DeviceLostReason::Dropped => return, // we handle this in WebGPUMsg::FreeDevice wgt::DeviceLostReason::ReplacedCallback => { panic!("DeviceLost callback should only be set once") }, wgt::DeviceLostReason::DeviceInvalid => { - Some(crate::DeviceLostReason::Unknown) + crate::DeviceLostReason::Unknown }, }; - if let Some(reason) = reason { - if let Err(e) = script_sender.send(WebGPUMsg::DeviceLost { - device: WebGPUDevice(device_id), - pipeline_id, - reason, - msg, - }) { - warn!("Failed to send WebGPUMsg::DeviceLost: {e}"); - } + // make device lost by removing error scopes stack + let _ = devices + .lock() + .unwrap() + .get_mut(&device_id) + .expect("Device should not be dropped by this point") + .error_scope_stack + .take(); + if let Err(e) = script_sender.send(WebGPUMsg::DeviceLost { + device: WebGPUDevice(device_id), + pipeline_id, + reason, + msg, + }) { + warn!("Failed to send WebGPUMsg::DeviceLost: {e}"); } })); gfx_select!(device_id => global.device_set_device_lost_closure(device_id, callback)); @@ -1105,8 +1120,11 @@ impl WGPU { WebGPURequest::PushErrorScope { device_id, filter } => { // let mut devices = self.devices.lock().unwrap(); - if let Some(device_scope) = devices.get_mut(&device_id) { - device_scope.error_scope_stack.push(ErrorScope::new(filter)); + let device_scope = devices + .get_mut(&device_id) + .expect("Device should not be dropped by this point"); + if let Some(error_scope_stack) = &mut device_scope.error_scope_stack { + error_scope_stack.push(ErrorScope::new(filter)); } // else device is lost }, WebGPURequest::DispatchError { device_id, error } => { @@ -1114,9 +1132,12 @@ impl WGPU { }, WebGPURequest::PopErrorScope { device_id, sender } => { // - if let Some(device_scope) = self.devices.lock().unwrap().get_mut(&device_id) - { - if let Some(error_scope) = device_scope.error_scope_stack.pop() { + let mut devices = self.devices.lock().unwrap(); + let device_scope = devices + .get_mut(&device_id) + .expect("Device should not be dropped by this point"); + if let Some(error_scope_stack) = &mut device_scope.error_scope_stack { + if let Some(error_scope) = error_scope_stack.pop() { if let Err(e) = sender.send(Some(Ok(WebGPUResponse::PoppedErrorScope(Ok( // TODO: Do actual selection instead of selecting first error @@ -1167,9 +1188,12 @@ impl WGPU { /// fn dispatch_error(&mut self, device_id: id::DeviceId, error: Error) { - if let Some(device_scope) = self.devices.lock().unwrap().get_mut(&device_id) { - if let Some(error_scope) = device_scope - .error_scope_stack + let mut devices = self.devices.lock().unwrap(); + let device_scope = devices + .get_mut(&device_id) + .expect("Device should not be dropped by this point"); + if let Some(error_scope_stack) = &mut device_scope.error_scope_stack { + if let Some(error_scope) = error_scope_stack .iter_mut() .rev() .find(|error_scope| error_scope.filter == error.filter())