diff options
Diffstat (limited to 'components/script/dom')
-rw-r--r-- | components/script/dom/bindings/buffer_source.rs | 142 | ||||
-rw-r--r-- | components/script/dom/bindings/trace.rs | 2 | ||||
-rw-r--r-- | components/script/dom/gpubuffer.rs | 389 | ||||
-rw-r--r-- | components/script/dom/gpucommandbuffer.rs | 6 | ||||
-rw-r--r-- | components/script/dom/gpudevice.rs | 37 | ||||
-rw-r--r-- | components/script/dom/gpuqueue.rs | 17 | ||||
-rw-r--r-- | components/script/dom/promise.rs | 18 | ||||
-rw-r--r-- | components/script/dom/webidls/WebGPU.webidl | 43 |
8 files changed, 362 insertions, 292 deletions
diff --git a/components/script/dom/bindings/buffer_source.rs b/components/script/dom/bindings/buffer_source.rs index 71c5051accd..5af00da3a45 100644 --- a/components/script/dom/bindings/buffer_source.rs +++ b/components/script/dom/bindings/buffer_source.rs @@ -4,18 +4,23 @@ #![allow(unsafe_code)] -use std::borrow::BorrowMut; use std::ffi::c_void; use std::marker::PhantomData; +use std::ops::Range; use std::ptr; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; -use js::jsapi::glue::NewExternalArrayBuffer; -use js::jsapi::{Heap, JSObject, JS_GetArrayBufferViewBuffer, JS_IsArrayBufferViewObject}; +use js::jsapi::{ + Heap, JSObject, JS_GetArrayBufferViewBuffer, JS_IsArrayBufferViewObject, NewExternalArrayBuffer, +}; use js::rust::wrappers::DetachArrayBuffer; use js::rust::{CustomAutoRooterGuard, Handle, MutableHandleObject}; -use js::typedarray::{CreateWith, TypedArray, TypedArrayElement, TypedArrayElementCreator}; +use js::typedarray::{ + ArrayBuffer, CreateWith, HeapArrayBuffer, TypedArray, TypedArrayElement, + TypedArrayElementCreator, +}; +use crate::dom::globalscope::GlobalScope; use crate::script_runtime::JSContext; /// <https://webidl.spec.whatwg.org/#BufferSource> @@ -402,44 +407,103 @@ where } } -pub fn create_new_external_array_buffer<T>( - cx: JSContext, - mapping: Arc<Mutex<Vec<T::Element>>>, - offset: usize, - range_size: usize, - m_end: usize, -) -> HeapBufferSource<T> -where - T: TypedArrayElement + TypedArrayElementCreator, - T::Element: Clone + Copy, -{ - /// `freeFunc()` must be threadsafe, should be safely callable from any thread - /// without causing conflicts , unexpected behavior. - /// <https://github.com/servo/mozjs/blob/main/mozjs-sys/mozjs/js/public/ArrayBuffer.h#L89> - unsafe extern "C" fn free_func(_contents: *mut c_void, free_user_data: *mut c_void) { - // Clippy warns about "creating a `Arc` from a void raw pointer" here, but suggests - // the exact same line to fix it. Doing the cast is tricky because of the use of - // a generic type in this parameter. - #[allow(clippy::from_raw_with_void_ptr)] - let _ = Arc::from_raw(free_user_data as *const _); +#[derive(JSTraceable, MallocSizeOf)] +pub struct DataBlock { + #[ignore_malloc_size_of = "Arc"] + data: Arc<Box<[u8]>>, + /// Data views (mutable subslices of data) + data_views: Vec<DataView>, +} + +/// Returns true if two non-inclusive ranges overlap +// https://stackoverflow.com/questions/3269434/whats-the-most-efficient-way-to-test-if-two-ranges-overlap +fn range_overlap<T: std::cmp::PartialOrd>(range1: &Range<T>, range2: &Range<T>) -> bool { + range1.start < range2.end && range2.start < range1.end +} + +impl DataBlock { + pub fn new_zeroed(size: usize) -> Self { + let data = vec![0; size]; + Self { + data: Arc::new(data.into_boxed_slice()), + data_views: Vec::new(), + } } - unsafe { - let mapping_slice_ptr = mapping.lock().unwrap().borrow_mut()[offset..m_end].as_mut_ptr(); + /// Panics if there is any active view or src data is not same length + pub fn load(&mut self, src: &[u8]) { + // `Arc::get_mut` ensures there are no views + Arc::get_mut(&mut self.data).unwrap().clone_from_slice(src) + } - // rooted! is needed to ensure memory safety and prevent potential garbage collection issues. - // https://github.com/mozilla-spidermonkey/spidermonkey-embedding-examples/blob/esr78/docs/GC%20Rooting%20Guide.md#performance-tweaking - rooted!(in(*cx) let array_buffer = NewExternalArrayBuffer( - *cx, - range_size, - mapping_slice_ptr as _, - Some(free_func), - Arc::into_raw(mapping) as _, - )); + /// Panics if there is any active view + pub fn data(&mut self) -> &mut [u8] { + // `Arc::get_mut` ensures there are no views + Arc::get_mut(&mut self.data).unwrap() + } - HeapBufferSource { - buffer_source: BufferSource::ArrayBuffer(Heap::boxed(*array_buffer)), - phantom: PhantomData, + pub fn clear_views(&mut self) { + self.data_views.clear() + } + + /// Returns error if requested range is already mapped + pub fn view(&mut self, range: Range<usize>) -> Result<&DataView, ()> { + if self + .data_views + .iter() + .any(|view| range_overlap(&view.range, &range)) + { + return Err(()); + } + let cx = GlobalScope::get_cx(); + /// `freeFunc()` must be threadsafe, should be safely callable from any thread + /// without causing conflicts, unexpected behavior. + unsafe extern "C" fn free_func(_contents: *mut c_void, free_user_data: *mut c_void) { + // Clippy warns about "creating a `Arc` from a void raw pointer" here, but suggests + // the exact same line to fix it. Doing the cast is tricky because of the use of + // a generic type in this parameter. + #[allow(clippy::from_raw_with_void_ptr)] + drop(Arc::from_raw(free_user_data as *const _)); } + let raw: *mut Box<[u8]> = Arc::into_raw(Arc::clone(&self.data)) as _; + rooted!(in(*cx) let object = unsafe { + NewExternalArrayBuffer( + *cx, + range.end - range.start, + // SAFETY: This is safe because we have checked there is no overlapping view + (*raw)[range.clone()].as_mut_ptr() as _, + Some(free_func), + raw as _, + ) + }); + self.data_views.push(DataView { + range, + buffer: HeapArrayBuffer::from(*object).unwrap(), + }); + Ok(self.data_views.last().unwrap()) + } +} + +#[derive(JSTraceable, MallocSizeOf)] +pub struct DataView { + #[no_trace] + range: Range<usize>, + #[ignore_malloc_size_of = "defined in mozjs"] + buffer: HeapArrayBuffer, +} + +impl DataView { + pub fn array_buffer(&self) -> ArrayBuffer { + unsafe { ArrayBuffer::from(self.buffer.underlying_object().get()).unwrap() } + } +} + +impl Drop for DataView { + #[allow(unsafe_code)] + fn drop(&mut self) { + let cx = GlobalScope::get_cx(); + assert!(unsafe { + js::jsapi::DetachArrayBuffer(*cx, self.buffer.underlying_object().handle()) + }) } } diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index cc5462f9dc1..ae05cf36ec4 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -61,7 +61,6 @@ use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::{DomObject, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::{DOMString, USVString}; -use crate::dom::gpubuffer::GPUBufferState; use crate::dom::gpucanvascontext::WebGPUContextId; use crate::dom::htmlimageelement::SourceSet; use crate::dom::htmlmediaelement::HTMLMediaElementFetchContext; @@ -366,7 +365,6 @@ unsafe_no_jsmanaged_fields!(WindowProxyHandler); unsafe_no_jsmanaged_fields!(DOMString); unsafe_no_jsmanaged_fields!(USVString); unsafe_no_jsmanaged_fields!(WebGPUContextId); -unsafe_no_jsmanaged_fields!(GPUBufferState); unsafe_no_jsmanaged_fields!(SourceSet); unsafe_no_jsmanaged_fields!(HTMLMediaElementFetchContext); unsafe_no_jsmanaged_fields!(StreamConsumer); diff --git a/components/script/dom/gpubuffer.rs b/components/script/dom/gpubuffer.rs index 1182e247a4a..a8d8d204163 100644 --- a/components/script/dom/gpubuffer.rs +++ b/components/script/dom/gpubuffer.rs @@ -2,19 +2,20 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::cell::Cell; use std::ops::Range; use std::rc::Rc; use std::string::String; -use std::sync::{Arc, Mutex}; use dom_struct::dom_struct; use ipc_channel::ipc::IpcSharedMemory; -use js::typedarray::{ArrayBuffer, ArrayBufferU8}; +use js::typedarray::ArrayBuffer; use webgpu::wgc::device::HostMap; -use webgpu::{WebGPU, WebGPUBuffer, WebGPURequest, WebGPUResponse}; +use webgpu::{wgt, Mapping, WebGPU, WebGPUBuffer, WebGPURequest, WebGPUResponse}; -use super::bindings::buffer_source::{create_new_external_array_buffer, HeapBufferSource}; +use super::bindings::buffer_source::DataBlock; +use super::bindings::codegen::Bindings::WebGPUBinding::{ + GPUBufferMapState, GPUFlagsConstant, GPUMapModeFlags, +}; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ GPUBufferMethods, GPUMapModeConstants, GPUSize64, @@ -30,31 +31,36 @@ use crate::dom::promise::Promise; use crate::realms::InRealm; use crate::script_runtime::JSContext; -const RANGE_OFFSET_ALIGN_MASK: u64 = 8; -const RANGE_SIZE_ALIGN_MASK: u64 = 4; - -// https://gpuweb.github.io/gpuweb/#buffer-state -#[derive(Clone, Copy, MallocSizeOf, PartialEq)] -pub enum GPUBufferState { - Mapped, - MappedAtCreation, - MappingPending, - Unmapped, - Destroyed, +#[derive(JSTraceable, MallocSizeOf)] +pub struct ActiveBufferMapping { + // TODO(sagudev): Use IpcSharedMemory when https://github.com/servo/ipc-channel/pull/356 lands + /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-data> + /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-views> + pub data: DataBlock, + /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-mode> + mode: GPUMapModeFlags, + /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-range> + range: Range<u64>, } -#[derive(JSTraceable, MallocSizeOf)] -pub struct GPUBufferMapInfo { - #[ignore_malloc_size_of = "Arc"] - #[no_trace] - /// The `mapping` is wrapped in an `Arc` to ensure thread safety. - /// This is necessary for integration with the SpiderMonkey engine, - pub mapping: Arc<Mutex<Vec<u8>>>, - pub mapping_range: Range<u64>, - pub mapped_ranges: Vec<Range<u64>>, - #[ignore_malloc_size_of = "defined in mozjs"] - pub js_buffers: Vec<HeapBufferSource<ArrayBufferU8>>, - pub map_mode: Option<u32>, +impl ActiveBufferMapping { + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-initialize-an-active-buffer-mapping> + pub fn new(mode: GPUMapModeFlags, range: Range<u64>) -> Fallible<Self> { + // Step 1 + let size = range.end - range.start; + // Step 2 + if size > (1 << 53) - 1 { + return Err(Error::Range("Over MAX_SAFE_INTEGER".to_string())); + } + let size: usize = size + .try_into() + .map_err(|_| Error::Range("Over usize".to_string()))?; + Ok(Self { + data: DataBlock::new_zeroed(size), + mode, + range, + }) + } } #[dom_struct] @@ -64,14 +70,18 @@ pub struct GPUBuffer { #[no_trace] channel: WebGPU, label: DomRefCell<USVString>, - state: Cell<GPUBufferState>, #[no_trace] buffer: WebGPUBuffer, device: Dom<GPUDevice>, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size> size: GPUSize64, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-usage> + usage: GPUFlagsConstant, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-pending_map-slot> #[ignore_malloc_size_of = "promises are hard"] - map_promise: DomRefCell<Option<Rc<Promise>>>, - map_info: DomRefCell<Option<GPUBufferMapInfo>>, + pending_map: DomRefCell<Option<Rc<Promise>>>, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapping-slot> + mapping: DomRefCell<Option<ActiveBufferMapping>>, } impl GPUBuffer { @@ -79,38 +89,37 @@ impl GPUBuffer { channel: WebGPU, buffer: WebGPUBuffer, device: &GPUDevice, - state: GPUBufferState, size: GPUSize64, - map_info: DomRefCell<Option<GPUBufferMapInfo>>, + usage: GPUFlagsConstant, + mapping: Option<ActiveBufferMapping>, label: USVString, ) -> Self { Self { reflector_: Reflector::new(), channel, label: DomRefCell::new(label), - state: Cell::new(state), device: Dom::from_ref(device), buffer, - map_promise: DomRefCell::new(None), + pending_map: DomRefCell::new(None), size, - map_info, + usage, + mapping: DomRefCell::new(mapping), } } - #[allow(clippy::too_many_arguments)] pub fn new( global: &GlobalScope, channel: WebGPU, buffer: WebGPUBuffer, device: &GPUDevice, - state: GPUBufferState, size: GPUSize64, - map_info: DomRefCell<Option<GPUBufferMapInfo>>, + usage: GPUFlagsConstant, + mapping: Option<ActiveBufferMapping>, label: USVString, ) -> DomRoot<Self> { reflect_dom_object( Box::new(GPUBuffer::new_inherited( - channel, buffer, device, state, size, map_info, label, + channel, buffer, device, size, usage, mapping, label, )), global, ) @@ -121,86 +130,49 @@ impl GPUBuffer { pub fn id(&self) -> WebGPUBuffer { self.buffer } - - pub fn state(&self) -> GPUBufferState { - self.state.get() - } } impl Drop for GPUBuffer { fn drop(&mut self) { - if matches!(self.state(), GPUBufferState::Destroyed) { - return; - } - if let Err(e) = self - .channel - .0 - .send(WebGPURequest::DropBuffer(self.buffer.0)) - { - warn!( - "Failed to send WebGPURequest::DropBuffer({:?}) ({})", - self.buffer.0, e - ); - }; + self.Destroy() } } impl GPUBufferMethods for GPUBuffer { + #[allow(unsafe_code)] /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap> - fn Unmap(&self) -> Fallible<()> { - let cx = GlobalScope::get_cx(); + fn Unmap(&self) { // Step 1 - match self.state.get() { - GPUBufferState::Unmapped | GPUBufferState::Destroyed => { - // TODO: Record validation error on the current scope - return Ok(()); - }, - // Step 3 - GPUBufferState::Mapped | GPUBufferState::MappedAtCreation => { - let mut info = self.map_info.borrow_mut(); - let m_info = if let Some(m_info) = info.as_mut() { - m_info - } else { - return Err(Error::Operation); - }; - let m_range = m_info.mapping_range.clone(); - if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer { - buffer_id: self.id().0, - device_id: self.device.id().0, - array_buffer: IpcSharedMemory::from_bytes(&m_info.mapping.lock().unwrap()), - is_map_read: m_info.map_mode == Some(GPUMapModeConstants::READ), - offset: m_range.start, - size: m_range.end - m_range.start, - }) { - warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e); - } - // Step 3.3 - m_info.js_buffers.drain(..).for_each(|obj| { - obj.detach_buffer(cx); - }); - }, - // Step 2 - GPUBufferState::MappingPending => { - let promise = self.map_promise.borrow_mut().take().unwrap(); - promise.reject_error(Error::Operation); - }, + if let Some(promise) = self.pending_map.borrow_mut().take() { + promise.reject_error(Error::Abort); + } + // Step 2 + let mut mapping = self.mapping.borrow_mut().take(); + let mapping = if let Some(mapping) = mapping.as_mut() { + mapping + } else { + return; }; - // Step 4 - self.state.set(GPUBufferState::Unmapped); - *self.map_info.borrow_mut() = None; - Ok(()) + + // Step 3 + mapping.data.clear_views(); + // Step 5&7 + if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer { + buffer_id: self.id().0, + array_buffer: IpcSharedMemory::from_bytes(mapping.data.data()), + write_back: mapping.mode >= GPUMapModeConstants::WRITE, + offset: mapping.range.start, + size: mapping.range.end - mapping.range.start, + }) { + warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e); + } } - /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy> - fn Destroy(&self) -> Fallible<()> { - let state = self.state.get(); - match state { - GPUBufferState::Mapped | GPUBufferState::MappedAtCreation => { - self.Unmap()?; - }, - GPUBufferState::Destroyed => return Ok(()), - _ => {}, - }; + /// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy + fn Destroy(&self) { + // Step 1 + self.Unmap(); + // Step 2 if let Err(e) = self .channel .0 @@ -211,11 +183,9 @@ impl GPUBufferMethods for GPUBuffer { self.buffer.0, e ); }; - self.state.set(GPUBufferState::Destroyed); - Ok(()) } - /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapasync-offset-size> + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapasync> fn MapAsync( &self, mode: u32, @@ -224,22 +194,16 @@ impl GPUBufferMethods for GPUBuffer { comp: InRealm, ) -> Rc<Promise> { let promise = Promise::new_in_current_realm(comp); - let range_size = if let Some(s) = size { - s - } else if offset >= self.size { + // Step 2 + if self.pending_map.borrow().is_some() { promise.reject_error(Error::Operation); return promise; - } else { - self.size - offset - }; - if self.state.get() != GPUBufferState::Unmapped { - self.device - .dispatch_error(webgpu::Error::Validation(String::from( - "Buffer is not Unmapped", - ))); - promise.reject_error(Error::Abort); - return promise; } + // Step 4 + *self.pending_map.borrow_mut() = Some(promise.clone()); + // Step 5 + + // This should be bitflags in wgpu-core let host_map = match mode { GPUMapModeConstants::READ => HostMap::Read, GPUMapModeConstants::WRITE => HostMap::Write, @@ -248,13 +212,11 @@ impl GPUBufferMethods for GPUBuffer { .dispatch_error(webgpu::Error::Validation(String::from( "Invalid MapModeFlags", ))); - promise.reject_error(Error::Abort); + self.map_failure(&promise); return promise; }, }; - let map_range = offset..offset + range_size; - let sender = response_async(&promise, self); if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync { sender, @@ -262,80 +224,53 @@ impl GPUBufferMethods for GPUBuffer { device_id: self.device.id().0, host_map, offset, - size: Some(range_size), + size, }) { warn!( "Failed to send BufferMapAsync ({:?}) ({})", self.buffer.0, e ); - promise.reject_error(Error::Operation); + self.map_failure(&promise); return promise; } - - self.state.set(GPUBufferState::MappingPending); - *self.map_info.borrow_mut() = Some(GPUBufferMapInfo { - mapping: Arc::new(Mutex::new(Vec::with_capacity(0))), - mapping_range: map_range, - mapped_ranges: Vec::new(), - js_buffers: Vec::new(), - map_mode: Some(mode), - }); - *self.map_promise.borrow_mut() = Some(promise.clone()); + // Step 6 promise } /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange> + #[allow(unsafe_code)] fn GetMappedRange( &self, - cx: JSContext, + _cx: JSContext, offset: GPUSize64, size: Option<GPUSize64>, ) -> Fallible<ArrayBuffer> { let range_size = if let Some(s) = size { s - } else if offset >= self.size { - return Err(Error::Operation); - } else { - self.size - offset - }; - let m_end = offset + range_size; - let mut info = self.map_info.borrow_mut(); - let m_info = if let Some(m_info) = info.as_mut() { - m_info } else { - return Err(Error::Operation); + self.size.checked_sub(offset).unwrap_or(0) }; - let mut valid = matches!( - self.state.get(), - GPUBufferState::Mapped | GPUBufferState::MappedAtCreation - ); + // Step 2: validation + let mut mapping = self.mapping.borrow_mut(); + let mapping = mapping.as_mut().ok_or(Error::Operation)?; - valid &= offset % RANGE_OFFSET_ALIGN_MASK == 0 && - range_size % RANGE_SIZE_ALIGN_MASK == 0 && - offset >= m_info.mapping_range.start && - m_end <= m_info.mapping_range.end; - valid &= m_info - .mapped_ranges - .iter() - .all(|range| range.start >= m_end || range.end <= offset); + let valid = offset % wgt::MAP_ALIGNMENT == 0 && + range_size % wgt::COPY_BUFFER_ALIGNMENT == 0 && + offset >= mapping.range.start && + offset + range_size <= mapping.range.end; if !valid { return Err(Error::Operation); } - let heap_typed_array = create_new_external_array_buffer::<ArrayBufferU8>( - cx, - Arc::clone(&m_info.mapping), - offset as usize, - range_size as usize, - m_end as usize, - ); - - let result = heap_typed_array.get_buffer().map_err(|_| Error::JSFailed); - - m_info.mapped_ranges.push(offset..m_end); - m_info.js_buffers.push(heap_typed_array); - - result + // Step 4 + // only mapping.range is mapped with mapping.range.start at 0 + // so we need to rebase range to mapped.range + let rebased_offset = (offset - mapping.range.start) as usize; + mapping + .data + .view(rebased_offset..rebased_offset + range_size as usize) + .map(|view| view.array_buffer()) + .map_err(|()| Error::Operation) } /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> @@ -347,30 +282,96 @@ impl GPUBufferMethods for GPUBuffer { fn SetLabel(&self, value: USVString) { *self.label.borrow_mut() = value; } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size> + fn Size(&self) -> GPUSize64 { + self.size + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-usage> + fn Usage(&self) -> GPUFlagsConstant { + self.usage + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapstate> + fn MapState(&self) -> GPUBufferMapState { + // Step 1&2&3 + if self.mapping.borrow().is_some() { + GPUBufferMapState::Mapped + } else if self.pending_map.borrow().is_some() { + GPUBufferMapState::Pending + } else { + GPUBufferMapState::Unmapped + } + } +} + +impl GPUBuffer { + fn map_failure(&self, p: &Rc<Promise>) { + let mut pending_map = self.pending_map.borrow_mut(); + // Step 1 + if pending_map.as_ref() != Some(p) { + assert!(p.is_rejected()); + return; + } + // Step 2 + assert!(p.is_pending()); + // Step 3 + pending_map.take(); + // Step 4 + if self.device.is_lost() { + p.reject_error(Error::Abort); + } else { + p.reject_error(Error::Operation); + } + } + + fn map_success(&self, p: &Rc<Promise>, wgpu_mapping: Mapping) { + let mut pending_map = self.pending_map.borrow_mut(); + + // Step 1 + if pending_map.as_ref() != Some(p) { + assert!(p.is_rejected()); + return; + } + + // Step 2 + assert!(p.is_pending()); + + // Step 4 + let mapping = ActiveBufferMapping::new( + match wgpu_mapping.mode { + HostMap::Read => GPUMapModeConstants::READ, + HostMap::Write => GPUMapModeConstants::WRITE, + }, + wgpu_mapping.range, + ); + + match mapping { + Err(error) => { + *pending_map = None; + p.reject_error(error.clone()); + }, + Ok(mut mapping) => { + // Step 5 + mapping.data.load(&wgpu_mapping.data); + // Step 6 + self.mapping.borrow_mut().replace(mapping); + // Step 7 + pending_map.take(); + p.resolve_native(&()); + }, + } + } } impl AsyncWGPUListener for GPUBuffer { + #[allow(unsafe_code)] fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>) { match response { - WebGPUResponse::BufferMapAsync(Ok(bytes)) => { - *self - .map_info - .borrow_mut() - .as_mut() - .unwrap() - .mapping - .lock() - .unwrap() - .as_mut() = bytes.to_vec(); - promise.resolve_native(&()); - self.state.set(GPUBufferState::Mapped); - }, - WebGPUResponse::BufferMapAsync(Err(e)) => { - warn!("Could not map buffer({:?})", e); - promise.reject_error(Error::Abort); - }, - _ => unreachable!("GPUBuffer received wrong WebGPUResponse"), + WebGPUResponse::BufferMapAsync(Ok(mapping)) => self.map_success(promise, mapping), + WebGPUResponse::BufferMapAsync(Err(_)) => self.map_failure(promise), + _ => unreachable!("Wrong response received on AsyncWGPUListener for GPUBuffer"), } - *self.map_promise.borrow_mut() = None; } } diff --git a/components/script/dom/gpucommandbuffer.rs b/components/script/dom/gpucommandbuffer.rs index e50c350f18c..be0edf8f2c8 100644 --- a/components/script/dom/gpucommandbuffer.rs +++ b/components/script/dom/gpucommandbuffer.rs @@ -8,7 +8,7 @@ use std::hash::{Hash, Hasher}; use dom_struct::dom_struct; use webgpu::{WebGPU, WebGPUCommandBuffer, WebGPURequest}; -use crate::dom::bindings::cell::{DomRefCell, Ref}; +use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUCommandBufferMethods; use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; @@ -89,10 +89,6 @@ impl GPUCommandBuffer { pub fn id(&self) -> WebGPUCommandBuffer { self.command_buffer } - - pub fn buffers(&self) -> Ref<HashSet<Dom<GPUBuffer>>> { - self.buffers.borrow() - } } impl GPUCommandBufferMethods for GPUCommandBuffer { diff --git a/components/script/dom/gpudevice.rs b/components/script/dom/gpudevice.rs index e3f19813890..47ab1e29fac 100644 --- a/components/script/dom/gpudevice.rs +++ b/components/script/dom/gpudevice.rs @@ -9,7 +9,6 @@ use std::cell::Cell; use std::collections::HashMap; use std::num::NonZeroU64; use std::rc::Rc; -use std::sync::{Arc, Mutex}; use dom_struct::dom_struct; use js::jsapi::{Heap, JSObject}; @@ -24,7 +23,9 @@ use webgpu::{ WebGPUResponse, }; -use super::bindings::codegen::Bindings::WebGPUBinding::{GPUPipelineErrorReason, GPUTextureFormat}; +use super::bindings::codegen::Bindings::WebGPUBinding::{ + GPUMapModeConstants, GPUPipelineErrorReason, GPUTextureFormat, +}; use super::bindings::codegen::UnionTypes::GPUPipelineLayoutOrGPUAutoLayoutMode; use super::bindings::error::Fallible; use super::gpu::AsyncWGPUListener; @@ -55,7 +56,7 @@ use crate::dom::gpu::response_async; use crate::dom::gpuadapter::GPUAdapter; use crate::dom::gpubindgroup::GPUBindGroup; use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; -use crate::dom::gpubuffer::{GPUBuffer, GPUBufferMapInfo, GPUBufferState}; +use crate::dom::gpubuffer::{ActiveBufferMapping, GPUBuffer}; use crate::dom::gpucommandencoder::GPUCommandEncoder; use crate::dom::gpucomputepipeline::GPUComputePipeline; use crate::dom::gpuconvert::{ @@ -213,6 +214,10 @@ impl GPUDevice { } } + pub fn is_lost(&self) -> bool { + self.lost_promise.borrow().is_fulfilled() + } + fn get_pipeline_layout_data( &self, layout: &GPUPipelineLayoutOrGPUAutoLayoutMode, @@ -452,31 +457,23 @@ impl GPUDeviceMethods for GPUDevice { .expect("Failed to create WebGPU buffer"); let buffer = webgpu::WebGPUBuffer(id); - let map_info; - let state; - if descriptor.mappedAtCreation { - let buf_data = vec![0u8; descriptor.size as usize]; - map_info = DomRefCell::new(Some(GPUBufferMapInfo { - mapping: Arc::new(Mutex::new(buf_data)), - mapping_range: 0..descriptor.size, - mapped_ranges: Vec::new(), - js_buffers: Vec::new(), - map_mode: None, - })); - state = GPUBufferState::MappedAtCreation; + let mapping = if descriptor.mappedAtCreation { + Some(ActiveBufferMapping::new( + GPUMapModeConstants::WRITE, + 0..descriptor.size, + )?) } else { - map_info = DomRefCell::new(None); - state = GPUBufferState::Unmapped; - } + None + }; Ok(GPUBuffer::new( &self.global(), self.channel.clone(), buffer, self, - state, descriptor.size, - map_info, + descriptor.usage, + mapping, descriptor.parent.label.clone(), )) } diff --git a/components/script/dom/gpuqueue.rs b/components/script/dom/gpuqueue.rs index aa0daa5ae0f..e4a4102141d 100644 --- a/components/script/dom/gpuqueue.rs +++ b/components/script/dom/gpuqueue.rs @@ -20,7 +20,7 @@ use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::USVString; use crate::dom::globalscope::GlobalScope; -use crate::dom::gpubuffer::{GPUBuffer, GPUBufferState}; +use crate::dom::gpubuffer::GPUBuffer; use crate::dom::gpucommandbuffer::GPUCommandBuffer; use crate::dom::gpuconvert::{ convert_ic_texture, convert_image_data_layout, convert_texture_size_to_dict, @@ -80,21 +80,6 @@ impl GPUQueueMethods for GPUQueue { /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-submit> fn Submit(&self, command_buffers: Vec<DomRoot<GPUCommandBuffer>>) { - let valid = command_buffers.iter().all(|cb| { - cb.buffers() - .iter() - .all(|b| matches!(b.state(), GPUBufferState::Unmapped)) - }); - if !valid { - self.device - .borrow() - .as_ref() - .unwrap() - .dispatch_error(webgpu::Error::Validation(String::from( - "Referenced GPUBuffer(s) are not Unmapped", - ))); - return; - } let command_buffers = command_buffers.iter().map(|cb| cb.id().0).collect(); self.channel .0 diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs index 7c98ddbd2ad..38e5a9112ac 100644 --- a/components/script/dom/promise.rs +++ b/components/script/dom/promise.rs @@ -52,6 +52,12 @@ pub struct Promise { permanent_js_root: Heap<JSVal>, } +impl PartialEq for Promise { + fn eq(&self, other: &Self) -> bool { + self.reflector == other.reflector + } +} + /// Private helper to enable adding new methods to `Rc<Promise>`. trait PromiseHelper { fn initialize(&self, cx: SafeJSContext); @@ -232,6 +238,18 @@ impl Promise { } #[allow(unsafe_code)] + pub fn is_rejected(&self) -> bool { + let state = unsafe { GetPromiseState(self.promise_obj()) }; + matches!(state, PromiseState::Rejected) + } + + #[allow(unsafe_code)] + pub fn is_pending(&self) -> bool { + let state = unsafe { GetPromiseState(self.promise_obj()) }; + matches!(state, PromiseState::Pending) + } + + #[allow(unsafe_code)] pub fn promise_obj(&self) -> HandleObject { let obj = self.reflector().get_jsobject(); unsafe { diff --git a/components/script/dom/webidls/WebGPU.webidl b/components/script/dom/webidls/WebGPU.webidl index db7889aff73..65694fb3b29 100644 --- a/components/script/dom/webidls/WebGPU.webidl +++ b/components/script/dom/webidls/WebGPU.webidl @@ -165,17 +165,28 @@ GPUDevice includes GPUObjectBase; [Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] interface GPUBuffer { + readonly attribute GPUSize64Out size; + readonly attribute GPUFlagsConstant usage; + + readonly attribute GPUBufferMapState mapState; + [NewObject] Promise<undefined> mapAsync(GPUMapModeFlags mode, optional GPUSize64 offset = 0, optional GPUSize64 size); [NewObject, Throws] ArrayBuffer getMappedRange(optional GPUSize64 offset = 0, optional GPUSize64 size); - [Throws] undefined unmap(); - [Throws] + undefined destroy(); }; GPUBuffer includes GPUObjectBase; + +enum GPUBufferMapState { + "unmapped", + "pending", + "mapped", +}; + dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { required GPUSize64 size; required GPUBufferUsageFlags usage; @@ -184,24 +195,24 @@ dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { typedef [EnforceRange] unsigned long GPUBufferUsageFlags; [Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] -interface GPUBufferUsage { - const GPUBufferUsageFlags MAP_READ = 0x0001; - const GPUBufferUsageFlags MAP_WRITE = 0x0002; - const GPUBufferUsageFlags COPY_SRC = 0x0004; - const GPUBufferUsageFlags COPY_DST = 0x0008; - const GPUBufferUsageFlags INDEX = 0x0010; - const GPUBufferUsageFlags VERTEX = 0x0020; - const GPUBufferUsageFlags UNIFORM = 0x0040; - const GPUBufferUsageFlags STORAGE = 0x0080; - const GPUBufferUsageFlags INDIRECT = 0x0100; - const GPUBufferUsageFlags QUERY_RESOLVE = 0x0200; +namespace GPUBufferUsage { + const GPUFlagsConstant MAP_READ = 0x0001; + const GPUFlagsConstant MAP_WRITE = 0x0002; + const GPUFlagsConstant COPY_SRC = 0x0004; + const GPUFlagsConstant COPY_DST = 0x0008; + const GPUFlagsConstant INDEX = 0x0010; + const GPUFlagsConstant VERTEX = 0x0020; + const GPUFlagsConstant UNIFORM = 0x0040; + const GPUFlagsConstant STORAGE = 0x0080; + const GPUFlagsConstant INDIRECT = 0x0100; + const GPUFlagsConstant QUERY_RESOLVE = 0x0200; }; typedef [EnforceRange] unsigned long GPUMapModeFlags; [Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] -interface GPUMapMode { - const GPUMapModeFlags READ = 0x0001; - const GPUMapModeFlags WRITE = 0x0002; +namespace GPUMapMode { + const GPUFlagsConstant READ = 0x0001; + const GPUFlagsConstant WRITE = 0x0002; }; [Exposed=(Window, DedicatedWorker), Serializable , Pref="dom.webgpu.enabled"] |