diff options
author | atbrakhi <atbrakhi@igalia.com> | 2024-11-28 15:24:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-28 14:24:15 +0000 |
commit | d2d3407501b83d03db522b5dde5e159073fd9e4b (patch) | |
tree | 299be7d04ca2470f1c6c8741e46784a2c7e85dd7 /components/script/dom/webgpu/gpubuffer.rs | |
parent | a37ccc3e64c92e8ba10a3cdc48ebd7f031bb7298 (diff) | |
download | servo-d2d3407501b83d03db522b5dde5e159073fd9e4b.tar.gz servo-d2d3407501b83d03db522b5dde5e159073fd9e4b.zip |
Move script gpu files into webgpu folder (#34415)
Signed-off-by: atbrakhi <atbrakhi@igalia.com>
Diffstat (limited to 'components/script/dom/webgpu/gpubuffer.rs')
-rw-r--r-- | components/script/dom/webgpu/gpubuffer.rs | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/components/script/dom/webgpu/gpubuffer.rs b/components/script/dom/webgpu/gpubuffer.rs new file mode 100644 index 00000000000..cab38d067c2 --- /dev/null +++ b/components/script/dom/webgpu/gpubuffer.rs @@ -0,0 +1,425 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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::ops::Range; +use std::rc::Rc; +use std::string::String; + +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSharedMemory; +use js::typedarray::ArrayBuffer; +use webgpu::wgc::device::HostMap; +use webgpu::{wgt, Mapping, WebGPU, WebGPUBuffer, WebGPURequest, WebGPUResponse}; + +use crate::dom::bindings::buffer_source::DataBlock; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUBufferDescriptor, GPUBufferMapState, GPUBufferMethods, GPUFlagsConstant, + GPUMapModeConstants, GPUMapModeFlags, GPUSize64, +}; +use crate::dom::bindings::error::{Error, Fallible}; +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::gpudevice::GPUDevice; +use crate::dom::promise::Promise; +use crate::dom::webgpu::gpu::{response_async, AsyncWGPUListener}; +use crate::realms::InRealm; +use crate::script_runtime::{CanGc, JSContext}; + +#[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>, +} + +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] +pub struct GPUBuffer { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[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"] + pending_map: DomRefCell<Option<Rc<Promise>>>, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapping-slot> + mapping: DomRefCell<Option<ActiveBufferMapping>>, +} + +impl GPUBuffer { + fn new_inherited( + channel: WebGPU, + buffer: WebGPUBuffer, + device: &GPUDevice, + size: GPUSize64, + usage: GPUFlagsConstant, + mapping: Option<ActiveBufferMapping>, + label: USVString, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(label), + device: Dom::from_ref(device), + buffer, + pending_map: DomRefCell::new(None), + size, + usage, + mapping: DomRefCell::new(mapping), + } + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + global: &GlobalScope, + channel: WebGPU, + buffer: WebGPUBuffer, + device: &GPUDevice, + size: GPUSize64, + usage: GPUFlagsConstant, + mapping: Option<ActiveBufferMapping>, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUBuffer::new_inherited( + channel, buffer, device, size, usage, mapping, label, + )), + global, + ) + } +} + +impl GPUBuffer { + pub fn id(&self) -> WebGPUBuffer { + self.buffer + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbuffer> + pub fn create( + device: &GPUDevice, + descriptor: &GPUBufferDescriptor, + ) -> Fallible<DomRoot<GPUBuffer>> { + let desc = wgt::BufferDescriptor { + label: (&descriptor.parent).into(), + size: descriptor.size as wgt::BufferAddress, + usage: wgt::BufferUsages::from_bits_retain(descriptor.usage), + mapped_at_creation: descriptor.mappedAtCreation, + }; + let id = device.global().wgpu_id_hub().create_buffer_id(); + + device + .channel() + .0 + .send(WebGPURequest::CreateBuffer { + device_id: device.id().0, + buffer_id: id, + descriptor: desc, + }) + .expect("Failed to create WebGPU buffer"); + + let buffer = WebGPUBuffer(id); + let mapping = if descriptor.mappedAtCreation { + Some(ActiveBufferMapping::new( + GPUMapModeConstants::WRITE, + 0..descriptor.size, + )?) + } else { + None + }; + + Ok(GPUBuffer::new( + &device.global(), + device.channel().clone(), + buffer, + device, + descriptor.size, + descriptor.usage, + mapping, + descriptor.parent.label.clone(), + )) + } +} + +impl Drop for GPUBuffer { + fn drop(&mut self) { + self.Destroy() + } +} + +impl GPUBufferMethods<crate::DomTypeHolder> for GPUBuffer { + #[allow(unsafe_code)] + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap> + fn Unmap(&self) { + // Step 1 + 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 3 + mapping.data.clear_views(); + // Step 5&7 + if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer { + buffer_id: self.id().0, + mapping: if mapping.mode >= GPUMapModeConstants::WRITE { + Some(Mapping { + data: IpcSharedMemory::from_bytes(mapping.data.data()), + range: mapping.range.clone(), + mode: HostMap::Write, + }) + } else { + None + }, + }) { + warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy> + fn Destroy(&self) { + // Step 1 + self.Unmap(); + // Step 2 + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DestroyBuffer(self.buffer.0)) + { + warn!( + "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})", + self.buffer.0, e + ); + }; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapasync> + fn MapAsync( + &self, + mode: u32, + offset: GPUSize64, + size: Option<GPUSize64>, + comp: InRealm, + can_gc: CanGc, + ) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(comp, can_gc); + // Step 2 + if self.pending_map.borrow().is_some() { + promise.reject_error(Error::Operation); + return promise; + } + // Step 4 + *self.pending_map.borrow_mut() = Some(promise.clone()); + // Step 5 + let host_map = match mode { + GPUMapModeConstants::READ => HostMap::Read, + GPUMapModeConstants::WRITE => HostMap::Write, + _ => { + self.device + .dispatch_error(webgpu::Error::Validation(String::from( + "Invalid MapModeFlags", + ))); + self.map_failure(&promise); + return promise; + }, + }; + + let sender = response_async(&promise, self); + if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync { + sender, + buffer_id: self.buffer.0, + device_id: self.device.id().0, + host_map, + offset, + size, + }) { + warn!( + "Failed to send BufferMapAsync ({:?}) ({})", + self.buffer.0, e + ); + self.map_failure(&promise); + return promise; + } + // Step 6 + promise + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange> + #[allow(unsafe_code)] + fn GetMappedRange( + &self, + _cx: JSContext, + offset: GPUSize64, + size: Option<GPUSize64>, + ) -> Fallible<ArrayBuffer> { + let range_size = if let Some(s) = size { + s + } else { + self.size.saturating_sub(offset) + }; + // Step 2: validation + let mut mapping = self.mapping.borrow_mut(); + let mapping = mapping.as_mut().ok_or(Error::Operation)?; + + 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); + } + + // 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> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + 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>, _can_gc: CanGc) { + match response { + WebGPUResponse::BufferMapAsync(Ok(mapping)) => self.map_success(promise, mapping), + WebGPUResponse::BufferMapAsync(Err(_)) => self.map_failure(promise), + _ => unreachable!("Wrong response received on AsyncWGPUListener for GPUBuffer"), + } + } +} |