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 super::bindings::buffer_source::DataBlock;
use super::bindings::codegen::Bindings::WebGPUBinding::{
GPUBufferDescriptor, GPUBufferMapState, GPUFlagsConstant, GPUMapModeFlags,
};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
GPUBufferMethods, GPUMapModeConstants, 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::gpu::{response_async, AsyncWGPUListener};
use crate::dom::gpudevice::GPUDevice;
use crate::dom::promise::Promise;
use crate::realms::InRealm;
use crate::script_runtime::{CanGc, JSContext};
#[derive(JSTraceable, MallocSizeOf)]
pub struct ActiveBufferMapping {
pub data: DataBlock,
mode: GPUMapModeFlags,
range: Range<u64>,
}
impl ActiveBufferMapping {
pub fn new(mode: GPUMapModeFlags, range: Range<u64>) -> Fallible<Self> {
let size = range.end - range.start;
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>,
size: GPUSize64,
usage: GPUFlagsConstant,
#[ignore_malloc_size_of = "promises are hard"]
pending_map: DomRefCell<Option<Rc<Promise>>>,
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
}
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 for GPUBuffer {
#[allow(unsafe_code)]
fn Unmap(&self) {
if let Some(promise) = self.pending_map.borrow_mut().take() {
promise.reject_error(Error::Abort);
}
let mut mapping = self.mapping.borrow_mut().take();
let mapping = if let Some(mapping) = mapping.as_mut() {
mapping
} else {
return;
};
mapping.data.clear_views();
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);
}
}
fn Destroy(&self) {
self.Unmap();
if let Err(e) = self
.channel
.0
.send(WebGPURequest::DestroyBuffer(self.buffer.0))
{
warn!(
"Failed to send WebGPURequest::DestroyBuffer({:?}) ({})",
self.buffer.0, e
);
};
}
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);
if self.pending_map.borrow().is_some() {
promise.reject_error(Error::Operation);
return promise;
}
*self.pending_map.borrow_mut() = Some(promise.clone());
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;
}
promise
}
#[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)
};
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);
}
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)
}
fn Label(&self) -> USVString {
self.label.borrow().clone()
}
fn SetLabel(&self, value: USVString) {
*self.label.borrow_mut() = value;
}
fn Size(&self) -> GPUSize64 {
self.size
}
fn Usage(&self) -> GPUFlagsConstant {
self.usage
}
fn MapState(&self) -> GPUBufferMapState {
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();
if pending_map.as_ref() != Some(p) {
assert!(p.is_rejected());
return;
}
assert!(p.is_pending());
pending_map.take();
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();
if pending_map.as_ref() != Some(p) {
assert!(p.is_rejected());
return;
}
assert!(p.is_pending());
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) => {
mapping.data.load(&wgpu_mapping.data);
self.mapping.borrow_mut().replace(mapping);
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"),
}
}
}