webgpu: Sync GPUBuffer (#33154)

* More helpers on `Promise`

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Sync `GPUBuffer`

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Set some good expectations

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Some bad expect

also on firefox

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Extract DataBlock, DataView impl from GPUBuffer

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* Fix size check to work on 32bit platforms

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
Samson 2024-08-27 09:54:55 +02:00 committed by GitHub
parent bb5926b329
commit 7fce24f9d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 690 additions and 1163 deletions

View file

@ -4,18 +4,23 @@
#![allow(unsafe_code)] #![allow(unsafe_code)]
use std::borrow::BorrowMut;
use std::ffi::c_void; use std::ffi::c_void;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::Range;
use std::ptr; use std::ptr;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use js::jsapi::glue::NewExternalArrayBuffer; use js::jsapi::{
use js::jsapi::{Heap, JSObject, JS_GetArrayBufferViewBuffer, JS_IsArrayBufferViewObject}; Heap, JSObject, JS_GetArrayBufferViewBuffer, JS_IsArrayBufferViewObject, NewExternalArrayBuffer,
};
use js::rust::wrappers::DetachArrayBuffer; use js::rust::wrappers::DetachArrayBuffer;
use js::rust::{CustomAutoRooterGuard, Handle, MutableHandleObject}; 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; use crate::script_runtime::JSContext;
/// <https://webidl.spec.whatwg.org/#BufferSource> /// <https://webidl.spec.whatwg.org/#BufferSource>
@ -402,44 +407,103 @@ where
} }
} }
pub fn create_new_external_array_buffer<T>( #[derive(JSTraceable, MallocSizeOf)]
cx: JSContext, pub struct DataBlock {
mapping: Arc<Mutex<Vec<T::Element>>>, #[ignore_malloc_size_of = "Arc"]
offset: usize, data: Arc<Box<[u8]>>,
range_size: usize, /// Data views (mutable subslices of data)
m_end: usize, data_views: Vec<DataView>,
) -> HeapBufferSource<T> }
where
T: TypedArrayElement + TypedArrayElementCreator, /// Returns true if two non-inclusive ranges overlap
T::Element: Clone + Copy, // 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(),
}
}
/// 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)
}
/// 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()
}
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 /// `freeFunc()` must be threadsafe, should be safely callable from any thread
/// without causing conflicts, unexpected behavior. /// 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) { 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 // 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 // the exact same line to fix it. Doing the cast is tricky because of the use of
// a generic type in this parameter. // a generic type in this parameter.
#[allow(clippy::from_raw_with_void_ptr)] #[allow(clippy::from_raw_with_void_ptr)]
let _ = Arc::from_raw(free_user_data as *const _); drop(Arc::from_raw(free_user_data as *const _));
} }
let raw: *mut Box<[u8]> = Arc::into_raw(Arc::clone(&self.data)) as _;
unsafe { rooted!(in(*cx) let object = unsafe {
let mapping_slice_ptr = mapping.lock().unwrap().borrow_mut()[offset..m_end].as_mut_ptr(); NewExternalArrayBuffer(
// 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, *cx,
range_size, range.end - range.start,
mapping_slice_ptr as _, // SAFETY: This is safe because we have checked there is no overlapping view
(*raw)[range.clone()].as_mut_ptr() as _,
Some(free_func), Some(free_func),
Arc::into_raw(mapping) as _, raw as _,
)); )
});
self.data_views.push(DataView {
range,
buffer: HeapArrayBuffer::from(*object).unwrap(),
});
Ok(self.data_views.last().unwrap())
}
}
HeapBufferSource { #[derive(JSTraceable, MallocSizeOf)]
buffer_source: BufferSource::ArrayBuffer(Heap::boxed(*array_buffer)), pub struct DataView {
phantom: PhantomData, #[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())
})
}
} }

View file

@ -61,7 +61,6 @@ use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{DomObject, Reflector}; use crate::dom::bindings::reflector::{DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::gpubuffer::GPUBufferState;
use crate::dom::gpucanvascontext::WebGPUContextId; use crate::dom::gpucanvascontext::WebGPUContextId;
use crate::dom::htmlimageelement::SourceSet; use crate::dom::htmlimageelement::SourceSet;
use crate::dom::htmlmediaelement::HTMLMediaElementFetchContext; use crate::dom::htmlmediaelement::HTMLMediaElementFetchContext;
@ -366,7 +365,6 @@ unsafe_no_jsmanaged_fields!(WindowProxyHandler);
unsafe_no_jsmanaged_fields!(DOMString); unsafe_no_jsmanaged_fields!(DOMString);
unsafe_no_jsmanaged_fields!(USVString); unsafe_no_jsmanaged_fields!(USVString);
unsafe_no_jsmanaged_fields!(WebGPUContextId); unsafe_no_jsmanaged_fields!(WebGPUContextId);
unsafe_no_jsmanaged_fields!(GPUBufferState);
unsafe_no_jsmanaged_fields!(SourceSet); unsafe_no_jsmanaged_fields!(SourceSet);
unsafe_no_jsmanaged_fields!(HTMLMediaElementFetchContext); unsafe_no_jsmanaged_fields!(HTMLMediaElementFetchContext);
unsafe_no_jsmanaged_fields!(StreamConsumer); unsafe_no_jsmanaged_fields!(StreamConsumer);

View file

@ -2,19 +2,20 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
use std::string::String; use std::string::String;
use std::sync::{Arc, Mutex};
use dom_struct::dom_struct; use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSharedMemory; use ipc_channel::ipc::IpcSharedMemory;
use js::typedarray::{ArrayBuffer, ArrayBufferU8}; use js::typedarray::ArrayBuffer;
use webgpu::wgc::device::HostMap; 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::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
GPUBufferMethods, GPUMapModeConstants, GPUSize64, GPUBufferMethods, GPUMapModeConstants, GPUSize64,
@ -30,31 +31,36 @@ use crate::dom::promise::Promise;
use crate::realms::InRealm; use crate::realms::InRealm;
use crate::script_runtime::JSContext; use crate::script_runtime::JSContext;
const RANGE_OFFSET_ALIGN_MASK: u64 = 8; #[derive(JSTraceable, MallocSizeOf)]
const RANGE_SIZE_ALIGN_MASK: u64 = 4; pub struct ActiveBufferMapping {
// TODO(sagudev): Use IpcSharedMemory when https://github.com/servo/ipc-channel/pull/356 lands
// https://gpuweb.github.io/gpuweb/#buffer-state /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-data>
#[derive(Clone, Copy, MallocSizeOf, PartialEq)] /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-views>
pub enum GPUBufferState { pub data: DataBlock,
Mapped, /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-mode>
MappedAtCreation, mode: GPUMapModeFlags,
MappingPending, /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-range>
Unmapped, range: Range<u64>,
Destroyed,
} }
#[derive(JSTraceable, MallocSizeOf)] impl ActiveBufferMapping {
pub struct GPUBufferMapInfo { /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-initialize-an-active-buffer-mapping>
#[ignore_malloc_size_of = "Arc"] pub fn new(mode: GPUMapModeFlags, range: Range<u64>) -> Fallible<Self> {
#[no_trace] // Step 1
/// The `mapping` is wrapped in an `Arc` to ensure thread safety. let size = range.end - range.start;
/// This is necessary for integration with the SpiderMonkey engine, // Step 2
pub mapping: Arc<Mutex<Vec<u8>>>, if size > (1 << 53) - 1 {
pub mapping_range: Range<u64>, return Err(Error::Range("Over MAX_SAFE_INTEGER".to_string()));
pub mapped_ranges: Vec<Range<u64>>, }
#[ignore_malloc_size_of = "defined in mozjs"] let size: usize = size
pub js_buffers: Vec<HeapBufferSource<ArrayBufferU8>>, .try_into()
pub map_mode: Option<u32>, .map_err(|_| Error::Range("Over usize".to_string()))?;
Ok(Self {
data: DataBlock::new_zeroed(size),
mode,
range,
})
}
} }
#[dom_struct] #[dom_struct]
@ -64,14 +70,18 @@ pub struct GPUBuffer {
#[no_trace] #[no_trace]
channel: WebGPU, channel: WebGPU,
label: DomRefCell<USVString>, label: DomRefCell<USVString>,
state: Cell<GPUBufferState>,
#[no_trace] #[no_trace]
buffer: WebGPUBuffer, buffer: WebGPUBuffer,
device: Dom<GPUDevice>, device: Dom<GPUDevice>,
/// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size>
size: GPUSize64, 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"] #[ignore_malloc_size_of = "promises are hard"]
map_promise: DomRefCell<Option<Rc<Promise>>>, pending_map: DomRefCell<Option<Rc<Promise>>>,
map_info: DomRefCell<Option<GPUBufferMapInfo>>, /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapping-slot>
mapping: DomRefCell<Option<ActiveBufferMapping>>,
} }
impl GPUBuffer { impl GPUBuffer {
@ -79,38 +89,37 @@ impl GPUBuffer {
channel: WebGPU, channel: WebGPU,
buffer: WebGPUBuffer, buffer: WebGPUBuffer,
device: &GPUDevice, device: &GPUDevice,
state: GPUBufferState,
size: GPUSize64, size: GPUSize64,
map_info: DomRefCell<Option<GPUBufferMapInfo>>, usage: GPUFlagsConstant,
mapping: Option<ActiveBufferMapping>,
label: USVString, label: USVString,
) -> Self { ) -> Self {
Self { Self {
reflector_: Reflector::new(), reflector_: Reflector::new(),
channel, channel,
label: DomRefCell::new(label), label: DomRefCell::new(label),
state: Cell::new(state),
device: Dom::from_ref(device), device: Dom::from_ref(device),
buffer, buffer,
map_promise: DomRefCell::new(None), pending_map: DomRefCell::new(None),
size, size,
map_info, usage,
mapping: DomRefCell::new(mapping),
} }
} }
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
global: &GlobalScope, global: &GlobalScope,
channel: WebGPU, channel: WebGPU,
buffer: WebGPUBuffer, buffer: WebGPUBuffer,
device: &GPUDevice, device: &GPUDevice,
state: GPUBufferState,
size: GPUSize64, size: GPUSize64,
map_info: DomRefCell<Option<GPUBufferMapInfo>>, usage: GPUFlagsConstant,
mapping: Option<ActiveBufferMapping>,
label: USVString, label: USVString,
) -> DomRoot<Self> { ) -> DomRoot<Self> {
reflect_dom_object( reflect_dom_object(
Box::new(GPUBuffer::new_inherited( Box::new(GPUBuffer::new_inherited(
channel, buffer, device, state, size, map_info, label, channel, buffer, device, size, usage, mapping, label,
)), )),
global, global,
) )
@ -121,86 +130,49 @@ impl GPUBuffer {
pub fn id(&self) -> WebGPUBuffer { pub fn id(&self) -> WebGPUBuffer {
self.buffer self.buffer
} }
pub fn state(&self) -> GPUBufferState {
self.state.get()
}
} }
impl Drop for GPUBuffer { impl Drop for GPUBuffer {
fn drop(&mut self) { fn drop(&mut self) {
if matches!(self.state(), GPUBufferState::Destroyed) { self.Destroy()
return;
}
if let Err(e) = self
.channel
.0
.send(WebGPURequest::DropBuffer(self.buffer.0))
{
warn!(
"Failed to send WebGPURequest::DropBuffer({:?}) ({})",
self.buffer.0, e
);
};
} }
} }
impl GPUBufferMethods for GPUBuffer { impl GPUBufferMethods for GPUBuffer {
#[allow(unsafe_code)]
/// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap> /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap>
fn Unmap(&self) -> Fallible<()> { fn Unmap(&self) {
let cx = GlobalScope::get_cx();
// Step 1 // Step 1
match self.state.get() { if let Some(promise) = self.pending_map.borrow_mut().take() {
GPUBufferState::Unmapped | GPUBufferState::Destroyed => { promise.reject_error(Error::Abort);
// TODO: Record validation error on the current scope }
return Ok(()); // Step 2
}, let mut mapping = self.mapping.borrow_mut().take();
// Step 3 let mapping = if let Some(mapping) = mapping.as_mut() {
GPUBufferState::Mapped | GPUBufferState::MappedAtCreation => { mapping
let mut info = self.map_info.borrow_mut();
let m_info = if let Some(m_info) = info.as_mut() {
m_info
} else { } else {
return Err(Error::Operation); return;
}; };
let m_range = m_info.mapping_range.clone();
// Step 3
mapping.data.clear_views();
// Step 5&7
if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer { if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer {
buffer_id: self.id().0, buffer_id: self.id().0,
device_id: self.device.id().0, array_buffer: IpcSharedMemory::from_bytes(mapping.data.data()),
array_buffer: IpcSharedMemory::from_bytes(&m_info.mapping.lock().unwrap()), write_back: mapping.mode >= GPUMapModeConstants::WRITE,
is_map_read: m_info.map_mode == Some(GPUMapModeConstants::READ), offset: mapping.range.start,
offset: m_range.start, size: mapping.range.end - mapping.range.start,
size: m_range.end - m_range.start,
}) { }) {
warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e); 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);
},
};
// Step 4
self.state.set(GPUBufferState::Unmapped);
*self.map_info.borrow_mut() = None;
Ok(())
} }
/// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy> /// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy
fn Destroy(&self) -> Fallible<()> { fn Destroy(&self) {
let state = self.state.get(); // Step 1
match state { self.Unmap();
GPUBufferState::Mapped | GPUBufferState::MappedAtCreation => { // Step 2
self.Unmap()?;
},
GPUBufferState::Destroyed => return Ok(()),
_ => {},
};
if let Err(e) = self if let Err(e) = self
.channel .channel
.0 .0
@ -211,11 +183,9 @@ impl GPUBufferMethods for GPUBuffer {
self.buffer.0, e 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( fn MapAsync(
&self, &self,
mode: u32, mode: u32,
@ -224,22 +194,16 @@ impl GPUBufferMethods for GPUBuffer {
comp: InRealm, comp: InRealm,
) -> Rc<Promise> { ) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp); let promise = Promise::new_in_current_realm(comp);
let range_size = if let Some(s) = size { // Step 2
s if self.pending_map.borrow().is_some() {
} else if offset >= self.size {
promise.reject_error(Error::Operation); promise.reject_error(Error::Operation);
return promise; 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 { let host_map = match mode {
GPUMapModeConstants::READ => HostMap::Read, GPUMapModeConstants::READ => HostMap::Read,
GPUMapModeConstants::WRITE => HostMap::Write, GPUMapModeConstants::WRITE => HostMap::Write,
@ -248,13 +212,11 @@ impl GPUBufferMethods for GPUBuffer {
.dispatch_error(webgpu::Error::Validation(String::from( .dispatch_error(webgpu::Error::Validation(String::from(
"Invalid MapModeFlags", "Invalid MapModeFlags",
))); )));
promise.reject_error(Error::Abort); self.map_failure(&promise);
return promise; return promise;
}, },
}; };
let map_range = offset..offset + range_size;
let sender = response_async(&promise, self); let sender = response_async(&promise, self);
if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync { if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync {
sender, sender,
@ -262,80 +224,53 @@ impl GPUBufferMethods for GPUBuffer {
device_id: self.device.id().0, device_id: self.device.id().0,
host_map, host_map,
offset, offset,
size: Some(range_size), size,
}) { }) {
warn!( warn!(
"Failed to send BufferMapAsync ({:?}) ({})", "Failed to send BufferMapAsync ({:?}) ({})",
self.buffer.0, e self.buffer.0, e
); );
promise.reject_error(Error::Operation); self.map_failure(&promise);
return promise; return promise;
} }
// Step 6
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());
promise promise
} }
/// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange> /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange>
#[allow(unsafe_code)]
fn GetMappedRange( fn GetMappedRange(
&self, &self,
cx: JSContext, _cx: JSContext,
offset: GPUSize64, offset: GPUSize64,
size: Option<GPUSize64>, size: Option<GPUSize64>,
) -> Fallible<ArrayBuffer> { ) -> Fallible<ArrayBuffer> {
let range_size = if let Some(s) = size { let range_size = if let Some(s) = size {
s s
} else if offset >= self.size {
return Err(Error::Operation);
} else { } else {
self.size - offset self.size.checked_sub(offset).unwrap_or(0)
}; };
let m_end = offset + range_size; // Step 2: validation
let mut info = self.map_info.borrow_mut(); let mut mapping = self.mapping.borrow_mut();
let m_info = if let Some(m_info) = info.as_mut() { let mapping = mapping.as_mut().ok_or(Error::Operation)?;
m_info
} else {
return Err(Error::Operation);
};
let mut valid = matches!(
self.state.get(),
GPUBufferState::Mapped | GPUBufferState::MappedAtCreation
);
valid &= offset % RANGE_OFFSET_ALIGN_MASK == 0 && let valid = offset % wgt::MAP_ALIGNMENT == 0 &&
range_size % RANGE_SIZE_ALIGN_MASK == 0 && range_size % wgt::COPY_BUFFER_ALIGNMENT == 0 &&
offset >= m_info.mapping_range.start && offset >= mapping.range.start &&
m_end <= m_info.mapping_range.end; offset + range_size <= mapping.range.end;
valid &= m_info
.mapped_ranges
.iter()
.all(|range| range.start >= m_end || range.end <= offset);
if !valid { if !valid {
return Err(Error::Operation); return Err(Error::Operation);
} }
let heap_typed_array = create_new_external_array_buffer::<ArrayBufferU8>( // Step 4
cx, // only mapping.range is mapped with mapping.range.start at 0
Arc::clone(&m_info.mapping), // so we need to rebase range to mapped.range
offset as usize, let rebased_offset = (offset - mapping.range.start) as usize;
range_size as usize, mapping
m_end as usize, .data
); .view(rebased_offset..rebased_offset + range_size as usize)
.map(|view| view.array_buffer())
let result = heap_typed_array.get_buffer().map_err(|_| Error::JSFailed); .map_err(|()| Error::Operation)
m_info.mapped_ranges.push(offset..m_end);
m_info.js_buffers.push(heap_typed_array);
result
} }
/// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
@ -347,30 +282,96 @@ impl GPUBufferMethods for GPUBuffer {
fn SetLabel(&self, value: USVString) { fn SetLabel(&self, value: USVString) {
*self.label.borrow_mut() = value; *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 { impl AsyncWGPUListener for GPUBuffer {
#[allow(unsafe_code)]
fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>) { fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>) {
match response { match response {
WebGPUResponse::BufferMapAsync(Ok(bytes)) => { WebGPUResponse::BufferMapAsync(Ok(mapping)) => self.map_success(promise, mapping),
*self WebGPUResponse::BufferMapAsync(Err(_)) => self.map_failure(promise),
.map_info _ => unreachable!("Wrong response received on AsyncWGPUListener for GPUBuffer"),
.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"),
}
*self.map_promise.borrow_mut() = None;
} }
} }

View file

@ -8,7 +8,7 @@ use std::hash::{Hash, Hasher};
use dom_struct::dom_struct; use dom_struct::dom_struct;
use webgpu::{WebGPU, WebGPUCommandBuffer, WebGPURequest}; 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::codegen::Bindings::WebGPUBinding::GPUCommandBufferMethods;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::root::{Dom, DomRoot};
@ -89,10 +89,6 @@ impl GPUCommandBuffer {
pub fn id(&self) -> WebGPUCommandBuffer { pub fn id(&self) -> WebGPUCommandBuffer {
self.command_buffer self.command_buffer
} }
pub fn buffers(&self) -> Ref<HashSet<Dom<GPUBuffer>>> {
self.buffers.borrow()
}
} }
impl GPUCommandBufferMethods for GPUCommandBuffer { impl GPUCommandBufferMethods for GPUCommandBuffer {

View file

@ -9,7 +9,6 @@ use std::cell::Cell;
use std::collections::HashMap; use std::collections::HashMap;
use std::num::NonZeroU64; use std::num::NonZeroU64;
use std::rc::Rc; use std::rc::Rc;
use std::sync::{Arc, Mutex};
use dom_struct::dom_struct; use dom_struct::dom_struct;
use js::jsapi::{Heap, JSObject}; use js::jsapi::{Heap, JSObject};
@ -24,7 +23,9 @@ use webgpu::{
WebGPUResponse, 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::codegen::UnionTypes::GPUPipelineLayoutOrGPUAutoLayoutMode;
use super::bindings::error::Fallible; use super::bindings::error::Fallible;
use super::gpu::AsyncWGPUListener; use super::gpu::AsyncWGPUListener;
@ -55,7 +56,7 @@ use crate::dom::gpu::response_async;
use crate::dom::gpuadapter::GPUAdapter; use crate::dom::gpuadapter::GPUAdapter;
use crate::dom::gpubindgroup::GPUBindGroup; use crate::dom::gpubindgroup::GPUBindGroup;
use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; 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::gpucommandencoder::GPUCommandEncoder;
use crate::dom::gpucomputepipeline::GPUComputePipeline; use crate::dom::gpucomputepipeline::GPUComputePipeline;
use crate::dom::gpuconvert::{ 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( fn get_pipeline_layout_data(
&self, &self,
layout: &GPUPipelineLayoutOrGPUAutoLayoutMode, layout: &GPUPipelineLayoutOrGPUAutoLayoutMode,
@ -452,31 +457,23 @@ impl GPUDeviceMethods for GPUDevice {
.expect("Failed to create WebGPU buffer"); .expect("Failed to create WebGPU buffer");
let buffer = webgpu::WebGPUBuffer(id); let buffer = webgpu::WebGPUBuffer(id);
let map_info; let mapping = if descriptor.mappedAtCreation {
let state; Some(ActiveBufferMapping::new(
if descriptor.mappedAtCreation { GPUMapModeConstants::WRITE,
let buf_data = vec![0u8; descriptor.size as usize]; 0..descriptor.size,
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;
} else { } else {
map_info = DomRefCell::new(None); None
state = GPUBufferState::Unmapped; };
}
Ok(GPUBuffer::new( Ok(GPUBuffer::new(
&self.global(), &self.global(),
self.channel.clone(), self.channel.clone(),
buffer, buffer,
self, self,
state,
descriptor.size, descriptor.size,
map_info, descriptor.usage,
mapping,
descriptor.parent.label.clone(), descriptor.parent.label.clone(),
)) ))
} }

View file

@ -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::root::{Dom, DomRoot};
use crate::dom::bindings::str::USVString; use crate::dom::bindings::str::USVString;
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::gpubuffer::{GPUBuffer, GPUBufferState}; use crate::dom::gpubuffer::GPUBuffer;
use crate::dom::gpucommandbuffer::GPUCommandBuffer; use crate::dom::gpucommandbuffer::GPUCommandBuffer;
use crate::dom::gpuconvert::{ use crate::dom::gpuconvert::{
convert_ic_texture, convert_image_data_layout, convert_texture_size_to_dict, 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> /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-submit>
fn Submit(&self, command_buffers: Vec<DomRoot<GPUCommandBuffer>>) { 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(); let command_buffers = command_buffers.iter().map(|cb| cb.id().0).collect();
self.channel self.channel
.0 .0

View file

@ -52,6 +52,12 @@ pub struct Promise {
permanent_js_root: Heap<JSVal>, 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>`. /// Private helper to enable adding new methods to `Rc<Promise>`.
trait PromiseHelper { trait PromiseHelper {
fn initialize(&self, cx: SafeJSContext); fn initialize(&self, cx: SafeJSContext);
@ -231,6 +237,18 @@ impl Promise {
matches!(state, PromiseState::Rejected | PromiseState::Fulfilled) matches!(state, PromiseState::Rejected | PromiseState::Fulfilled)
} }
#[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)] #[allow(unsafe_code)]
pub fn promise_obj(&self) -> HandleObject { pub fn promise_obj(&self) -> HandleObject {
let obj = self.reflector().get_jsobject(); let obj = self.reflector().get_jsobject();

View file

@ -165,17 +165,28 @@ GPUDevice includes GPUObjectBase;
[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"] [Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"]
interface GPUBuffer { interface GPUBuffer {
readonly attribute GPUSize64Out size;
readonly attribute GPUFlagsConstant usage;
readonly attribute GPUBufferMapState mapState;
[NewObject] [NewObject]
Promise<undefined> mapAsync(GPUMapModeFlags mode, optional GPUSize64 offset = 0, optional GPUSize64 size); Promise<undefined> mapAsync(GPUMapModeFlags mode, optional GPUSize64 offset = 0, optional GPUSize64 size);
[NewObject, Throws] [NewObject, Throws]
ArrayBuffer getMappedRange(optional GPUSize64 offset = 0, optional GPUSize64 size); ArrayBuffer getMappedRange(optional GPUSize64 offset = 0, optional GPUSize64 size);
[Throws]
undefined unmap(); undefined unmap();
[Throws]
undefined destroy(); undefined destroy();
}; };
GPUBuffer includes GPUObjectBase; GPUBuffer includes GPUObjectBase;
enum GPUBufferMapState {
"unmapped",
"pending",
"mapped",
};
dictionary GPUBufferDescriptor : GPUObjectDescriptorBase { dictionary GPUBufferDescriptor : GPUObjectDescriptorBase {
required GPUSize64 size; required GPUSize64 size;
required GPUBufferUsageFlags usage; required GPUBufferUsageFlags usage;
@ -184,24 +195,24 @@ dictionary GPUBufferDescriptor : GPUObjectDescriptorBase {
typedef [EnforceRange] unsigned long GPUBufferUsageFlags; typedef [EnforceRange] unsigned long GPUBufferUsageFlags;
[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] [Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"]
interface GPUBufferUsage { namespace GPUBufferUsage {
const GPUBufferUsageFlags MAP_READ = 0x0001; const GPUFlagsConstant MAP_READ = 0x0001;
const GPUBufferUsageFlags MAP_WRITE = 0x0002; const GPUFlagsConstant MAP_WRITE = 0x0002;
const GPUBufferUsageFlags COPY_SRC = 0x0004; const GPUFlagsConstant COPY_SRC = 0x0004;
const GPUBufferUsageFlags COPY_DST = 0x0008; const GPUFlagsConstant COPY_DST = 0x0008;
const GPUBufferUsageFlags INDEX = 0x0010; const GPUFlagsConstant INDEX = 0x0010;
const GPUBufferUsageFlags VERTEX = 0x0020; const GPUFlagsConstant VERTEX = 0x0020;
const GPUBufferUsageFlags UNIFORM = 0x0040; const GPUFlagsConstant UNIFORM = 0x0040;
const GPUBufferUsageFlags STORAGE = 0x0080; const GPUFlagsConstant STORAGE = 0x0080;
const GPUBufferUsageFlags INDIRECT = 0x0100; const GPUFlagsConstant INDIRECT = 0x0100;
const GPUBufferUsageFlags QUERY_RESOLVE = 0x0200; const GPUFlagsConstant QUERY_RESOLVE = 0x0200;
}; };
typedef [EnforceRange] unsigned long GPUMapModeFlags; typedef [EnforceRange] unsigned long GPUMapModeFlags;
[Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"] [Exposed=(Window, DedicatedWorker), Pref="dom.webgpu.enabled"]
interface GPUMapMode { namespace GPUMapMode {
const GPUMapModeFlags READ = 0x0001; const GPUFlagsConstant READ = 0x0001;
const GPUMapModeFlags WRITE = 0x0002; const GPUFlagsConstant WRITE = 0x0002;
}; };
[Exposed=(Window, DedicatedWorker), Serializable , Pref="dom.webgpu.enabled"] [Exposed=(Window, DedicatedWorker), Serializable , Pref="dom.webgpu.enabled"]

View file

@ -267,9 +267,8 @@ pub enum WebGPURequest {
}, },
UnmapBuffer { UnmapBuffer {
buffer_id: id::BufferId, buffer_id: id::BufferId,
device_id: id::DeviceId,
array_buffer: IpcSharedMemory, array_buffer: IpcSharedMemory,
is_map_read: bool, write_back: bool,
offset: u64, offset: u64,
size: u64, size: u64,
}, },

View file

@ -4,10 +4,13 @@
//! IPC messages that are send to WebGPU DOM objects. //! IPC messages that are send to WebGPU DOM objects.
use std::ops::Range;
use ipc_channel::ipc::IpcSharedMemory; use ipc_channel::ipc::IpcSharedMemory;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use wgc::id; use wgc::id;
use wgc::pipeline::CreateShaderModuleError; use wgc::pipeline::CreateShaderModuleError;
use wgpu_core::device::HostMap;
use wgpu_core::instance::{RequestAdapterError, RequestDeviceError}; use wgpu_core::instance::{RequestAdapterError, RequestDeviceError};
use wgpu_core::resource::BufferAccessError; use wgpu_core::resource::BufferAccessError;
pub use {wgpu_core as wgc, wgpu_types as wgt}; pub use {wgpu_core as wgc, wgpu_types as wgt};
@ -72,6 +75,13 @@ pub struct Pipeline<T: std::fmt::Debug + Serialize> {
pub label: String, pub label: String,
} }
#[derive(Debug, Deserialize, Serialize)]
pub struct Mapping {
pub data: IpcSharedMemory,
pub mode: HostMap,
pub range: Range<u64>,
}
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub enum WebGPUResponse { pub enum WebGPUResponse {
@ -85,7 +95,7 @@ pub enum WebGPUResponse {
Result<wgt::DeviceDescriptor<Option<String>>, RequestDeviceError>, Result<wgt::DeviceDescriptor<Option<String>>, RequestDeviceError>,
), ),
), ),
BufferMapAsync(Result<IpcSharedMemory, BufferAccessError>), BufferMapAsync(Result<Mapping, BufferAccessError>),
SubmittedWorkDone, SubmittedWorkDone,
PoppedErrorScope(Result<Option<Error>, PopError>), PoppedErrorScope(Result<Option<Error>, PopError>),
CompilationInfo(Option<ShaderCompilationInfo>), CompilationInfo(Option<ShaderCompilationInfo>),

View file

@ -40,8 +40,8 @@ use crate::gpu_error::ErrorScope;
use crate::poll_thread::Poller; use crate::poll_thread::Poller;
use crate::render_commands::apply_render_command; use crate::render_commands::apply_render_command;
use crate::{ use crate::{
Adapter, ComputePassId, Error, Pipeline, PopError, PresentationData, RenderPassId, WebGPU, Adapter, ComputePassId, Error, Mapping, Pipeline, PopError, PresentationData, RenderPassId,
WebGPUAdapter, WebGPUDevice, WebGPUMsg, WebGPUQueue, WebGPURequest, WebGPUResponse, WebGPU, WebGPUAdapter, WebGPUDevice, WebGPUMsg, WebGPUQueue, WebGPURequest, WebGPUResponse,
}; };
pub const PRESENTATION_BUFFER_COUNT: usize = 10; pub const PRESENTATION_BUFFER_COUNT: usize = 10;
@ -191,11 +191,11 @@ impl WGPU {
let callback = BufferMapCallback::from_rust(Box::from( let callback = BufferMapCallback::from_rust(Box::from(
move |result: BufferAccessResult| { move |result: BufferAccessResult| {
drop(token); drop(token);
let response = result.map(|_| { let response = result.and_then(|_| {
let global = &glob; let global = &glob;
let (slice_pointer, range_size) = gfx_select!(buffer_id => let (slice_pointer, range_size) = gfx_select!(buffer_id =>
global.buffer_get_mapped_range(buffer_id, 0, None)) global.buffer_get_mapped_range(buffer_id, offset, size))
.unwrap(); ?;
// SAFETY: guarantee to be safe from wgpu // SAFETY: guarantee to be safe from wgpu
let data = unsafe { let data = unsafe {
slice::from_raw_parts( slice::from_raw_parts(
@ -204,7 +204,11 @@ impl WGPU {
) )
}; };
IpcSharedMemory::from_bytes(data) Ok(Mapping {
data: IpcSharedMemory::from_bytes(data),
range: offset..offset + range_size,
mode: host_map,
})
}); });
if let Err(e) = if let Err(e) =
resp_sender.send(WebGPUResponse::BufferMapAsync(response)) resp_sender.send(WebGPUResponse::BufferMapAsync(response))
@ -226,13 +230,6 @@ impl WGPU {
operation operation
)); ));
self.poller.wake(); self.poller.wake();
if let Err(e) = &result {
if let Err(w) =
sender.send(WebGPUResponse::BufferMapAsync(Err(e.to_owned())))
{
warn!("Failed to send BufferMapAsync Response ({:?})", w);
}
}
// Per spec we also need to raise validation error here // Per spec we also need to raise validation error here
self.maybe_dispatch_wgpu_error(device_id, result.err()); self.maybe_dispatch_wgpu_error(device_id, result.err());
}, },
@ -1208,21 +1205,20 @@ impl WGPU {
}, },
WebGPURequest::UnmapBuffer { WebGPURequest::UnmapBuffer {
buffer_id, buffer_id,
device_id,
array_buffer, array_buffer,
is_map_read, write_back,
offset, offset,
size, size,
} => { } => {
let global = &self.global; let global = &self.global;
if !is_map_read { if write_back {
let (slice_pointer, range_size) = if let Ok((slice_pointer, range_size)) = gfx_select!(
gfx_select!(buffer_id => global.buffer_get_mapped_range( buffer_id => global.buffer_get_mapped_range(
buffer_id, buffer_id,
offset, offset,
Some(size) Some(size)
)) )
.unwrap(); ) {
unsafe { unsafe {
slice::from_raw_parts_mut( slice::from_raw_parts_mut(
slice_pointer.as_ptr(), slice_pointer.as_ptr(),
@ -1231,8 +1227,9 @@ impl WGPU {
} }
.copy_from_slice(&array_buffer); .copy_from_slice(&array_buffer);
} }
let result = gfx_select!(buffer_id => global.buffer_unmap(buffer_id)); }
self.maybe_dispatch_wgpu_error(device_id, result.err()); // Ignore result because this operation always succeed from user perspective
let _result = gfx_select!(buffer_id => global.buffer_unmap(buffer_id));
}, },
WebGPURequest::WriteBuffer { WebGPURequest::WriteBuffer {
device_id, device_id,

File diff suppressed because it is too large Load diff