| /* 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_traits::{Mapping, WebGPU, WebGPUBuffer, WebGPURequest}; |
| use wgpu_core::device::HostMap; |
| use wgpu_core::resource::BufferAccessError; |
| |
| use crate::conversions::Convert; |
| 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::{DomGlobal, Reflector, reflect_dom_object}; |
| use crate::dom::bindings::root::{Dom, DomRoot}; |
| use crate::dom::bindings::str::USVString; |
| use crate::dom::globalscope::GlobalScope; |
| use crate::dom::promise::Promise; |
| use crate::dom::webgpu::gpudevice::GPUDevice; |
| use crate::realms::InRealm; |
| use crate::routed_promise::{RoutedPromiseListener, route_promise}; |
| use crate::script_runtime::{CanGc, JSContext}; |
| |
| #[derive(JSTraceable, MallocSizeOf)] |
| pub(crate) 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(crate) 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(crate) 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(crate) 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(crate) fn new( |
| global: &GlobalScope, |
| channel: WebGPU, |
| buffer: WebGPUBuffer, |
| device: &GPUDevice, |
| size: GPUSize64, |
| usage: GPUFlagsConstant, |
| mapping: Option<ActiveBufferMapping>, |
| label: USVString, |
| can_gc: CanGc, |
| ) -> DomRoot<Self> { |
| reflect_dom_object( |
| Box::new(GPUBuffer::new_inherited( |
| channel, buffer, device, size, usage, mapping, label, |
| )), |
| global, |
| can_gc, |
| ) |
| } |
| } |
| |
| impl GPUBuffer { |
| pub(crate) fn id(&self) -> WebGPUBuffer { |
| self.buffer |
| } |
| |
| /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbuffer> |
| pub(crate) fn create( |
| device: &GPUDevice, |
| descriptor: &GPUBufferDescriptor, |
| can_gc: CanGc, |
| ) -> Fallible<DomRoot<GPUBuffer>> { |
| let desc = wgpu_types::BufferDescriptor { |
| label: (&descriptor.parent).convert(), |
| size: descriptor.size as wgpu_types::BufferAddress, |
| usage: wgpu_types::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(), |
| can_gc, |
| )) |
| } |
| } |
| |
| 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, CanGc::note()); |
| } |
| // 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, can_gc); |
| 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_traits::Error::Validation(String::from( |
| "Invalid MapModeFlags", |
| ))); |
| self.map_failure(&promise, can_gc); |
| return promise; |
| }, |
| }; |
| |
| let sender = route_promise( |
| &promise, |
| self, |
| self.global().task_manager().dom_manipulation_task_source(), |
| ); |
| 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, can_gc); |
| 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>, |
| can_gc: CanGc, |
| ) -> 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 % wgpu_types::MAP_ALIGNMENT == 0 && |
| range_size % wgpu_types::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, can_gc) |
| .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>, can_gc: CanGc) { |
| 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, can_gc); |
| } else { |
| p.reject_error(Error::Operation, can_gc); |
| } |
| } |
| |
| fn map_success(&self, p: &Rc<Promise>, wgpu_mapping: Mapping, can_gc: CanGc) { |
| 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(), can_gc); |
| }, |
| 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(&(), can_gc); |
| }, |
| } |
| } |
| } |
| |
| impl RoutedPromiseListener<Result<Mapping, BufferAccessError>> for GPUBuffer { |
| fn handle_response( |
| &self, |
| response: Result<Mapping, BufferAccessError>, |
| promise: &Rc<Promise>, |
| can_gc: CanGc, |
| ) { |
| match response { |
| Ok(mapping) => self.map_success(promise, mapping, can_gc), |
| Err(_) => self.map_failure(promise, can_gc), |
| } |
| } |
| } |