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::routed_promise::RoutedPromiseListener;
24use script_bindings::trace::RootedTraceableBox;
25use servo_base::generic_channel::GenericSharedMemory;
26use webgpu_traits::{
27 BufferAccessError, BufferAddress, BufferDescriptor, BufferUpdate, BufferUsages,
28 COPY_BUFFER_ALIGNMENT, HostMap, MAP_ALIGNMENT, Mapping, WebGPU, WebGPUBuffer, WebGPURequest,
29};
30
31use crate::datablock::DataBlock;
32use crate::dom::bindings::root::{Dom, DomRoot};
33use crate::dom::bindings::str::USVString;
34use crate::gpuconvert::WebGPUConvert;
35use crate::traits::{Equivalence, WebGPUGlobalTrait, WebGPUPromise, WebGPUPromiseCallbackTrait};
36
37#[derive(JSTraceable, MallocSizeOf)]
38#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
39pub(crate) struct ActiveBufferMapping {
40 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 data: Option<GenericSharedMemory>,
55 ) -> Fallible<RootedTraceableBox<Self>> {
56 let size = range.end - range.start;
58 if size > (1 << 53) - 1 {
60 return Err(Error::Range(c"Over MAX_SAFE_INTEGER".to_owned()));
61 }
62 let size: usize = size
63 .try_into()
64 .map_err(|_| Error::Range(c"Over usize".to_owned()))?;
65 Ok(RootedTraceableBox::new(Self {
66 data: data
67 .map(DataBlock::new_from_shared_memory)
68 .unwrap_or_else(|| DataBlock::new_zeroed(size)),
69 mode,
70 range,
71 }))
72 }
73
74 #[cfg_attr(
75 crown,
76 expect(
77 crown::unrooted_must_root,
78 reason = "No GC can happen when this is called"
79 )
80 )]
81 pub(crate) fn consume(self) -> (GenericSharedMemory, GPUMapModeFlags, Range<u64>) {
82 let ActiveBufferMapping { data, mode, range } = self;
83 (data.consume(), mode, range)
84 }
85}
86
87#[derive(JSTraceable, MallocSizeOf)]
88pub(crate) struct DroppableGPUBuffer {
89 #[no_trace]
90 channel: WebGPU,
91 #[no_trace]
92 buffer: WebGPUBuffer,
93}
94
95impl Drop for DroppableGPUBuffer {
96 fn drop(&mut self) {
97 if let Err(e) = self
98 .channel
99 .0
100 .send(WebGPURequest::DropBuffer(self.buffer.0))
101 {
102 error!(
103 "Failed to send WebGPURequest::DropBuffer({:?}) ({}) - Potential leak",
104 self.buffer.0, e
105 );
106 }
107 }
108}
109
110#[dom_struct]
111pub struct GPUBuffer<D: DomTypes> {
112 reflector_: Reflector,
113 droppable: DroppableGPUBuffer,
114 label: DomRefCell<USVString>,
115 device: Dom<D::GPUDevice>,
116 size: GPUSize64,
118 usage: GPUFlagsConstant,
120 pending_map: DomRefCell<Option<<D::Promise as PromiseHelpers<D>>::HeapTraced>>,
122 mapping: DomRefCell<Option<ActiveBufferMapping>>,
124}
125
126impl<D> GPUBuffer<D>
127where
128 D: Equivalence,
129 D::Promise: PromiseHelpers<D>,
130{
131 fn new_inherited(
132 channel: WebGPU,
133 buffer: WebGPUBuffer,
134 device: &D::GPUDevice,
135 size: GPUSize64,
136 usage: GPUFlagsConstant,
137 mapping: Option<RootedTraceableBox<ActiveBufferMapping>>,
138 label: USVString,
139 ) -> Self {
140 Self {
141 reflector_: Reflector::new(),
142 droppable: DroppableGPUBuffer { channel, buffer },
143 label: DomRefCell::new(label),
144 device: Dom::from_ref(device),
145 pending_map: DomRefCell::new(None),
146 size,
147 usage,
148 mapping: DomRefCell::new(mapping.map(|mapping| *mapping.into_box())),
149 }
150 }
151
152 #[expect(clippy::too_many_arguments)]
153 pub(crate) fn new(
154 cx: &mut js::context::JSContext,
155 global: &D::GlobalScope,
156 channel: WebGPU,
157 buffer: WebGPUBuffer,
158 device: &D::GPUDevice,
159 size: GPUSize64,
160 usage: GPUFlagsConstant,
161 mapping: Option<RootedTraceableBox<ActiveBufferMapping>>,
162 label: USVString,
163 ) -> DomRoot<Self> {
164 reflect_dom_object_with_wrap::<D, _, _>(
165 cx,
166 Box::new(GPUBuffer::new_inherited(
167 channel, buffer, device, size, usage, mapping, label,
168 )),
169 global,
170 GPUBufferWrap::<D>,
171 )
172 }
173}
174
175impl<D> GPUBuffer<D>
176where
177 D: Equivalence,
178 <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
179{
180 pub fn id(&self) -> WebGPUBuffer {
181 self.droppable.buffer
182 }
183
184 pub(crate) fn create(
186 cx: &mut js::context::JSContext,
187 device: &D::GPUDevice,
188 descriptor: &GPUBufferDescriptor,
189 ) -> Fallible<DomRoot<GPUBuffer<D>>> {
190 let desc = BufferDescriptor {
191 label: (&descriptor.parent).convert(),
192 size: descriptor.size as BufferAddress,
193 usage: BufferUsages::from_bits_retain(descriptor.usage),
194 mapped_at_creation: descriptor.mappedAtCreation,
195 };
196 let id = <D::GPUDevice as DomGlobalGeneric<D>>::global_from_reflector(device)
197 .global_wgpu_id_hub()
198 .create_buffer_id();
199
200 device
201 .channel()
202 .0
203 .send(WebGPURequest::CreateBuffer {
204 device_id: device.id().0,
205 buffer_id: id,
206 descriptor: desc,
207 })
208 .expect("Failed to create WebGPU buffer");
209
210 let buffer = WebGPUBuffer(id);
211 let mapping = if descriptor.mappedAtCreation {
212 Some(ActiveBufferMapping::new(
213 GPUMapModeConstants::WRITE,
214 0..descriptor.size,
215 None,
216 )?)
217 } else {
218 None
219 };
220
221 let global = <D::GPUDevice as DomGlobalGeneric<D>>::global_from_reflector(device);
222 Ok(GPUBuffer::new(
223 cx,
224 &*global,
225 device.channel(),
226 buffer,
227 device,
228 descriptor.size,
229 descriptor.usage,
230 mapping,
231 descriptor.parent.label.clone(),
232 ))
233 }
234}
235
236impl<D> GPUBufferMethods<D> for GPUBuffer<D>
237where
238 D: Equivalence,
239 <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
240{
241 fn Unmap(&self, cx: &mut js::context::JSContext) {
243 let promise = self.pending_map.safe_borrow_mut(cx).take();
246 if let Some(promise) = promise {
247 promise.reject_error(cx, Error::Abort(Some("No pending map".into())));
249 }
250 let mut rooted_mapping = RootedTraceableBox::new(self.mapping.safe_borrow_mut(cx).take());
252 let mapping = if let Some(mapping) = rooted_mapping.as_mut() {
253 mapping
254 } else {
255 return;
257 };
258
259 mapping.data.clear_views(cx);
262 let (data, mode, range) = rooted_mapping.into_box().unwrap().consume();
263 let buffer_update = if mode & GPUMapModeConstants::WRITE > 0 {
267 BufferUpdate::Write(data, range)
268 } else {
269 BufferUpdate::Read(data)
272 };
273 if let Err(e) = self.droppable.channel.0.send(WebGPURequest::UnmapBuffer {
274 buffer_id: self.id().0,
275 buffer_update,
276 }) {
277 warn!(
278 "Failed to send Buffer unmap ({:?}) ({})",
279 self.droppable.buffer.0, e
280 );
281 }
282 }
283
284 fn Destroy(&self, cx: &mut JSContext) {
286 self.Unmap(cx);
288 if let Err(e) = self
290 .droppable
291 .channel
292 .0
293 .send(WebGPURequest::DestroyBuffer(self.droppable.buffer.0))
294 {
295 warn!(
296 "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})",
297 self.droppable.buffer.0, e
298 );
299 };
300 }
301
302 fn MapAsync(
304 &self,
305 cx: &mut CurrentRealm<'_>,
306 mode: u32,
307 offset: GPUSize64,
308 size: Option<GPUSize64>,
309 ) -> <D::Promise as PromiseHelpers<D>>::StackRoot {
310 let p = D::Promise::new_in_realm(cx);
312 if self.MapState() != GPUBufferMapState::Unmapped {
314 self.device
316 .dispatch_error(webgpu_traits::Error::Validation(String::from(
317 "Buffer is not in unmapped state",
318 )));
319 p.reject_error(
321 cx,
322 Error::Operation(Some("Buffer is not in unmapped state".into())),
323 );
324 return p;
325 }
326 *self.pending_map.safe_borrow_mut(cx) = Some(p.to_traced());
328 let host_map = match mode {
330 GPUMapModeConstants::READ => HostMap::Read,
331 GPUMapModeConstants::WRITE => HostMap::Write,
332 _ => {
333 self.device
334 .dispatch_error(webgpu_traits::Error::Validation(String::from(
335 "Invalid MapModeFlags",
336 )));
337 self.map_failure(cx, &p);
338 return p;
339 },
340 };
341
342 let callback = p.callback_promise_dom_manipulation_task_source(self);
343 if let Err(e) = self
344 .droppable
345 .channel
346 .0
347 .send(WebGPURequest::BufferMapAsync {
348 callback,
349 buffer_id: self.droppable.buffer.0,
350 device_id: self.device.id().0,
351 host_map,
352 offset,
353 size,
354 buffer_size: self.size,
355 })
356 {
357 warn!(
358 "Failed to send BufferMapAsync ({:?}) ({})",
359 self.droppable.buffer.0, e
360 );
361 self.map_failure(cx, &p);
362 return p;
363 }
364 p
366 }
367
368 fn GetMappedRange(
370 &self,
371 cx: &mut js::context::JSContext,
372 offset: GPUSize64,
373 size: Option<GPUSize64>,
374 ) -> Fallible<RootedTraceableBox<HeapArrayBuffer>> {
375 let range_size = size.unwrap_or(self.size.saturating_sub(offset));
379 let mut mapping = self
383 .mapping
384 .safe_borrow_mut(cx)
385 .take()
386 .map(RootedTraceableBox::new)
387 .ok_or(Error::Operation(Some("No active buffer map".into())))?;
388
389 if !(offset.is_multiple_of(MAP_ALIGNMENT)) {
391 self.mapping
392 .safe_borrow_mut(cx)
393 .replace(*mapping.into_box());
394
395 return Err(Error::Operation(Some(
396 "`offset` is not a multiple of 8".into(),
397 )));
398 }
399
400 if !range_size.is_multiple_of(COPY_BUFFER_ALIGNMENT) {
402 self.mapping
403 .safe_borrow_mut(cx)
404 .replace(*mapping.into_box());
405
406 return Err(Error::Operation(Some(
407 "`rangeSize` is not a multiple of 4".into(),
408 )));
409 }
410
411 if !(offset >= mapping.range.start) {
413 self.mapping
414 .safe_borrow_mut(cx)
415 .replace(*mapping.into_box());
416
417 return Err(Error::Operation(Some(
418 "`offset` is greater than `[[mapping]].range[0]`".into(),
419 )));
420 }
421
422 if !(offset + range_size <= mapping.range.end) {
424 self.mapping
425 .safe_borrow_mut(cx)
426 .replace(*mapping.into_box());
427
428 return Err(Error::Operation(Some(
429 "`offset` + `rangeSize` is less than or equal to `[[mapping]].range[1]`".into(),
430 )));
431 }
432
433 let result = mapping
441 .data
442 .view(cx, offset as usize..(offset as usize + range_size as usize))
443 .map(|view| view.array_buffer())
444 .map_err(|()| {
445 Error::Operation(Some(
446 "Mapped range overlaps with others or is out of bounds.".into(),
447 ))
448 });
449
450 self.mapping
451 .safe_borrow_mut(cx)
452 .replace(*mapping.into_box());
453
454 result
456 }
457
458 fn Label(&self) -> USVString {
460 self.label.borrow().clone()
461 }
462
463 fn SetLabel(&self, no_gc: &NoGC, value: USVString) {
465 *self.label.safe_borrow_mut(no_gc) = value;
466 }
467
468 fn Size(&self) -> GPUSize64 {
470 self.size
471 }
472
473 fn Usage(&self) -> GPUFlagsConstant {
475 self.usage
476 }
477
478 fn MapState(&self) -> GPUBufferMapState {
480 if self.mapping.borrow().is_some() {
482 GPUBufferMapState::Mapped
483 } else if self.pending_map.borrow().is_some() {
484 GPUBufferMapState::Pending
485 } else {
486 GPUBufferMapState::Unmapped
487 }
488 }
489}
490
491impl<D> GPUBuffer<D>
492where
493 D: Equivalence,
494{
495 pub fn map_failure(
496 &self,
497 cx: &mut JSContext,
498 p: &<D::Promise as PromiseHelpers<D>>::StackRoot,
499 ) {
500 if self.pending_map.borrow().as_deref() != Some(p) {
502 assert!(p.is_rejected());
503 return;
504 }
505 assert!(p.is_pending());
507 self.pending_map.safe_borrow_mut(cx).take();
509 let is_lost = self.device.is_lost();
511 if is_lost {
512 p.reject_error(cx, Error::Abort(Some("GPUDevice is lost".into())));
513 } else {
514 p.reject_error(cx, Error::Operation(Some("Failed to map GPUBuffer".into())));
515 }
516 }
517
518 pub fn map_success(
519 &self,
520 cx: &mut js::context::JSContext,
521 p: &<D::Promise as PromiseHelpers<D>>::StackRoot,
522 Mapping { data, mode, range }: Mapping,
523 ) {
524 if self.pending_map.borrow().as_deref() != Some(p) {
526 assert!(p.is_rejected());
527 return;
528 }
529
530 assert!(p.is_pending());
532
533 let mapping = ActiveBufferMapping::new(
535 match mode {
536 HostMap::Read => GPUMapModeConstants::READ,
537 HostMap::Write => GPUMapModeConstants::WRITE,
538 },
539 range,
540 Some(data),
541 );
542
543 match mapping {
544 Err(error) => {
545 *self.pending_map.safe_borrow_mut(cx) = None;
546 p.reject_error(cx, error);
547 },
548 Ok(mapping) => {
549 self.mapping
551 .safe_borrow_mut(cx)
552 .replace(*mapping.into_box());
553 self.pending_map.safe_borrow_mut(cx).take();
555 p.resolve_native(cx, &());
556 },
557 }
558 }
559}
560
561impl<D: Equivalence> RoutedPromiseListener<D, Result<Mapping, BufferAccessError>> for GPUBuffer<D> {
562 fn handle_response(
563 &self,
564 cx: &mut js::context::JSContext,
565 response: Result<Mapping, BufferAccessError>,
566 promise: &<D::Promise as PromiseHelpers<D>>::StackRoot,
567 ) {
568 match response {
569 Ok(mapping) => self.map_success(cx, promise, mapping),
570 Err(_) => self.map_failure(cx, promise),
571 }
572 }
573}