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