1use std::ops::Range;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::context::{JSContext, NoGC};
10use js::realm::CurrentRealm;
11use js::typedarray::HeapArrayBuffer;
12use jstraceable_derive::JSTraceable;
13use log::{error, warn};
14use malloc_size_of_derive::MallocSizeOf;
15use script_bindings::DomTypes;
16use script_bindings::cell::DomRefCell;
17use script_bindings::codegen::GenericBindings::WebGPUBinding::{
18 GPUBufferDescriptor, GPUBufferMapState, GPUBufferMethods, GPUBufferWrap, GPUFlagsConstant,
19 GPUMapModeConstants, GPUMapModeFlags, GPUSize64,
20};
21use script_bindings::error::{Error, Fallible};
22use script_bindings::interfaces::PromiseHelpers;
23use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
24use script_bindings::trace::RootedTraceableBox;
25use servo_base::generic_channel::GenericSharedMemory;
26use webgpu_traits::{Mapping, WebGPU, WebGPUBuffer, WebGPURequest};
27use wgpu_core::device::HostMap;
28
29use crate::datablock::DataBlock;
30use crate::dom::bindings::root::{Dom, DomRoot};
31use crate::dom::bindings::str::USVString;
32use crate::gpuconvert::WebGPUConvert;
33use crate::traits::{GPUDeviceTrait, WebGPUGlobalTrait, WebGPUPromiseTrait};
34
35#[derive(JSTraceable, MallocSizeOf)]
36#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
37pub(crate) struct ActiveBufferMapping {
38 pub(crate) data: DataBlock,
42 mode: GPUMapModeFlags,
44 range: Range<u64>,
46}
47
48impl ActiveBufferMapping {
49 pub(crate) fn new(
51 mode: GPUMapModeFlags,
52 range: Range<u64>,
53 ) -> Fallible<RootedTraceableBox<Self>> {
54 let size = range.end - range.start;
56 if size > (1 << 53) - 1 {
58 return Err(Error::Range(c"Over MAX_SAFE_INTEGER".to_owned()));
59 }
60 let size: usize = size
61 .try_into()
62 .map_err(|_| Error::Range(c"Over usize".to_owned()))?;
63 Ok(RootedTraceableBox::new(Self {
64 data: DataBlock::new_zeroed(size),
65 mode,
66 range,
67 }))
68 }
69}
70
71#[derive(JSTraceable, MallocSizeOf)]
72pub struct DroppableGPUBuffer {
73 #[no_trace]
74 channel: WebGPU,
75 #[no_trace]
76 buffer: WebGPUBuffer,
77}
78
79impl Drop for DroppableGPUBuffer {
80 fn drop(&mut self) {
81 if let Err(e) = self
82 .channel
83 .0
84 .send(WebGPURequest::DropBuffer(self.buffer.0))
85 {
86 error!(
87 "Failed to send WebGPURequest::DropBuffer({:?}) ({}) - Potential leak",
88 self.buffer.0, e
89 );
90 }
91 }
92}
93
94#[dom_struct]
95pub struct GPUBuffer<D: DomTypes> {
96 reflector_: Reflector,
97 droppable: DroppableGPUBuffer,
98 label: DomRefCell<USVString>,
99 device: Dom<D::GPUDevice>,
100 size: GPUSize64,
102 usage: GPUFlagsConstant,
104 #[conditional_malloc_size_of]
106 pending_map: DomRefCell<Option<Rc<D::Promise>>>,
107 mapping: DomRefCell<Option<ActiveBufferMapping>>,
109}
110
111impl<D> GPUBuffer<D>
112where
113 D: DomTypes<GPUBuffer = GPUBuffer<D>>,
114 D::Promise: PromiseHelpers<D>,
115{
116 fn new_inherited(
117 channel: WebGPU,
118 buffer: WebGPUBuffer,
119 device: &D::GPUDevice,
120 size: GPUSize64,
121 usage: GPUFlagsConstant,
122 mapping: Option<RootedTraceableBox<ActiveBufferMapping>>,
123 label: USVString,
124 ) -> Self {
125 Self {
126 reflector_: Reflector::new(),
127 droppable: DroppableGPUBuffer { channel, buffer },
128 label: DomRefCell::new(label),
129 device: Dom::from_ref(device),
130 pending_map: DomRefCell::new(None),
131 size,
132 usage,
133 mapping: DomRefCell::new(mapping.map(|mapping| *mapping.into_box())),
134 }
135 }
136
137 #[allow(clippy::too_many_arguments)]
138 pub(crate) fn new(
139 cx: &mut js::context::JSContext,
140 global: &D::GlobalScope,
141 channel: WebGPU,
142 buffer: WebGPUBuffer,
143 device: &D::GPUDevice,
144 size: GPUSize64,
145 usage: GPUFlagsConstant,
146 mapping: Option<RootedTraceableBox<ActiveBufferMapping>>,
147 label: USVString,
148 ) -> DomRoot<Self> {
149 reflect_dom_object_with_wrap::<D, _, _>(
150 Box::new(GPUBuffer::new_inherited(
151 channel, buffer, device, size, usage, mapping, label,
152 )),
153 global,
154 cx,
155 GPUBufferWrap::<D>,
156 )
157 }
158}
159
160impl<D> GPUBuffer<D>
161where
162 D: DomTypes<GPUBuffer = GPUBuffer<D>>,
163 D::GPUDevice: DomGlobalGeneric<D> + GPUDeviceTrait<D>,
164 D::GlobalScope: WebGPUGlobalTrait,
165 D::Promise: PromiseHelpers<D>,
166{
167 pub fn id(&self) -> WebGPUBuffer {
168 self.droppable.buffer
169 }
170
171 pub fn create(
173 cx: &mut js::context::JSContext,
174 device: &D::GPUDevice,
175 descriptor: &GPUBufferDescriptor,
176 ) -> Fallible<DomRoot<GPUBuffer<D>>> {
177 let desc = wgpu_types::BufferDescriptor {
178 label: (&descriptor.parent).convert(),
179 size: descriptor.size as wgpu_types::BufferAddress,
180 usage: wgpu_types::BufferUsages::from_bits_retain(descriptor.usage),
181 mapped_at_creation: descriptor.mappedAtCreation,
182 };
183 let id = <D::GPUDevice as DomGlobalGeneric<D>>::global_from_reflector(device)
184 .global_wgpu_id_hub()
185 .create_buffer_id();
186
187 device
188 .channel()
189 .0
190 .send(WebGPURequest::CreateBuffer {
191 device_id: device.id().0,
192 buffer_id: id,
193 descriptor: desc,
194 })
195 .expect("Failed to create WebGPU buffer");
196
197 let buffer = WebGPUBuffer(id);
198 let mapping = if descriptor.mappedAtCreation {
199 Some(ActiveBufferMapping::new(
200 GPUMapModeConstants::WRITE,
201 0..descriptor.size,
202 )?)
203 } else {
204 None
205 };
206
207 let global = <D::GPUDevice as DomGlobalGeneric<D>>::global_from_reflector(device);
208 Ok(GPUBuffer::new(
209 cx,
210 &*global,
211 device.channel(),
212 buffer,
213 device,
214 descriptor.size,
215 descriptor.usage,
216 mapping,
217 descriptor.parent.label.clone(),
218 ))
219 }
220}
221
222impl<D> GPUBufferMethods<D> for GPUBuffer<D>
223where
224 D: DomTypes<GPUBuffer = GPUBuffer<D>>,
225 D::Promise: PromiseHelpers<D> + WebGPUPromiseTrait<D> + PartialEq,
226 D::GPUDevice: GPUDeviceTrait<D>,
227 D::GPUDevice: DomGlobalGeneric<D> + GPUDeviceTrait<D>,
228 D::GlobalScope: WebGPUGlobalTrait,
229 D::Promise: PromiseHelpers<D>,
230{
231 fn Unmap(&self, cx: &mut js::context::JSContext) {
233 let promise = self.pending_map.safe_borrow_mut(cx).take();
235 if let Some(promise) = promise {
236 promise.reject_error(cx, Error::Abort(Some("No pending map".into())));
237 }
238 let mut mapping = RootedTraceableBox::new(self.mapping.safe_borrow_mut(cx).take());
240 let mapping = if let Some(mapping) = mapping.as_mut() {
241 mapping
242 } else {
243 return;
244 };
245
246 mapping.data.clear_views(cx);
248 if let Err(e) = self.droppable.channel.0.send(WebGPURequest::UnmapBuffer {
250 buffer_id: self.id().0,
251 mapping: if mapping.mode >= GPUMapModeConstants::WRITE {
252 Some(Mapping {
253 data: GenericSharedMemory::from_bytes(mapping.data.data()),
254 range: mapping.range.clone(),
255 mode: HostMap::Write,
256 })
257 } else {
258 None
259 },
260 }) {
261 warn!(
262 "Failed to send Buffer unmap ({:?}) ({})",
263 self.droppable.buffer.0, e
264 );
265 }
266 }
267
268 fn Destroy(&self, cx: &mut JSContext) {
270 self.Unmap(cx);
272 if let Err(e) = self
274 .droppable
275 .channel
276 .0
277 .send(WebGPURequest::DestroyBuffer(self.droppable.buffer.0))
278 {
279 warn!(
280 "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})",
281 self.droppable.buffer.0, e
282 );
283 };
284 }
285
286 fn MapAsync(
288 &self,
289 cx: &mut CurrentRealm<'_>,
290 mode: u32,
291 offset: GPUSize64,
292 size: Option<GPUSize64>,
293 ) -> Rc<D::Promise> {
294 let promise = D::Promise::new_in_realm(cx);
295 if self.pending_map.borrow().is_some() {
297 promise.reject_error(
298 cx,
299 Error::Operation(Some("There is already an active map".into())),
300 );
301 return promise;
302 }
303 *self.pending_map.safe_borrow_mut(cx) = Some(promise.clone());
305 let host_map = match mode {
307 GPUMapModeConstants::READ => HostMap::Read,
308 GPUMapModeConstants::WRITE => HostMap::Write,
309 _ => {
310 self.device
311 .dispatch_error(webgpu_traits::Error::Validation(String::from(
312 "Invalid MapModeFlags",
313 )));
314 self.map_failure(cx, &promise);
315 return promise;
316 },
317 };
318
319 let callback = D::Promise::callback_promise_gpubuffer(&promise, self);
320 if let Err(e) = self
321 .droppable
322 .channel
323 .0
324 .send(WebGPURequest::BufferMapAsync {
325 callback,
326 buffer_id: self.droppable.buffer.0,
327 device_id: self.device.id().0,
328 host_map,
329 offset,
330 size,
331 })
332 {
333 warn!(
334 "Failed to send BufferMapAsync ({:?}) ({})",
335 self.droppable.buffer.0, e
336 );
337 self.map_failure(cx, &promise);
338 return promise;
339 }
340 promise
342 }
343
344 fn GetMappedRange(
346 &self,
347 cx: &mut js::context::JSContext,
348 offset: GPUSize64,
349 size: Option<GPUSize64>,
350 ) -> Fallible<RootedTraceableBox<HeapArrayBuffer>> {
351 let range_size = if let Some(s) = size {
352 s
353 } else {
354 self.size.saturating_sub(offset)
355 };
356 let mut mapping = self
358 .mapping
359 .safe_borrow_mut(cx)
360 .take()
361 .map(RootedTraceableBox::new)
362 .ok_or(Error::Operation(Some("No active buffer map".into())))?;
363
364 let valid = offset.is_multiple_of(wgpu_types::MAP_ALIGNMENT) &&
365 range_size % wgpu_types::COPY_BUFFER_ALIGNMENT == 0 &&
366 offset >= mapping.range.start &&
367 offset + range_size <= mapping.range.end;
368 if !valid {
369 self.mapping
370 .safe_borrow_mut(cx)
371 .replace(*mapping.into_box());
372 return Err(Error::Operation(Some(
373 "Buffer Mapping is not active".into(),
374 )));
375 }
376
377 let rebased_offset = (offset - mapping.range.start) as usize;
381 let result = mapping
382 .data
383 .view(cx, rebased_offset..rebased_offset + range_size as usize)
384 .map(|view| view.array_buffer())
385 .map_err(|()| {
386 Error::Operation(Some(
387 "Mapped range overlaps with others or is out of bounds.".into(),
388 ))
389 });
390
391 self.mapping
392 .safe_borrow_mut(cx)
393 .replace(*mapping.into_box());
394 result
395 }
396
397 fn Label(&self) -> USVString {
399 self.label.borrow().clone()
400 }
401
402 fn SetLabel(&self, no_gc: &NoGC, value: USVString) {
404 *self.label.safe_borrow_mut(no_gc) = value;
405 }
406
407 fn Size(&self) -> GPUSize64 {
409 self.size
410 }
411
412 fn Usage(&self) -> GPUFlagsConstant {
414 self.usage
415 }
416
417 fn MapState(&self) -> GPUBufferMapState {
419 if self.mapping.borrow().is_some() {
421 GPUBufferMapState::Mapped
422 } else if self.pending_map.borrow().is_some() {
423 GPUBufferMapState::Pending
424 } else {
425 GPUBufferMapState::Unmapped
426 }
427 }
428}
429
430impl<D> GPUBuffer<D>
431where
432 D: DomTypes,
433 D::Promise: PromiseHelpers<D> + PartialEq,
434 D::GPUDevice: GPUDeviceTrait<D>,
435{
436 pub fn map_failure(&self, cx: &mut JSContext, p: &Rc<D::Promise>) {
437 if self.pending_map.borrow().as_ref() != Some(p) {
439 assert!(p.is_rejected());
440 return;
441 }
442 assert!(p.is_pending());
444 self.pending_map.safe_borrow_mut(cx).take();
446 let is_lost = self.device.is_lost();
448 if is_lost {
449 p.reject_error(cx, Error::Abort(Some("GPUDevice is lost".into())));
450 } else {
451 p.reject_error(cx, Error::Operation(Some("Mapping failure".into())));
452 }
453 }
454
455 pub fn map_success(
456 &self,
457 cx: &mut js::context::JSContext,
458 p: &Rc<D::Promise>,
459 wgpu_mapping: Mapping,
460 ) {
461 if self.pending_map.borrow().as_ref() != Some(p) {
463 assert!(p.is_rejected());
464 return;
465 }
466
467 assert!(p.is_pending());
469
470 let mapping = ActiveBufferMapping::new(
472 match wgpu_mapping.mode {
473 HostMap::Read => GPUMapModeConstants::READ,
474 HostMap::Write => GPUMapModeConstants::WRITE,
475 },
476 wgpu_mapping.range,
477 );
478
479 match mapping {
480 Err(error) => {
481 *self.pending_map.safe_borrow_mut(cx) = None;
482 p.reject_error(cx, error);
483 },
484 Ok(mut mapping) => {
485 mapping.data.load(&wgpu_mapping.data);
487 self.mapping
489 .safe_borrow_mut(cx)
490 .replace(*mapping.into_box());
491 self.pending_map.safe_borrow_mut(cx).take();
493 p.resolve_native(cx, &());
494 },
495 }
496 }
497}