mirror of
https://github.com/servo/servo
synced 2026-05-12 09:56:50 +02:00
Reviewable per commits: As noted in https://github.com/servo/servo/pull/42180#discussion_r2749861902 transmuting `&Reflector<AssociatedMemory>` to `&Reflector<()>` will ignore AssociatedMemory information and thus it will not remove extra associated memory. By returning `&Reflector<Self::ReflectorType>` from `DomObject::reflector()` we will preserve this information and thus correctly handle cases with associated memory. We also do not need `overrideMemoryUsage` in bindings.conf anymore. 🎉 Instead of removing associated memory in drop code we should do it as part of finalizers, otherwise we have problems in nested (inherited) structs, where drop is run for both parent and child, but we only added associated memory for child (on init_reflector) which already included size of parent. The only exception here is promise, because it is RCed and not finalized. Testing: Tested locally that it fixes speedometer. Fixes #42269 --------- Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
446 lines
14 KiB
Rust
446 lines
14 KiB
Rust
/* 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 base::generic_channel::GenericSharedMemory;
|
|
use dom_struct::dom_struct;
|
|
use js::typedarray::HeapArrayBuffer;
|
|
use script_bindings::trace::RootedTraceableBox;
|
|
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, callback_promise};
|
|
use crate::script_runtime::{CanGc, JSContext};
|
|
|
|
#[derive(JSTraceable, MallocSizeOf)]
|
|
pub(crate) struct ActiveBufferMapping {
|
|
// TODO(sagudev): Use GenericSharedMemory 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>
|
|
#[conditional_malloc_size_of]
|
|
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 {
|
|
/// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap>
|
|
fn Unmap(&self) {
|
|
// Step 1
|
|
let promise = self.pending_map.borrow_mut().take();
|
|
if let Some(promise) = promise {
|
|
promise.reject_error(Error::Abort(None), CanGc::note());
|
|
*self.pending_map.borrow_mut() = Some(promise);
|
|
}
|
|
// 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: GenericSharedMemory::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(None), 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 callback = callback_promise(
|
|
&promise,
|
|
self,
|
|
self.global().task_manager().dom_manipulation_task_source(),
|
|
);
|
|
if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync {
|
|
callback,
|
|
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>
|
|
fn GetMappedRange(
|
|
&self,
|
|
_cx: JSContext,
|
|
offset: GPUSize64,
|
|
size: Option<GPUSize64>,
|
|
can_gc: CanGc,
|
|
) -> Fallible<RootedTraceableBox<HeapArrayBuffer>> {
|
|
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()
|
|
.take()
|
|
.ok_or(Error::Operation(None))?;
|
|
|
|
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 {
|
|
self.mapping.borrow_mut().replace(mapping);
|
|
return Err(Error::Operation(None));
|
|
}
|
|
|
|
// 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;
|
|
let result = mapping
|
|
.data
|
|
.view(rebased_offset..rebased_offset + range_size as usize, can_gc)
|
|
.map(|view| view.array_buffer())
|
|
.map_err(|()| Error::Operation(None));
|
|
|
|
self.mapping.borrow_mut().replace(mapping);
|
|
result
|
|
}
|
|
|
|
/// <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) {
|
|
// Step 1
|
|
if self.pending_map.borrow().as_ref() != Some(p) {
|
|
assert!(p.is_rejected());
|
|
return;
|
|
}
|
|
// Step 2
|
|
assert!(p.is_pending());
|
|
// Step 3
|
|
self.pending_map.borrow_mut().take();
|
|
// Step 4
|
|
let is_lost = self.device.is_lost();
|
|
if is_lost {
|
|
p.reject_error(Error::Abort(None), can_gc);
|
|
} else {
|
|
p.reject_error(Error::Operation(None), can_gc);
|
|
}
|
|
}
|
|
|
|
fn map_success(&self, p: &Rc<Promise>, wgpu_mapping: Mapping, can_gc: CanGc) {
|
|
// Step 1
|
|
if self.pending_map.borrow().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) => {
|
|
*self.pending_map.borrow_mut() = 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
|
|
self.pending_map.borrow_mut().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),
|
|
}
|
|
}
|
|
}
|