1use std::ops::Range;
6use std::rc::Rc;
7use std::string::String;
8
9use dom_struct::dom_struct;
10use js::context::{JSContext, NoGC};
11use js::realm::CurrentRealm;
12use js::typedarray::HeapArrayBuffer;
13use script_bindings::cell::DomRefCell;
14use script_bindings::codegen::GenericBindings::WebGPUBinding::GPUMapModeConstants;
15use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
16use script_bindings::trace::RootedTraceableBox;
17use servo_base::generic_channel::GenericSharedMemory;
18use webgpu_traits::{Mapping, WebGPU, WebGPUBuffer, WebGPURequest};
19use wgpu_core::device::HostMap;
20use wgpu_core::resource::BufferAccessError;
21
22use crate::conversions::Convert;
23use crate::dom::bindings::buffer_source::DataBlock;
24use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
25 GPUBufferDescriptor, GPUBufferMapState, GPUBufferMethods, GPUFlagsConstant, GPUMapModeFlags,
26 GPUSize64,
27};
28use crate::dom::bindings::error::{Error, Fallible};
29use crate::dom::bindings::reflector::DomGlobal;
30use crate::dom::bindings::root::{Dom, DomRoot};
31use crate::dom::bindings::str::USVString;
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::promise::Promise;
34use crate::dom::webgpu::gpudevice::GPUDevice;
35use crate::routed_promise::{RoutedPromiseListener, callback_promise};
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,
44 mode: GPUMapModeFlags,
46 range: Range<u64>,
48}
49
50impl ActiveBufferMapping {
51 pub(crate) fn new(
53 mode: GPUMapModeFlags,
54 range: Range<u64>,
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: DataBlock::new_zeroed(size),
67 mode,
68 range,
69 }))
70 }
71}
72
73#[derive(JSTraceable, MallocSizeOf)]
74pub struct DroppableGPUBuffer {
75 #[no_trace]
76 channel: WebGPU,
77 #[no_trace]
78 buffer: WebGPUBuffer,
79}
80
81impl Drop for DroppableGPUBuffer {
82 fn drop(&mut self) {
83 if let Err(e) = self
84 .channel
85 .0
86 .send(WebGPURequest::DropBuffer(self.buffer.0))
87 {
88 error!(
89 "Failed to send WebGPURequest::DropBuffer({:?}) ({}) - Potential leak",
90 self.buffer.0, e
91 );
92 }
93 }
94}
95
96#[dom_struct]
97pub(crate) struct GPUBuffer {
98 reflector_: Reflector,
99 droppable: DroppableGPUBuffer,
100 label: DomRefCell<USVString>,
101 device: Dom<GPUDevice>,
102 size: GPUSize64,
104 usage: GPUFlagsConstant,
106 #[conditional_malloc_size_of]
108 pending_map: DomRefCell<Option<Rc<Promise>>>,
109 mapping: DomRefCell<Option<ActiveBufferMapping>>,
111}
112
113impl GPUBuffer {
114 fn new_inherited(
115 channel: WebGPU,
116 buffer: WebGPUBuffer,
117 device: &GPUDevice,
118 size: GPUSize64,
119 usage: GPUFlagsConstant,
120 mapping: Option<RootedTraceableBox<ActiveBufferMapping>>,
121 label: USVString,
122 ) -> Self {
123 Self {
124 reflector_: Reflector::new(),
125 droppable: DroppableGPUBuffer { channel, buffer },
126 label: DomRefCell::new(label),
127 device: Dom::from_ref(device),
128 pending_map: DomRefCell::new(None),
129 size,
130 usage,
131 mapping: DomRefCell::new(mapping.map(|mapping| *mapping.into_box())),
132 }
133 }
134
135 #[allow(clippy::too_many_arguments)]
136 pub(crate) fn new(
137 cx: &mut js::context::JSContext,
138 global: &GlobalScope,
139 channel: WebGPU,
140 buffer: WebGPUBuffer,
141 device: &GPUDevice,
142 size: GPUSize64,
143 usage: GPUFlagsConstant,
144 mapping: Option<RootedTraceableBox<ActiveBufferMapping>>,
145 label: USVString,
146 ) -> DomRoot<Self> {
147 reflect_dom_object_with_cx(
148 Box::new(GPUBuffer::new_inherited(
149 channel, buffer, device, size, usage, mapping, label,
150 )),
151 global,
152 cx,
153 )
154 }
155}
156
157impl GPUBuffer {
158 pub(crate) fn id(&self) -> WebGPUBuffer {
159 self.droppable.buffer
160 }
161
162 pub(crate) fn create(
164 cx: &mut js::context::JSContext,
165 device: &GPUDevice,
166 descriptor: &GPUBufferDescriptor,
167 ) -> Fallible<DomRoot<GPUBuffer>> {
168 let desc = wgpu_types::BufferDescriptor {
169 label: (&descriptor.parent).convert(),
170 size: descriptor.size as wgpu_types::BufferAddress,
171 usage: wgpu_types::BufferUsages::from_bits_retain(descriptor.usage),
172 mapped_at_creation: descriptor.mappedAtCreation,
173 };
174 let id = device.global().wgpu_id_hub().create_buffer_id();
175
176 device
177 .channel()
178 .0
179 .send(WebGPURequest::CreateBuffer {
180 device_id: device.id().0,
181 buffer_id: id,
182 descriptor: desc,
183 })
184 .expect("Failed to create WebGPU buffer");
185
186 let buffer = WebGPUBuffer(id);
187 let mapping = if descriptor.mappedAtCreation {
188 Some(ActiveBufferMapping::new(
189 GPUMapModeConstants::WRITE,
190 0..descriptor.size,
191 )?)
192 } else {
193 None
194 };
195
196 Ok(GPUBuffer::new(
197 cx,
198 &device.global(),
199 device.channel(),
200 buffer,
201 device,
202 descriptor.size,
203 descriptor.usage,
204 mapping,
205 descriptor.parent.label.clone(),
206 ))
207 }
208}
209
210impl GPUBufferMethods<crate::DomTypeHolder> for GPUBuffer {
211 fn Unmap(&self, cx: &mut js::context::JSContext) {
213 let promise = self.pending_map.safe_borrow_mut(cx).take();
215 if let Some(promise) = promise {
216 promise.reject_error(cx, Error::Abort(None));
217 }
218 let mut mapping = RootedTraceableBox::new(self.mapping.safe_borrow_mut(cx).take());
220 let mapping = if let Some(mapping) = mapping.as_mut() {
221 mapping
222 } else {
223 return;
224 };
225
226 mapping.data.clear_views(cx);
228 if let Err(e) = self.droppable.channel.0.send(WebGPURequest::UnmapBuffer {
230 buffer_id: self.id().0,
231 mapping: if mapping.mode >= GPUMapModeConstants::WRITE {
232 Some(Mapping {
233 data: GenericSharedMemory::from_bytes(mapping.data.data()),
234 range: mapping.range.clone(),
235 mode: HostMap::Write,
236 })
237 } else {
238 None
239 },
240 }) {
241 warn!(
242 "Failed to send Buffer unmap ({:?}) ({})",
243 self.droppable.buffer.0, e
244 );
245 }
246 }
247
248 fn Destroy(&self, cx: &mut JSContext) {
250 self.Unmap(cx);
252 if let Err(e) = self
254 .droppable
255 .channel
256 .0
257 .send(WebGPURequest::DestroyBuffer(self.droppable.buffer.0))
258 {
259 warn!(
260 "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})",
261 self.droppable.buffer.0, e
262 );
263 };
264 }
265
266 fn MapAsync(
268 &self,
269 cx: &mut CurrentRealm<'_>,
270 mode: u32,
271 offset: GPUSize64,
272 size: Option<GPUSize64>,
273 ) -> Rc<Promise> {
274 let promise = Promise::new_in_realm(cx);
275 if self.pending_map.borrow().is_some() {
277 promise.reject_error(cx, Error::Operation(None));
278 return promise;
279 }
280 *self.pending_map.safe_borrow_mut(cx) = Some(promise.clone());
282 let host_map = match mode {
284 GPUMapModeConstants::READ => HostMap::Read,
285 GPUMapModeConstants::WRITE => HostMap::Write,
286 _ => {
287 self.device
288 .dispatch_error(webgpu_traits::Error::Validation(String::from(
289 "Invalid MapModeFlags",
290 )));
291 self.map_failure(cx, &promise);
292 return promise;
293 },
294 };
295
296 let callback = callback_promise(
297 &promise,
298 self,
299 self.global().task_manager().dom_manipulation_task_source(),
300 );
301 if let Err(e) = self
302 .droppable
303 .channel
304 .0
305 .send(WebGPURequest::BufferMapAsync {
306 callback,
307 buffer_id: self.droppable.buffer.0,
308 device_id: self.device.id().0,
309 host_map,
310 offset,
311 size,
312 })
313 {
314 warn!(
315 "Failed to send BufferMapAsync ({:?}) ({})",
316 self.droppable.buffer.0, e
317 );
318 self.map_failure(cx, &promise);
319 return promise;
320 }
321 promise
323 }
324
325 fn GetMappedRange(
327 &self,
328 cx: &mut js::context::JSContext,
329 offset: GPUSize64,
330 size: Option<GPUSize64>,
331 ) -> Fallible<RootedTraceableBox<HeapArrayBuffer>> {
332 let range_size = if let Some(s) = size {
333 s
334 } else {
335 self.size.saturating_sub(offset)
336 };
337 let mut mapping = self
339 .mapping
340 .safe_borrow_mut(cx)
341 .take()
342 .map(RootedTraceableBox::new)
343 .ok_or(Error::Operation(None))?;
344
345 let valid = offset.is_multiple_of(wgpu_types::MAP_ALIGNMENT) &&
346 range_size % wgpu_types::COPY_BUFFER_ALIGNMENT == 0 &&
347 offset >= mapping.range.start &&
348 offset + range_size <= mapping.range.end;
349 if !valid {
350 self.mapping
351 .safe_borrow_mut(cx)
352 .replace(*mapping.into_box());
353 return Err(Error::Operation(None));
354 }
355
356 let rebased_offset = (offset - mapping.range.start) as usize;
360 let result = mapping
361 .data
362 .view(cx, rebased_offset..rebased_offset + range_size as usize)
363 .map(|view| view.array_buffer())
364 .map_err(|()| Error::Operation(None));
365
366 self.mapping
367 .safe_borrow_mut(cx)
368 .replace(*mapping.into_box());
369 result
370 }
371
372 fn Label(&self) -> USVString {
374 self.label.borrow().clone()
375 }
376
377 fn SetLabel(&self, no_gc: &NoGC, value: USVString) {
379 *self.label.safe_borrow_mut(no_gc) = value;
380 }
381
382 fn Size(&self) -> GPUSize64 {
384 self.size
385 }
386
387 fn Usage(&self) -> GPUFlagsConstant {
389 self.usage
390 }
391
392 fn MapState(&self) -> GPUBufferMapState {
394 if self.mapping.borrow().is_some() {
396 GPUBufferMapState::Mapped
397 } else if self.pending_map.borrow().is_some() {
398 GPUBufferMapState::Pending
399 } else {
400 GPUBufferMapState::Unmapped
401 }
402 }
403}
404
405impl GPUBuffer {
406 fn map_failure(&self, cx: &mut JSContext, p: &Rc<Promise>) {
407 if self.pending_map.borrow().as_ref() != Some(p) {
409 assert!(p.is_rejected());
410 return;
411 }
412 assert!(p.is_pending());
414 self.pending_map.safe_borrow_mut(cx).take();
416 let is_lost = self.device.is_lost();
418 if is_lost {
419 p.reject_error(cx, Error::Abort(None));
420 } else {
421 p.reject_error(cx, Error::Operation(None));
422 }
423 }
424
425 fn map_success(&self, cx: &mut js::context::JSContext, p: &Rc<Promise>, wgpu_mapping: Mapping) {
426 if self.pending_map.borrow().as_ref() != Some(p) {
428 assert!(p.is_rejected());
429 return;
430 }
431
432 assert!(p.is_pending());
434
435 let mapping = ActiveBufferMapping::new(
437 match wgpu_mapping.mode {
438 HostMap::Read => GPUMapModeConstants::READ,
439 HostMap::Write => GPUMapModeConstants::WRITE,
440 },
441 wgpu_mapping.range,
442 );
443
444 match mapping {
445 Err(error) => {
446 *self.pending_map.safe_borrow_mut(cx) = None;
447 p.reject_error(cx, error);
448 },
449 Ok(mut mapping) => {
450 mapping.data.load(&wgpu_mapping.data);
452 self.mapping
454 .safe_borrow_mut(cx)
455 .replace(*mapping.into_box());
456 self.pending_map.safe_borrow_mut(cx).take();
458 p.resolve_native(cx, &());
459 },
460 }
461 }
462}
463
464impl RoutedPromiseListener<Result<Mapping, BufferAccessError>> for GPUBuffer {
465 fn handle_response(
466 &self,
467 cx: &mut js::context::JSContext,
468 response: Result<Mapping, BufferAccessError>,
469 promise: &Rc<Promise>,
470 ) {
471 match response {
472 Ok(mapping) => self.map_success(cx, promise, mapping),
473 Err(_) => self.map_failure(cx, promise),
474 }
475 }
476}