From 3c81052f0d199c8bf8d874f3b0a79b16ce040847 Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Thu, 22 Oct 2026 10:07:02 +0200 Subject: [PATCH 01/14] [core] Destruct BufferMappingOperation into mode and callback arguments Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/buffer.rs | 11 +-- player/tests/player/main.rs | 6 +- wgpu-core-remote/src/global/device.rs | 9 +-- wgpu-core/src/device/mod.rs | 8 +-- wgpu-core/src/resource.rs | 100 +++++++++++++------------- wgpu/src/backend/wgpu_core.rs | 11 ++- 6 files changed, 71 insertions(+), 74 deletions(-) diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index 77cb456a9bb..a7e9d4fc510 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -191,14 +191,9 @@ impl GPUBuffer { sender.send(status).unwrap(); }); - self.wgpu_buffer.map_async( - offset, - size, - wgpu_core::resource::BufferMapOperation { - mode, - callback: Some(callback), - }, - ); + self + .wgpu_buffer + .map_async(mode, offset, size, Some(callback)); } let done = Rc::new(RefCell::new(false)); diff --git a/player/tests/player/main.rs b/player/tests/player/main.rs index 1a67f3726b4..859c00df571 100644 --- a/player/tests/player/main.rs +++ b/player/tests/player/main.rs @@ -97,12 +97,10 @@ impl Test<'_> { player .resolve_buffer_id(expect.buffer) .map_async( + wgt::MapMode::Read, expect.offset, Some(expect.data.len() as u64), - wgc::resource::BufferMapOperation { - mode: wgt::MapMode::Read, - callback: Some(Box::new(map_callback)), - }, + Some(Box::new(map_callback)), ) .unwrap(); } diff --git a/wgpu-core-remote/src/global/device.rs b/wgpu-core-remote/src/global/device.rs index 69d1e0e42bf..6d0b2d7cee9 100644 --- a/wgpu-core-remote/src/global/device.rs +++ b/wgpu-core-remote/src/global/device.rs @@ -17,7 +17,7 @@ use wgpu_core::{ self, ProgrammableStageDescriptor, RenderPipelineVertexProcessor, ResolvedGeneralRenderPipelineDescriptor, }, - resource::{self, BufferAccessError, BufferMapOperation, BufferMapping, CreateBufferError}, + resource::{self, BufferAccessError, BufferMapCallback, BufferMapping, CreateBufferError}, Label, LabelHelpers, SubmissionIndex, }; @@ -28,7 +28,7 @@ use crate::{ registry::Registry, }; -use wgt::{error::WebGpuError, BufferAddress}; +use wgt::{error::WebGpuError, BufferAddress, MapMode}; pub use wgpu_core_remote_types::binding_model::*; @@ -1273,15 +1273,16 @@ impl Global { pub fn buffer_map_async( &self, buffer_id: id::BufferId, + mode: MapMode, offset: BufferAddress, size: Option, - op: BufferMapOperation, + callback: Option, ) -> Option { let hub = self.hub.borrow(); let buffer = hub.buffers.get(buffer_id); - buffer.map_async(offset, size, op) + buffer.map_async(mode, offset, size, callback) } pub fn buffer_get_mapped_range( diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index a5da18f5cbc..8caf7b5eff2 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -5,7 +5,7 @@ use crate::{ binding_model, ray_tracing::BlasCompactReadyPendingClosure, resource::{ - Buffer, BufferAccessError, BufferAccessResult, BufferMapOperation, Labeled, + Buffer, BufferAccessError, BufferAccessResult, BufferMapCallback, Labeled, RawResourceAccess, ResourceErrorIdent, }, snatch::SnatchGuard, @@ -163,7 +163,7 @@ impl RenderPassContext { } } -pub type BufferMapPendingClosure = (BufferMapOperation, BufferAccessResult); +pub type BufferMapPendingClosure = (Option, BufferAccessResult); #[must_use] #[derive(Default)] @@ -189,8 +189,8 @@ impl UserClosures { // Mappings _must_ be fired before submissions, as the spec requires all mapping callbacks that are registered before // a on_submitted_work_done callback to be fired before the on_submitted_work_done callback. - for (mut operation, status) in self.mappings { - if let Some(callback) = operation.callback.take() { + for (mut callback, status) in self.mappings { + if let Some(callback) = callback.take() { callback(status); } } diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 81d048fa49b..3fc68489405 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -348,20 +348,6 @@ pub type BufferMapCallback = Box; -pub struct BufferMapOperation { - pub mode: MapMode, - pub callback: Option, -} - -impl fmt::Debug for BufferMapOperation { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BufferMapOperation") - .field("mode", &self.mode) - .field("callback", &self.callback.as_ref().map(|_| "?")) - .finish() - } -} - #[derive(Clone, Debug, Error)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] @@ -508,14 +494,25 @@ impl WebGpuError for InvalidResourceError { pub type BufferAccessResult = Result<(), BufferAccessError>; -#[derive(Debug)] pub(crate) struct BufferPendingMapping { pub(crate) range: Range, - pub(crate) op: BufferMapOperation, + pub(crate) callback: Option, + pub(crate) mode: MapMode, // hold the parent alive while the mapping is active pub(crate) _parent_buffer: Arc, } +impl fmt::Debug for BufferPendingMapping { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BufferPendingMapping") + .field("range", &self.range) + .field("callback", &self.callback.as_ref().map(|_| "?")) + .field("mode", &self.mode) + .field("_parent_buffer", &self._parent_buffer) + .finish() + } +} + pub type BufferDescriptor<'a> = wgt::BufferDescriptor>; #[derive(Debug)] @@ -565,13 +562,13 @@ impl Drop for Buffer { false } BufferMapState::Waiting(buffer_pending_mapping) => { - if buffer_pending_mapping.op.callback.is_some() { + if buffer_pending_mapping.callback.is_some() { let result = Err(BufferAccessError::DestroyedResource( DestroyedResourceError(self.error_ident()), )); self.device .deferred_buffer_map_pending_closures - .push((buffer_pending_mapping.op, result)); + .push((buffer_pending_mapping.callback, result)); } false } @@ -777,21 +774,22 @@ impl Buffer { /// `op.callback` is guaranteed to be called, regardless of the outcome. pub fn map_async( self: &Arc, + mode: MapMode, offset: wgt::BufferAddress, size: Option, - op: BufferMapOperation, + callback: Option, ) -> Option { profiling::scope!("Buffer::map_async"); api_log!( - "Buffer::map_async {:?} offset {offset:?} size {size:?} op: {op:?}", + "Buffer::map_async {:?} mode {mode:?} offset {offset:?} size {size:?}", Arc::as_ptr(self) ); - self.try_map_async(offset, size, op) - .map_err(|(mut operation, err)| { + self.try_map_async(mode, offset, size, callback) + .map_err(|(mut callback, err)| { self.device .handle_error(err.clone(), Some(&self.label), "Buffer::map_async"); - if let Some(callback) = operation.callback.take() { + if let Some(callback) = callback.take() { callback(Err(err)); } }) @@ -822,10 +820,11 @@ impl Buffer { /// it does not mean that the buffer has already been mapped. fn try_map_async( self: &Arc, + mode: MapMode, offset: wgt::BufferAddress, size: Option, - op: BufferMapOperation, - ) -> Result { + callback: Option, + ) -> Result, BufferAccessError)> { let range_size = if let Some(size) = size { size } else { @@ -833,19 +832,22 @@ impl Buffer { }; if let Err(e) = self.check_is_valid() { - return Err((op, e.into())); + return Err((callback, e.into())); } if !offset.is_multiple_of(wgt::MAP_ALIGNMENT) { - return Err((op, BufferAccessError::UnalignedOffset { offset })); + return Err((callback, BufferAccessError::UnalignedOffset { offset })); } if !range_size.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { - return Err((op, BufferAccessError::UnalignedRangeSize { range_size })); + return Err(( + callback, + BufferAccessError::UnalignedRangeSize { range_size }, + )); } if offset > self.size { return Err(( - op, + callback, BufferAccessError::MapStartOffsetOverrun { offset, buffer_size: self.size, @@ -855,7 +857,7 @@ impl Buffer { // NOTE: Should never underflow because of our earlier check. if range_size > self.size - offset { return Err(( - op, + callback, BufferAccessError::MapEndOffsetOverrun { offset, size: range_size, @@ -868,41 +870,42 @@ impl Buffer { if !offset.is_multiple_of(wgt::MAP_ALIGNMENT) || !end_offset.is_multiple_of(wgt::COPY_BUFFER_ALIGNMENT) { - return Err((op, BufferAccessError::UnalignedRange)); + return Err((callback, BufferAccessError::UnalignedRange)); } - let (pub_usage, internal_use) = match op.mode { + let (pub_usage, internal_use) = match mode { MapMode::Read => (wgt::BufferUsages::MAP_READ, wgt::BufferUses::MAP_READ), MapMode::Write => (wgt::BufferUsages::MAP_WRITE, wgt::BufferUses::MAP_WRITE), }; if let Err(e) = self.check_usage(pub_usage) { - return Err((op, e.into())); + return Err((callback, e.into())); } let device = &self.device; if let Err(e) = device.check_is_valid() { - return Err((op, e.into())); + return Err((callback, e.into())); } let submit_index = { let snatch_guard = device.snatchable_lock.read(); if let Err(e) = self.check_destroyed(&snatch_guard) { - return Err((op, e.into())); + return Err((callback, e.into())); } { let map_state = &mut *self.map_state.write(); *map_state = match *map_state { BufferMapState::Init { .. } | BufferMapState::Active { .. } => { - return Err((op, BufferAccessError::AlreadyMapped)); + return Err((callback, BufferAccessError::AlreadyMapped)); } BufferMapState::Waiting(_) => { - return Err((op, BufferAccessError::MapAlreadyPending)); + return Err((callback, BufferAccessError::MapAlreadyPending)); } BufferMapState::Idle => BufferMapState::Waiting(BufferPendingMapping { range: offset..end_offset, - op, + mode, + callback, _parent_buffer: self.clone(), }), }; @@ -913,10 +916,11 @@ impl Buffer { Err(err) => { let state = mem::replace(&mut *self.map_state.write(), BufferMapState::Idle); - let BufferMapState::Waiting(BufferPendingMapping { op, .. }) = state else { + let BufferMapState::Waiting(BufferPendingMapping { callback, .. }) = state + else { unreachable!(); }; - return Err((op, err)); + return Err((callback, err)); } Ok(()) => { // Schedule the buffer map in the lifetime tracker. @@ -953,8 +957,8 @@ impl Buffer { } else { // We don't have a queue, so go ahead and map the buffer. // We can safely unwrap below since we just set the `map_state` to `BufferMapState::Waiting`. - let (mut operation, status) = self.map(&device.snatchable_lock.read()).unwrap(); - if let Some(callback) = operation.callback.take() { + let (mut callback, status) = self.map(&device.snatchable_lock.read()).unwrap(); + if let Some(callback) = callback.take() { callback(status); } Ok(0) @@ -1087,7 +1091,7 @@ impl Buffer { let status = if let Err(error) = self.device.check_is_valid() { Err(error.into()) } else if pending_mapping.range.start != pending_mapping.range.end { - let host = pending_mapping.op.mode; + let host = pending_mapping.mode; let size = pending_mapping.range.end - pending_mapping.range.start; match crate::device::map_buffer( self, @@ -1113,19 +1117,19 @@ impl Buffer { is_coherent: true, }, range: pending_mapping.range, - host: pending_mapping.op.mode, + host: pending_mapping.mode, }; Ok(()) }; - Some((pending_mapping.op, status)) + Some((pending_mapping.callback, status)) } // Note: This must not be called while holding a lock. pub fn unmap(self: &Arc) { profiling::scope!("unmap", "Buffer"); api_log!("Buffer::unmap {:?}", Arc::as_ptr(self)); - if let Some((mut operation, status)) = self.unmap_inner() { - if let Some(callback) = operation.callback.take() { + if let Some((mut callback, status)) = self.unmap_inner() { + if let Some(callback) = callback.take() { callback(status); } } @@ -1211,7 +1215,7 @@ impl Buffer { } BufferMapState::Idle => None, BufferMapState::Waiting(pending) => { - Some((pending.op, Err(BufferAccessError::MapAborted))) + Some((pending.callback, Err(BufferAccessError::MapAborted))) } BufferMapState::Active { mapping, diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index c295e92744a..cd3f910b4ed 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -1821,16 +1821,15 @@ impl dispatch::BufferInterface for CoreBuffer { range: Range, callback: dispatch::BufferMapCallback, ) { - let operation = wgc::resource::BufferMapOperation { + self.wgpu_buffer.map_async( mode, - callback: Some(Box::new(|status| { + range.start, + Some(range.end - range.start), + Some(Box::new(|status| { let res = status.map_err(|_| crate::BufferAsyncError); callback(res); })), - }; - - self.wgpu_buffer - .map_async(range.start, Some(range.end - range.start), operation); + ); } fn get_mapped_range( From 7655b206d630b3d7cd1680c24847d8d10389e06f Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:47:05 +0200 Subject: [PATCH 02/14] [core] Do device validity check at the start of `try_map_async` this is important as we need to return Abort error instead of Operation error which is raised on invalid buffer Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- wgpu-core/src/resource.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 3fc68489405..38d8e3babf2 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -831,6 +831,11 @@ impl Buffer { self.size.saturating_sub(offset) }; + let device = &self.device; + if let Err(e) = device.check_is_valid() { + return Err((callback, e.into())); + } + if let Err(e) = self.check_is_valid() { return Err((callback, e.into())); } @@ -882,11 +887,6 @@ impl Buffer { return Err((callback, e.into())); } - let device = &self.device; - if let Err(e) = device.check_is_valid() { - return Err((callback, e.into())); - } - let submit_index = { let snatch_guard = device.snatchable_lock.read(); if let Err(e) = self.check_destroyed(&snatch_guard) { From dfb7bafcdbcbace6abb4eb5a74b88ce639550574 Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:48:12 +0200 Subject: [PATCH 03/14] [deno] Map BufferErrors to Abort/Operation as in spec Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/buffer.rs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index a7e9d4fc510..66f34c5af03 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -39,15 +39,12 @@ pub enum BufferError { #[class(generic)] #[error(transparent)] Canceled(#[from] oneshot::Canceled), - #[class("DOMExceptionOperationError")] - #[error(transparent)] - Access(wgpu_core::resource::BufferAccessError), #[class("DOMExceptionAbortError")] #[error("{0}")] - Aborted(&'static str), + Aborted(String), #[class("DOMExceptionOperationError")] #[error("{0}")] - Operation(&'static str), + Operation(String), #[class(inherit)] #[error(transparent)] Other(#[from] JsErrorBox), @@ -56,10 +53,13 @@ pub enum BufferError { impl From for BufferError { fn from(err: wgpu_core::resource::BufferAccessError) -> Self { match err { - wgpu_core::resource::BufferAccessError::Device( + e @ wgpu_core::resource::BufferAccessError::Device( wgpu_core::device::DeviceError::Lost, - ) => BufferError::Aborted("Device lost"), - err => BufferError::Access(err), + ) => BufferError::Aborted(e.to_string()), + e @ wgpu_core::resource::BufferAccessError::MapAborted => { + BufferError::Aborted(e.to_string()) + } + e => BufferError::Operation(e.to_string()), } } } @@ -168,7 +168,7 @@ impl GPUBuffer { let write_mode = (mode & 0x0002) == 0x0002; if (read_mode && write_mode) || (!read_mode && !write_mode) { return Err(BufferError::Operation( - "exactly one of READ or WRITE map mode must be set", + "exactly one of READ or WRITE map mode must be set".to_string(), )); } @@ -253,30 +253,34 @@ impl GPUBuffer { views, } = &mut *self.map_state.borrow_mut() else { - return Err(BufferError::Operation("Buffer is not mapped")); + return Err(BufferError::Operation("Buffer is not mapped".to_string())); }; if !(offset.is_multiple_of(8)) { - return Err(BufferError::Operation("Offset must be a multiple of 8")); + return Err(BufferError::Operation( + "Offset must be a multiple of 8".to_string(), + )); } if !(size.is_multiple_of(4)) { - return Err(BufferError::Operation("Size must be a multiple of 4")); + return Err(BufferError::Operation( + "Size must be a multiple of 4".to_string(), + )); } if !(offset >= range.start) { - return Err(BufferError::Operation("Offset is out of range")); + return Err(BufferError::Operation("Offset is out of range".to_string())); } if !(offset + size <= range.end) { - return Err(BufferError::Operation("Size is out of range")); + return Err(BufferError::Operation("Size is out of range".to_string())); } let range = offset..(offset + size); if views.iter().any(|view| range_overlap(&view.range, &range)) { return Err(BufferError::Operation( - "Overlapping mapped ranges are not allowed", + "Overlapping mapped ranges are not allowed".to_string(), )); } From b815d9762f4dd46852fcfe56ce424994fc95b7bb Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:50:41 +0200 Subject: [PATCH 04/14] [core] Support passing MapMode as (potentially invalid) bitflags Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- Cargo.lock | 1 + deno_webgpu/buffer.rs | 28 ++++------- player/src/lib.rs | 7 ++- player/tests/player/main.rs | 2 +- wgpu-core-remote-types/Cargo.toml | 1 + wgpu-core-remote-types/src/lib.rs | 9 ++++ wgpu-core-remote/src/global/device.rs | 11 +++-- wgpu-core/src/device/resource.rs | 3 +- wgpu-core/src/resource.rs | 69 +++++++++++++++++++++++---- wgpu/src/backend/wgpu_core.rs | 2 +- 10 files changed, 96 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c5a7ca7c92..ce2210e40e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4942,6 +4942,7 @@ dependencies = [ name = "wgpu-core-remote-types" version = "30.0.0" dependencies = [ + "bitflags 2.13.2", "hashbrown 0.17.1", "naga-types", "serde", diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index 66f34c5af03..7c8534fe2c4 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -164,21 +164,6 @@ impl GPUBuffer { #[webidl(default = 0)] offset: u64, #[webidl] size: Option, ) -> Result<(), BufferError> { - let read_mode = (mode & 0x0001) == 0x0001; - let write_mode = (mode & 0x0002) == 0x0002; - if (read_mode && write_mode) || (!read_mode && !write_mode) { - return Err(BufferError::Operation( - "exactly one of READ or WRITE map mode must be set".to_string(), - )); - } - - let mode = if read_mode { - MapMode::Read - } else { - assert!(write_mode); - MapMode::Write - }; - { *self.map_state.borrow_mut() = BufferMapState::Pending; } @@ -191,9 +176,12 @@ impl GPUBuffer { sender.send(status).unwrap(); }); - self - .wgpu_buffer - .map_async(mode, offset, size, Some(callback)); + self.wgpu_buffer.map_async( + wgpu_core::resource::MapModeFlags::from_bits_retain(mode), + offset, + size, + Some(callback), + ); } let done = Rc::new(RefCell::new(false)); @@ -222,7 +210,7 @@ impl GPUBuffer { let mapping = self.wgpu_buffer.get_mapped_range(offset, size)?; - if mode == MapMode::Read { + if mapping.mode() == MapMode::Read { let mut data = self.data.borrow_mut(); let data = data .as_mut() @@ -232,7 +220,7 @@ impl GPUBuffer { } self.map_state.replace(BufferMapState::Mapped { - mode, + mode: mapping.mode(), range: offset..(offset + mapping.len()), views: vec![], }); diff --git a/player/src/lib.rs b/player/src/lib.rs index 7cc3caad2ae..c008ffb5f3a 100644 --- a/player/src/lib.rs +++ b/player/src/lib.rs @@ -382,7 +382,12 @@ impl Player { queue.write_buffer(buffer, offset, &bin[..size.try_into().unwrap()]); } else { device - .set_buffer_data(&buffer, offset, &bin[..size.try_into().unwrap()]) + .set_buffer_data( + &buffer, + offset, + &bin[..size.try_into().unwrap()], + wgt::MapMode::Write, + ) .expect("Device::set_buffer_data error"); } } diff --git a/player/tests/player/main.rs b/player/tests/player/main.rs index 859c00df571..0fd4aa3c7f6 100644 --- a/player/tests/player/main.rs +++ b/player/tests/player/main.rs @@ -54,7 +54,7 @@ struct Test<'a> { actions: Vec>, } -fn map_callback(status: Result<(), wgc::resource::BufferAccessError>) { +fn map_callback(status: Result) { if let Err(e) = status { panic!("Buffer map error: {e}"); } diff --git a/wgpu-core-remote-types/Cargo.toml b/wgpu-core-remote-types/Cargo.toml index 55d66252465..777eb2d13cd 100644 --- a/wgpu-core-remote-types/Cargo.toml +++ b/wgpu-core-remote-types/Cargo.toml @@ -28,6 +28,7 @@ targets = [ ] [dependencies] +bitflags = { workspace = true, features = ["serde"] } hashbrown = { workspace = true } naga-types = { workspace = true } serde = { workspace = true } diff --git a/wgpu-core-remote-types/src/lib.rs b/wgpu-core-remote-types/src/lib.rs index 767989e6dc0..8b643790132 100644 --- a/wgpu-core-remote-types/src/lib.rs +++ b/wgpu-core-remote-types/src/lib.rs @@ -199,3 +199,12 @@ pub enum BufferMapError { Aborted(String), Operation(String), } + +bitflags::bitflags! { + /// Corresponds to [WebGPU `GPUMapModeFlags`](https://gpuweb.github.io/gpuweb/#dictdef-gpumapmodeflags). + #[derive(serde::Serialize, serde::Deserialize)] + pub struct MapMode: u32 { + const READ = 0x0001; + const WRITE = 0x0002; + } +} diff --git a/wgpu-core-remote/src/global/device.rs b/wgpu-core-remote/src/global/device.rs index 6d0b2d7cee9..d0f7df71291 100644 --- a/wgpu-core-remote/src/global/device.rs +++ b/wgpu-core-remote/src/global/device.rs @@ -3,7 +3,7 @@ use core::ops::Deref; use wgpu_core_remote_types::{ encoders::{RenderBundleDescriptor, RenderBundleEncoderDescriptor}, pipelines::{ComputePipelineDescriptor, RenderPipelineDescriptor}, - BufferDescriptor, ExternalTextureDescriptor, PipelineError, PipelineLayoutDescriptor, + BufferDescriptor, ExternalTextureDescriptor, MapMode, PipelineError, PipelineLayoutDescriptor, QuerySetDescriptor, SamplerDescriptor, ShaderModuleDescriptor, TextureDescriptor, TextureViewDescriptor, }; @@ -28,7 +28,7 @@ use crate::{ registry::Registry, }; -use wgt::{error::WebGpuError, BufferAddress, MapMode}; +use wgt::{error::WebGpuError, BufferAddress}; pub use wgpu_core_remote_types::binding_model::*; @@ -1282,7 +1282,12 @@ impl Global { let buffer = hub.buffers.get(buffer_id); - buffer.map_async(mode, offset, size, callback) + buffer.map_async( + resource::MapModeFlags::from_bits_retain(mode.bits()), + offset, + size, + callback, + ) } pub fn buffer_get_mapped_range( diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 762ac52a6b2..fbfebd99172 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -1532,6 +1532,7 @@ impl Device { buffer: &Arc, offset: wgt::BufferAddress, data: &[u8], + mode: MapMode, ) -> resource::BufferAccessResult { use crate::resource::RawResourceAccess; @@ -1584,7 +1585,7 @@ impl Device { unsafe { device.raw().unmap_buffer(raw_buf) }; - Ok(()) + Ok(mode) } /// # Safety diff --git a/wgpu-core/src/resource.rs b/wgpu-core/src/resource.rs index 38d8e3babf2..1bd7325f515 100644 --- a/wgpu-core/src/resource.rs +++ b/wgpu-core/src/resource.rs @@ -414,6 +414,41 @@ pub enum BufferAccessError { size: wgt::BufferAddress, buffer_size: wgt::BufferAddress, }, + #[error(transparent)] + InvalidMapMode(#[from] BadMapMode), +} + +#[derive(Clone, Debug, Error)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[error("Invalid map mode: {0:?}, exactly one of READ or WRITE map mode must be set")] +pub struct BadMapMode(MapModeFlags); + +bitflags::bitflags! { + /// Corresponds to [WebGPU `GPUMapModeFlags`](https://gpuweb.github.io/gpuweb/#dictdef-gpumapmodeflags). + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct MapModeFlags: u32 { + const READ = 0x0001; + const WRITE = 0x0002; + } +} + +impl From for BadMapMode { + fn from(_: core::convert::Infallible) -> Self { + unreachable!() + } +} + +impl TryFrom for MapMode { + type Error = BadMapMode; + + fn try_from(mode: MapModeFlags) -> Result { + match mode { + m if m == MapModeFlags::READ => Ok(MapMode::Read), + m if m == MapModeFlags::WRITE => Ok(MapMode::Write), + m => Err(BadMapMode(m)), + } + } } impl WebGpuError for BufferAccessError { @@ -436,7 +471,8 @@ impl WebGpuError for BufferAccessError { | Self::OutOfBoundsEndOffsetOverrun { .. } | Self::MapAborted | Self::MapStartOffsetOverrun { .. } - | Self::MapEndOffsetOverrun { .. } => ErrorType::Validation, + | Self::MapEndOffsetOverrun { .. } + | Self::InvalidMapMode(_) => ErrorType::Validation, } } } @@ -492,7 +528,7 @@ impl WebGpuError for InvalidResourceError { } } -pub type BufferAccessResult = Result<(), BufferAccessError>; +pub type BufferAccessResult = Result; pub(crate) struct BufferPendingMapping { pub(crate) range: Range, @@ -772,13 +808,17 @@ impl Buffer { /// Schedule buffer mapping. /// /// `op.callback` is guaranteed to be called, regardless of the outcome. - pub fn map_async( + pub fn map_async( self: &Arc, - mode: MapMode, + mode: M, offset: wgt::BufferAddress, size: Option, callback: Option, - ) -> Option { + ) -> Option + where + M: fmt::Debug + TryInto + Copy, + M::Error: Into, + { profiling::scope!("Buffer::map_async"); api_log!( "Buffer::map_async {:?} mode {mode:?} offset {offset:?} size {size:?}", @@ -818,13 +858,17 @@ impl Buffer { /// /// A return value of `Ok(0)` means that mapping does not need to wait on the queue, but /// it does not mean that the buffer has already been mapped. - fn try_map_async( + fn try_map_async( self: &Arc, - mode: MapMode, + mode: M, offset: wgt::BufferAddress, size: Option, callback: Option, - ) -> Result, BufferAccessError)> { + ) -> Result, BufferAccessError)> + where + M: fmt::Debug + TryInto + Copy, + M::Error: Into, + { let range_size = if let Some(size) = size { size } else { @@ -878,6 +922,11 @@ impl Buffer { return Err((callback, BufferAccessError::UnalignedRange)); } + let mode = match mode.try_into() { + Ok(mode) => mode, + Err(e) => return Err((callback, e.into().into())), + }; + let (pub_usage, internal_use) = match mode { MapMode::Read => (wgt::BufferUsages::MAP_READ, wgt::BufferUses::MAP_READ), MapMode::Write => (wgt::BufferUsages::MAP_WRITE, wgt::BufferUses::MAP_WRITE), @@ -1106,7 +1155,7 @@ impl Buffer { range: pending_mapping.range.clone(), host, }; - Ok(()) + Ok(host) } Err(e) => Err(e), } @@ -1119,7 +1168,7 @@ impl Buffer { range: pending_mapping.range, host: pending_mapping.mode, }; - Ok(()) + Ok(pending_mapping.mode) }; Some((pending_mapping.callback, status)) } diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index cd3f910b4ed..af99c12b210 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -1826,7 +1826,7 @@ impl dispatch::BufferInterface for CoreBuffer { range.start, Some(range.end - range.start), Some(Box::new(|status| { - let res = status.map_err(|_| crate::BufferAsyncError); + let res = status.map(|_| ()).map_err(|_| crate::BufferAsyncError); callback(res); })), ); From 74c7b6a3b18d4e2c9c4ecf717cee0d7497ddf718 Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:02:53 +0200 Subject: [PATCH 05/14] [deno] Fixup DOMException builders Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- cts_runner/src/bootstrap.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cts_runner/src/bootstrap.js b/cts_runner/src/bootstrap.js index 3510c6bb663..1a170ea18ae 100644 --- a/cts_runner/src/bootstrap.js +++ b/cts_runner/src/bootstrap.js @@ -299,19 +299,25 @@ core.registerErrorBuilder( core.registerErrorBuilder( "DOMExceptionAbortError", function DOMExceptionAbortError(msg) { - return new domException.DOMException(msg, "AbortError"); + return new DOMException(msg, "AbortError"); }, ); core.registerErrorBuilder( "DOMExceptionInvalidCharacterError", function DOMExceptionInvalidCharacterError(msg) { - return new domException.DOMException(msg, "InvalidCharacterError"); + return new DOMException(msg, "InvalidCharacterError"); + }, +); +core.registerErrorBuilder( + "DOMExceptionInvalidStateError", + function DOMExceptionInvalidStateError(msg) { + return new DOMException(msg, "InvalidStateError"); }, ); core.registerErrorBuilder( "DOMExceptionDataError", function DOMExceptionDataError(msg) { - return new domException.DOMException(msg, "DataError"); + return new DOMException(msg, "DataError"); }, ); From 1aebb4280c96a6c7e25acbf602fbbb5a78116d3a Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:03:23 +0200 Subject: [PATCH 06/14] [deno] Check buffer state in content timeline Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/buffer.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index 7c8534fe2c4..cf88900905a 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -165,6 +165,25 @@ impl GPUBuffer { #[webidl] size: Option, ) -> Result<(), BufferError> { { + // If this.mapState is not "unmapped" + let maybe_error = match *self.map_state.borrow() { + BufferMapState::Unmapped => None, + BufferMapState::Pending => { + Some(wgpu_core::resource::BufferAccessError::MapAlreadyPending) + } + BufferMapState::Mapped { .. } => { + Some(wgpu_core::resource::BufferAccessError::AlreadyMapped) + } + }; + if let Some(error) = maybe_error { + // Issue the early-reject steps on the Device timeline of this.[[device]]. + // Generate a validation error. + self + .wgpu_device + .handle_error_nolabel(error.clone(), "GPUBuffer.mapAsync"); + // Return a promise rejected with OperationError. + return Err(error.into()); + } *self.map_state.borrow_mut() = BufferMapState::Pending; } From 3ccef88a5db6380d658bbfbb0f867f52ba82a131 Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sun, 25 Oct 2026 06:49:54 +0100 Subject: [PATCH 07/14] [deno] proper deferred mapAsync promise resolution Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/buffer.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index cf88900905a..57aa4e61b25 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -14,6 +14,8 @@ use deno_core::op2; use deno_core::v8; use deno_core::webidl::WebIdlInterfaceConverter; use deno_core::GarbageCollected; +use deno_core::OpState; +use deno_core::V8CrossThreadTaskSpawner; use deno_core::WebIDL; use deno_error::JsErrorBox; use wgpu_core::resource::Labeled as _; @@ -160,6 +162,7 @@ impl GPUBuffer { #[async_method] async fn map_async( &self, + state: Rc>, #[webidl(options(enforce_range = true))] mode: u32, #[webidl(default = 0)] offset: u64, #[webidl] size: Option, @@ -203,6 +206,20 @@ impl GPUBuffer { ); } + // Per specification we should have deferred promise resolution here + // (all microtasks should resolve before it). + // We do this by spawning a V8 task that will resolve the promise after all + // previous microtasks have been processed. + { + let spawner = + { state.borrow().borrow::().clone() }; + let (sender, receiver) = oneshot::channel(); + spawner.spawn(|_| { + let _ = sender.send(()); + }); + receiver.await?; + }; + let done = Rc::new(RefCell::new(false)); let done_ = done.clone(); let device_poll_fut = async move { From 85f5f5268abf02637d3438ac80d911e5a9e5e64a Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sat, 24 Oct 2026 14:02:07 +0200 Subject: [PATCH 08/14] [deno] Call GPUBuffer::unmap procedure on GPUBuffer::destroy Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/buffer.rs | 63 +++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index 57aa4e61b25..9f8611a499b 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -121,6 +121,39 @@ impl GarbageCollected for GPUBuffer { } } +impl GPUBuffer { + fn unmap_inner( + &self, + scope: &mut v8::HandleScope, + ) -> Result<(), BufferError> { + if let BufferMapState::Mapped { mode, range, views } = + self.map_state.replace(BufferMapState::Unmapped) + { + for ab in views { + let ab = ab.array_buffer.open(scope); + ab.detach(None); + } + + if mode == MapMode::Write { + if let Ok(mapping) = self + .wgpu_buffer + .get_mapped_range(range.start, Some(range.end - range.start)) + { + let data = self.data.borrow(); + let data = data.as_ref().expect("mapAsync succeeded"); + mapping + .write_slice() + .copy_from_slice(&data[range.start as usize..range.end as usize]); + } + } + } + + self.wgpu_buffer.unmap(); + + Ok(()) + } +} + #[op2] impl GPUBuffer { #[constructor] @@ -342,37 +375,15 @@ impl GPUBuffer { #[nofast] #[undefined] fn unmap(&self, scope: &mut v8::HandleScope) -> Result<(), BufferError> { - if let BufferMapState::Mapped { mode, range, views } = - self.map_state.replace(BufferMapState::Unmapped) - { - for ab in views { - let ab = ab.array_buffer.open(scope); - ab.detach(None); - } - - if mode == MapMode::Write { - if let Ok(mapping) = self - .wgpu_buffer - .get_mapped_range(range.start, Some(range.end - range.start)) - { - let data = self.data.borrow(); - let data = data.as_ref().expect("mapAsync succeeded"); - mapping - .write_slice() - .copy_from_slice(&data[range.start as usize..range.end as usize]); - } - } - } - - self.wgpu_buffer.unmap(); - - Ok(()) + self.unmap_inner(scope) } #[fast] #[undefined] - fn destroy(&self) { + fn destroy(&self, scope: &mut v8::HandleScope) -> Result<(), BufferError> { + self.unmap_inner(scope)?; self.data.replace(None); self.wgpu_buffer.destroy(); + Ok(()) } } From 04f19d167075bee10c85bea732c9c5f75014b75d Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sat, 24 Oct 2026 14:08:24 +0200 Subject: [PATCH 09/14] [deno] Do poll after Device::destroy to call Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/device.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deno_webgpu/device.rs b/deno_webgpu/device.rs index 78e8f1c2f18..ce0d9306e54 100644 --- a/deno_webgpu/device.rs +++ b/deno_webgpu/device.rs @@ -148,6 +148,9 @@ impl GPUDevice { #[undefined] fn destroy(&self) { self.wgpu_device.destroy(); + // Poll the device to encourage it to process any pending operations + // and release resources associated with the destroyed device (and thus calling lost closure). + let _ = self.wgpu_device.poll(wgpu_types::PollType::Poll); } #[reentrant] From 4db8e2aa957fa24ec53294108d4bfc9917f6ee0f Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:13:09 +0100 Subject: [PATCH 10/14] [deno] Ensure map state is still pending after defer Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/buffer.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index 9f8611a499b..e2c551b308c 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -253,6 +253,13 @@ impl GPUBuffer { receiver.await?; }; + // Now that we have awaited the V8 microtask queue + // we need to check the map state of the buffer again + // to ensure it is still pending (unmap could interrupt the mapping process). + if !matches!(*self.map_state.borrow(), BufferMapState::Pending) { + return Err(wgpu_core::resource::BufferAccessError::MapAborted.into()); + } + let done = Rc::new(RefCell::new(false)); let done_ = done.clone(); let device_poll_fut = async move { From 4ddb22f1699e35c925c041eb68bc75bcc1c2f7ba Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:16:07 +0100 Subject: [PATCH 11/14] `webgpu:api,validation,buffer,mapping:mapAsync,*` now pass Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- cts_runner/test.lst | 1 + 1 file changed, 1 insertion(+) diff --git a/cts_runner/test.lst b/cts_runner/test.lst index e46aca3643a..fcb065166d7 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -73,6 +73,7 @@ webgpu:api,validation,buffer,create:limit,* webgpu:api,validation,buffer,create:size,* webgpu:api,validation,buffer,create:usage,* webgpu:api,validation,buffer,destroy:* +webgpu:api,validation,buffer,mapping:mapAsync,* webgpu:api,validation,capability_checks,features,clip_distances:* webgpu:api,validation,capability_checks,features,query_types:createQuerySet:* webgpu:api,validation,capability_checks,features,texture_component_swizzle:* From 3adda89497f5454e6419920810081121c6a77bd6 Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:19:08 +0100 Subject: [PATCH 12/14] [deno] Fix substract overflow Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/buffer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deno_webgpu/buffer.rs b/deno_webgpu/buffer.rs index e2c551b308c..3639e9dbf97 100644 --- a/deno_webgpu/buffer.rs +++ b/deno_webgpu/buffer.rs @@ -310,7 +310,8 @@ impl GPUBuffer { #[webidl(default = 0)] offset: u64, #[webidl] size: Option, ) -> Result, BufferError> { - let size = size.unwrap_or_else(|| self.wgpu_buffer.size() - offset); + let size = + size.unwrap_or_else(|| self.wgpu_buffer.size().saturating_sub(offset)); let BufferMapState::Mapped { mode: _, range, From cae3892f80bad572f3aa8989e1dbc8aa371ca259 Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:20:09 +0100 Subject: [PATCH 13/14] `webgpu:api,validation,buffer,mapping:*` now pass Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- cts_runner/fail.lst | 1 - cts_runner/test.lst | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cts_runner/fail.lst b/cts_runner/fail.lst index 0f8734efd27..fe9fd49f81d 100644 --- a/cts_runner/fail.lst +++ b/cts_runner/fail.lst @@ -20,7 +20,6 @@ webgpu:api,operation,rendering,3d_texture_slices:* // https://github.com/gfx-rs/ webgpu:api,operation,vertex_state,correctness:* // Vulkan (https://github.com/gfx-rs/wgpu/issues/1105) and dx12 (https://github.com/gfx-rs/wgpu/issues/10216) webgpu:api,validation,buffer,create:new_usages,* // 0%, deno_webgpu declares the GPU*Usage constants as static getters, which are non-enumerable, so the test sees no legal usage bits, https://github.com/denoland/deno/issues/33846 -webgpu:api,validation,buffer,mapping:* // crash webgpu:api,validation,capability_checks,features,* // 21%, TypeError not thrown for missing features (needs deno_webgpu fix) webgpu:api,validation,capability_checks,limits,* // 60%, workgroup storage size validation unimplemented; interstage vars counting; bind group timing on dx12 webgpu:api,validation,compute_pipeline:limits,workgroup_storage_size:* // 0% diff --git a/cts_runner/test.lst b/cts_runner/test.lst index fcb065166d7..1b9dd16b026 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -73,7 +73,7 @@ webgpu:api,validation,buffer,create:limit,* webgpu:api,validation,buffer,create:size,* webgpu:api,validation,buffer,create:usage,* webgpu:api,validation,buffer,destroy:* -webgpu:api,validation,buffer,mapping:mapAsync,* +webgpu:api,validation,buffer,mapping:* webgpu:api,validation,capability_checks,features,clip_distances:* webgpu:api,validation,capability_checks,features,query_types:createQuerySet:* webgpu:api,validation,capability_checks,features,texture_component_swizzle:* From 521286bd416dda68bec5684f0db16e441a2775cf Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sun, 25 Oct 2026 07:48:07 +0100 Subject: [PATCH 14/14] [deno] Minimize OpState borrow Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- deno_webgpu/adapter.rs | 64 +++++++++++++++++++++++++-------------- deno_webgpu/lib.rs | 69 +++++++++++++++++++++--------------------- 2 files changed, 76 insertions(+), 57 deletions(-) diff --git a/deno_webgpu/adapter.rs b/deno_webgpu/adapter.rs index a11a9cb2b7e..60cbe4bf583 100644 --- a/deno_webgpu/adapter.rs +++ b/deno_webgpu/adapter.rs @@ -1,5 +1,6 @@ // Copyright 2018-2025 the Deno authors. MIT license. +use std::cell::RefCell; use std::ops::BitOr; use std::rc::Rc; use std::sync::Arc; @@ -122,7 +123,7 @@ impl GPUAdapter { #[global] fn request_device( &self, - state: &mut OpState, + state: Rc>, scope: &mut v8::HandleScope, #[webidl] descriptor: GPUDeviceDescriptor, ) -> Result, CreateDeviceError> { @@ -190,11 +191,14 @@ impl GPUAdapter { let lost_resolver = v8::PromiseResolver::new(scope).unwrap(); let lost_promise = lost_resolver.get_promise(scope); - let cross_thread_spawner = - state.borrow::().clone(); - state - .borrow_mut::() - .insert(wgpu_device_id, v8::Global::new(scope, lost_resolver)); + let lost_resolver = v8::Global::new(scope, lost_resolver); + let cross_thread_spawner = { + let mut state = state.borrow_mut(); + state + .borrow_mut::() + .insert(wgpu_device_id, lost_resolver); + state.borrow::().clone() + }; let wake = Arc::new(Mutex::new(move |reason: DeviceLostReason| { cross_thread_spawner.spawn(move |scope| { let lost_resolver = JsRuntime::op_state_from(scope) @@ -242,12 +246,18 @@ impl GPUAdapter { }; let device = deno_core::cppgc::make_cppgc_object(scope, device); let weak_device = v8::Weak::new(scope, device); - let event_target_setup = state.borrow::(); - let webidl_brand = v8::Local::new(scope, event_target_setup.brand.clone()); + let (brand, set_event_target_data) = { + let state = state.borrow(); + let event_target_setup = state.borrow::(); + ( + event_target_setup.brand.clone(), + event_target_setup.set_event_target_data.clone(), + ) + }; + let webidl_brand = v8::Local::new(scope, brand); device.set(scope, webidl_brand, webidl_brand); let set_event_target_data = - v8::Local::new(scope, event_target_setup.set_event_target_data.clone()) - .cast::(); + v8::Local::new(scope, set_event_target_data).cast::(); let null = v8::null(scope); set_event_target_data.call(scope, null.into(), &[device.into()]); @@ -276,19 +286,30 @@ impl GPUAdapter { deno_core::cppgc::try_unwrap_cppgc_object::(scope, device) .unwrap(); - let cross_thread_spawner = - state.borrow::().clone(); - state - .borrow_mut::() - .insert(wgpu_device_id, weak_device); + let cross_thread_spawner = { + let mut state = state.borrow_mut(); + state + .borrow_mut::() + .insert(wgpu_device_id, weak_device); + state.borrow::().clone() + }; let wake = Arc::new(Mutex::new(move |error: wgpu_types::error::Error| { cross_thread_spawner.spawn(move |scope| { - let state = JsRuntime::op_state_from(&*scope); - let state = state.borrow(); - let weak_device = - state.borrow::().get(&wgpu_device_id).unwrap(); + // Do not hold the OpState borrow while calling into V8: a GC + // finalizer may need to borrow it mutably. + let (weak_device, error_event_class) = { + let state = JsRuntime::op_state_from(&*scope); + let state = state.borrow(); + ( + state + .borrow::() + .get(&wgpu_device_id) + .unwrap() + .clone(), + state.borrow::().0.clone(), + ) + }; let err: GPUError = error.into(); - let weak_device = weak_device.clone(); let Some(device) = weak_device.to_local(scope) else { // The device has already gone away, so we don't have @@ -300,9 +321,6 @@ impl GPUAdapter { let func = v8::Global::new(scope, val.try_cast::().unwrap()); let device = v8::Global::new(scope, device.cast::()); - let error_event_class = - state.borrow::().0.clone(); - let error = deno_core::error::to_v8_error(scope, &err); let error_event_class = diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index 6e8297ee4e1..c020fa1db5c 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -182,8 +182,6 @@ impl GPU { state: Rc>, #[webidl] options: adapter::GPURequestAdapterOptions, ) -> Option { - let mut state = state.borrow_mut(); - let dx12_compiler = std::env::var(DX12_COMPILER_ENV_VAR) .ok() .and_then(|s| s.parse().ok()); @@ -191,40 +189,43 @@ impl GPU { |_| wgpu_types::Backends::all(), |s| wgpu_types::Backends::from_comma_list(&s), ); - let instance = if let Some(instance) = state.try_borrow::() { - instance - } else { - let mut flags = wgpu_types::InstanceFlags::from_build_config(); - let strict_compliance = std::env::var(STRICT_COMPLIANCE_ENV_VAR) - .is_ok_and(|value| { - !matches!(value.to_ascii_lowercase().as_str(), "false" | "no" | "0") - }); - if strict_compliance { - flags |= wgpu_types::InstanceFlags::STRICT_WEBGPU_COMPLIANCE; - } - state.put(wgpu_core::instance::Instance::new( - "webgpu", - wgpu_types::InstanceDescriptor { - backends, - flags, - memory_budget_thresholds: wgpu_types::MemoryBudgetThresholds { - for_resource_creation: Some(97), - for_device_loss: Some(99), - }, - backend_options: wgpu_types::BackendOptions { - dx12: wgpu_types::Dx12BackendOptions { - shader_compiler: dx12_compiler - .unwrap_or(wgpu_types::Dx12Compiler::Fxc), - ..Default::default() + let instance = { + let mut state = state.borrow_mut(); + if let Some(instance) = state.try_borrow::() { + instance.clone() + } else { + let mut flags = wgpu_types::InstanceFlags::from_build_config(); + let strict_compliance = std::env::var(STRICT_COMPLIANCE_ENV_VAR) + .is_ok_and(|value| { + !matches!(value.to_ascii_lowercase().as_str(), "false" | "no" | "0") + }); + if strict_compliance { + flags |= wgpu_types::InstanceFlags::STRICT_WEBGPU_COMPLIANCE; + } + state.put(wgpu_core::instance::Instance::new( + "webgpu", + wgpu_types::InstanceDescriptor { + backends, + flags, + memory_budget_thresholds: wgpu_types::MemoryBudgetThresholds { + for_resource_creation: Some(97), + for_device_loss: Some(99), }, - gl: wgpu_types::GlBackendOptions::default(), - noop: wgpu_types::NoopBackendOptions::default(), + backend_options: wgpu_types::BackendOptions { + dx12: wgpu_types::Dx12BackendOptions { + shader_compiler: dx12_compiler + .unwrap_or(wgpu_types::Dx12Compiler::Fxc), + ..Default::default() + }, + gl: wgpu_types::GlBackendOptions::default(), + noop: wgpu_types::NoopBackendOptions::default(), + }, + display: None, }, - display: None, - }, - None, - )); - state.borrow::() + None, + )); + state.borrow::().clone() + } }; // Check that the feature level string is valid.