1use std::ops::Range;
6
7use dom_struct::dom_struct;
8use js::context::{JSContext, NoGC};
9use js::realm::CurrentRealm;
10use js::typedarray::HeapArrayBuffer;
11use jstraceable_derive::JSTraceable;
12use log::{error, warn};
13use malloc_size_of_derive::MallocSizeOf;
14use script_bindings::DomTypes;
15use script_bindings::cell::DomRefCell;
16use script_bindings::codegen::GenericBindings::WebGPUBinding::{
17 GPUBufferDescriptor, GPUBufferMapState, GPUBufferMethods, GPUBufferWrap, GPUFlagsConstant,
18 GPUMapModeConstants, GPUMapModeFlags, GPUSize64,
19};
20use script_bindings::error::{Error, Fallible};
21use script_bindings::interfaces::{PromiseHelpers, StackRootPromiseHelpers};
22use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
23use script_bindings::trace::RootedTraceableBox;
24use servo_base::generic_channel::GenericSharedMemory;
25use webgpu_traits::{
26 BufferAddress, BufferDescriptor, BufferUsages, COPY_BUFFER_ALIGNMENT, HostMap, MAP_ALIGNMENT,
27 Mapping, WebGPU, WebGPUBuffer, WebGPURequest,
28};
29
30use crate::datablock::DataBlock;
31use crate::dom::bindings::root::{Dom, DomRoot};
32use crate::dom::bindings::str::USVString;
33use crate::gpuconvert::WebGPUConvert;
34use crate::traits::{Equivalence, WebGPUGlobalTrait, WebGPUPromise, WebGPUPromiseCallbackTrait};
35
36#[derive(JSTraceable, MallocSizeOf)]
37#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
38pub(crate) struct ActiveBufferMapping {
39 pub(crate) data: DataBlock,
43 mode: GPUMapModeFlags,
45 range: Range<u64>,
47}
48
49impl ActiveBufferMapping {
50 pub(crate) fn new(
52 mode: GPUMapModeFlags,
53 range: Range<u64>,
54 ) -> Fallible<RootedTraceableBox<Self>> {
55 let size = range.end - range.start;
57 if size > (1 << 53) - 1 {
59 return Err(Error::Range(c"Over MAX_SAFE_INTEGER".to_owned()));
60 }
61 let size: usize = size
62 .try_into()
63 .map_err(|_| Error::Range(c"Over usize".to_owned()))?;
64 Ok(RootedTraceableBox::new(Self {
65 data: DataBlock::new_zeroed(size),
66 mode,
67 range,
68 }))
69 }
70}
71
72#[derive(JSTraceable, MallocSizeOf)]
73pub(crate) struct DroppableGPUBuffer {
74 #[no_trace]
75 channel: WebGPU,
76 #[no_trace]
77 buffer: WebGPUBuffer,
78}
79
80impl Drop for DroppableGPUBuffer {
81 fn drop(&mut self) {
82 if let Err(e) = self
83 .channel
84 .0
85 .send(WebGPURequest::DropBuffer(self.buffer.0))
86 {
87 error!(
88 "Failed to send WebGPURequest::DropBuffer({:?}) ({}) - Potential leak",
89 self.buffer.0, e
90 );
91 }
92 }
93}
94
95#[dom_struct]
96pub struct GPUBuffer<D: DomTypes> {
97 reflector_: Reflector,
98 droppable: DroppableGPUBuffer,
99 label: DomRefCell<USVString>,
100 device: Dom<D::GPUDevice>,
101 size: GPUSize64,
103 usage: GPUFlagsConstant,
105 pending_map: DomRefCell<Option<<D::Promise as PromiseHelpers<D>>::HeapTraced>>,
107 mapping: DomRefCell<Option<ActiveBufferMapping>>,
109}
110
111impl<D> GPUBuffer<D>
112where
113 D: Equivalence,
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: Equivalence,
163 <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
164{
165 pub fn id(&self) -> WebGPUBuffer {
166 self.droppable.buffer
167 }
168
169 pub(crate) fn create(
171 cx: &mut js::context::JSContext,
172 device: &D::GPUDevice,
173 descriptor: &GPUBufferDescriptor,
174 ) -> Fallible<DomRoot<GPUBuffer<D>>> {
175 let desc = BufferDescriptor {
176 label: (&descriptor.parent).convert(),
177 size: descriptor.size as BufferAddress,
178 usage: BufferUsages::from_bits_retain(descriptor.usage),
179 mapped_at_creation: descriptor.mappedAtCreation,
180 };
181 let id = <D::GPUDevice as DomGlobalGeneric<D>>::global_from_reflector(device)
182 .global_wgpu_id_hub()
183 .create_buffer_id();
184
185 device
186 .channel()
187 .0
188 .send(WebGPURequest::CreateBuffer {
189 device_id: device.id().0,
190 buffer_id: id,
191 descriptor: desc,
192 })
193 .expect("Failed to create WebGPU buffer");
194
195 let buffer = WebGPUBuffer(id);
196 let mapping = if descriptor.mappedAtCreation {
197 Some(ActiveBufferMapping::new(
198 GPUMapModeConstants::WRITE,
199 0..descriptor.size,
200 )?)
201 } else {
202 None
203 };
204
205 let global = <D::GPUDevice as DomGlobalGeneric<D>>::global_from_reflector(device);
206 Ok(GPUBuffer::new(
207 cx,
208 &*global,
209 device.channel(),
210 buffer,
211 device,
212 descriptor.size,
213 descriptor.usage,
214 mapping,
215 descriptor.parent.label.clone(),
216 ))
217 }
218}
219
220impl<D> GPUBufferMethods<D> for GPUBuffer<D>
221where
222 D: Equivalence,
223 <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
224{
225 fn Unmap(&self, cx: &mut js::context::JSContext) {
227 let promise = self.pending_map.safe_borrow_mut(cx).take();
229 if let Some(promise) = promise {
230 promise.reject_error(cx, Error::Abort(Some("No pending map".into())));
231 }
232 let mut mapping = RootedTraceableBox::new(self.mapping.safe_borrow_mut(cx).take());
234 let mapping = if let Some(mapping) = mapping.as_mut() {
235 mapping
236 } else {
237 return;
238 };
239
240 mapping.data.clear_views(cx);
242 if let Err(e) = self.droppable.channel.0.send(WebGPURequest::UnmapBuffer {
244 buffer_id: self.id().0,
245 mapping: if mapping.mode >= GPUMapModeConstants::WRITE {
246 Some(Mapping {
247 data: GenericSharedMemory::from_bytes(mapping.data.data()),
248 range: mapping.range.clone(),
249 mode: HostMap::Write,
250 })
251 } else {
252 None
253 },
254 }) {
255 warn!(
256 "Failed to send Buffer unmap ({:?}) ({})",
257 self.droppable.buffer.0, e
258 );
259 }
260 }
261
262 fn Destroy(&self, cx: &mut JSContext) {
264 self.Unmap(cx);
266 if let Err(e) = self
268 .droppable
269 .channel
270 .0
271 .send(WebGPURequest::DestroyBuffer(self.droppable.buffer.0))
272 {
273 warn!(
274 "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})",
275 self.droppable.buffer.0, e
276 );
277 };
278 }
279
280 fn MapAsync(
282 &self,
283 cx: &mut CurrentRealm<'_>,
284 mode: u32,
285 offset: GPUSize64,
286 size: Option<GPUSize64>,
287 ) -> <D::Promise as PromiseHelpers<D>>::StackRoot {
288 let promise = D::Promise::new_in_realm_rooted(cx);
289 if self.pending_map.borrow().is_some() {
291 promise.reject_error(
292 cx,
293 Error::Operation(Some("There is already an active map".into())),
294 );
295 return promise;
296 }
297 *self.pending_map.safe_borrow_mut(cx) = Some(promise.to_traced());
299 let host_map = match mode {
301 GPUMapModeConstants::READ => HostMap::Read,
302 GPUMapModeConstants::WRITE => HostMap::Write,
303 _ => {
304 self.device
305 .dispatch_error(webgpu_traits::Error::Validation(String::from(
306 "Invalid MapModeFlags",
307 )));
308 self.map_failure(cx, &promise);
309 return promise;
310 },
311 };
312
313 let callback = promise.callback_promise_dom_manipulation_task_source(self);
314 if let Err(e) = self
315 .droppable
316 .channel
317 .0
318 .send(WebGPURequest::BufferMapAsync {
319 callback,
320 buffer_id: self.droppable.buffer.0,
321 device_id: self.device.id().0,
322 host_map,
323 offset,
324 size,
325 })
326 {
327 warn!(
328 "Failed to send BufferMapAsync ({:?}) ({})",
329 self.droppable.buffer.0, e
330 );
331 self.map_failure(cx, &promise);
332 return promise;
333 }
334 promise
336 }
337
338 fn GetMappedRange(
340 &self,
341 cx: &mut js::context::JSContext,
342 offset: GPUSize64,
343 size: Option<GPUSize64>,
344 ) -> Fallible<RootedTraceableBox<HeapArrayBuffer>> {
345 let range_size = if let Some(s) = size {
346 s
347 } else {
348 self.size.saturating_sub(offset)
349 };
350 let mut mapping = self
352 .mapping
353 .safe_borrow_mut(cx)
354 .take()
355 .map(RootedTraceableBox::new)
356 .ok_or(Error::Operation(Some("No active buffer map".into())))?;
357
358 if !(offset.is_multiple_of(MAP_ALIGNMENT)) {
359 self.mapping
360 .safe_borrow_mut(cx)
361 .replace(*mapping.into_box());
362
363 return Err(Error::Operation(Some(
364 "`offset` is not a multiple of 8".into(),
365 )));
366 }
367
368 if !(range_size % COPY_BUFFER_ALIGNMENT == 0) {
369 self.mapping
370 .safe_borrow_mut(cx)
371 .replace(*mapping.into_box());
372
373 return Err(Error::Operation(Some(
374 "`rangeSize` is not a multiple of 4".into(),
375 )));
376 }
377
378 if !(offset >= mapping.range.start) {
379 self.mapping
380 .safe_borrow_mut(cx)
381 .replace(*mapping.into_box());
382
383 return Err(Error::Operation(Some(
384 "`offset` is greater than `[[mapping]].range[0]`".into(),
385 )));
386 }
387
388 if !(offset + range_size <= mapping.range.end) {
389 self.mapping
390 .safe_borrow_mut(cx)
391 .replace(*mapping.into_box());
392
393 return Err(Error::Operation(Some(
394 "`offset` + `rangeSize` is less than or equal to `[[mapping]].range[1]`".into(),
395 )));
396 }
397
398 let rebased_offset = (offset - mapping.range.start) as usize;
402 let result = mapping
403 .data
404 .view(cx, rebased_offset..rebased_offset + range_size as usize)
405 .map(|view| view.array_buffer())
406 .map_err(|()| {
407 Error::Operation(Some(
408 "Mapped range overlaps with others or is out of bounds.".into(),
409 ))
410 });
411
412 self.mapping
413 .safe_borrow_mut(cx)
414 .replace(*mapping.into_box());
415 result
416 }
417
418 fn Label(&self) -> USVString {
420 self.label.borrow().clone()
421 }
422
423 fn SetLabel(&self, no_gc: &NoGC, value: USVString) {
425 *self.label.safe_borrow_mut(no_gc) = value;
426 }
427
428 fn Size(&self) -> GPUSize64 {
430 self.size
431 }
432
433 fn Usage(&self) -> GPUFlagsConstant {
435 self.usage
436 }
437
438 fn MapState(&self) -> GPUBufferMapState {
440 if self.mapping.borrow().is_some() {
442 GPUBufferMapState::Mapped
443 } else if self.pending_map.borrow().is_some() {
444 GPUBufferMapState::Pending
445 } else {
446 GPUBufferMapState::Unmapped
447 }
448 }
449}
450
451impl<D> GPUBuffer<D>
452where
453 D: Equivalence,
454 <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
455{
456 pub fn map_failure(
457 &self,
458 cx: &mut JSContext,
459 p: &<D::Promise as PromiseHelpers<D>>::StackRoot,
460 ) {
461 if self.pending_map.borrow().as_deref() != Some(p) {
463 assert!(p.is_rejected());
464 return;
465 }
466 assert!(p.is_pending());
468 self.pending_map.safe_borrow_mut(cx).take();
470 let is_lost = self.device.is_lost();
472 if is_lost {
473 p.reject_error(cx, Error::Abort(Some("GPUDevice is lost".into())));
474 } else {
475 p.reject_error(cx, Error::Operation(Some("Failed to map GPUBuffer".into())));
476 }
477 }
478
479 pub fn map_success(
480 &self,
481 cx: &mut js::context::JSContext,
482 p: &<D::Promise as PromiseHelpers<D>>::StackRoot,
483 wgpu_mapping: Mapping,
484 ) {
485 if self.pending_map.borrow().as_deref() != Some(p) {
487 assert!(p.is_rejected());
488 return;
489 }
490
491 assert!(p.is_pending());
493
494 let mapping = ActiveBufferMapping::new(
496 match wgpu_mapping.mode {
497 HostMap::Read => GPUMapModeConstants::READ,
498 HostMap::Write => GPUMapModeConstants::WRITE,
499 },
500 wgpu_mapping.range,
501 );
502
503 match mapping {
504 Err(error) => {
505 *self.pending_map.safe_borrow_mut(cx) = None;
506 p.reject_error(cx, error);
507 },
508 Ok(mut mapping) => {
509 mapping.data.load(&wgpu_mapping.data);
511 self.mapping
513 .safe_borrow_mut(cx)
514 .replace(*mapping.into_box());
515 self.pending_map.safe_borrow_mut(cx).take();
517 p.resolve_native(cx, &());
518 },
519 }
520 }
521}