script/dom/webgpu/
gpubuffer.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::ops::Range;
6use std::rc::Rc;
7use std::string::String;
8
9use base::generic_channel::GenericSharedMemory;
10use dom_struct::dom_struct;
11use js::typedarray::HeapArrayBuffer;
12use script_bindings::trace::RootedTraceableBox;
13use webgpu_traits::{Mapping, WebGPU, WebGPUBuffer, WebGPURequest};
14use wgpu_core::device::HostMap;
15use wgpu_core::resource::BufferAccessError;
16
17use crate::conversions::Convert;
18use crate::dom::bindings::buffer_source::DataBlock;
19use crate::dom::bindings::cell::DomRefCell;
20use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
21    GPUBufferDescriptor, GPUBufferMapState, GPUBufferMethods, GPUFlagsConstant,
22    GPUMapModeConstants, GPUMapModeFlags, GPUSize64,
23};
24use crate::dom::bindings::error::{Error, Fallible};
25use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
26use crate::dom::bindings::root::{Dom, DomRoot};
27use crate::dom::bindings::str::USVString;
28use crate::dom::globalscope::GlobalScope;
29use crate::dom::promise::Promise;
30use crate::dom::webgpu::gpudevice::GPUDevice;
31use crate::realms::InRealm;
32use crate::routed_promise::{RoutedPromiseListener, callback_promise};
33use crate::script_runtime::{CanGc, JSContext};
34
35#[derive(JSTraceable, MallocSizeOf)]
36pub(crate) struct ActiveBufferMapping {
37    // TODO(sagudev): Use GenericSharedMemory when https://github.com/servo/ipc-channel/pull/356 lands
38    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-data>
39    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-views>
40    pub(crate) data: DataBlock,
41    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-mode>
42    mode: GPUMapModeFlags,
43    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-range>
44    range: Range<u64>,
45}
46
47impl ActiveBufferMapping {
48    /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-initialize-an-active-buffer-mapping>
49    pub(crate) fn new(mode: GPUMapModeFlags, range: Range<u64>) -> Fallible<Self> {
50        // Step 1
51        let size = range.end - range.start;
52        // Step 2
53        if size > (1 << 53) - 1 {
54            return Err(Error::Range(c"Over MAX_SAFE_INTEGER".to_owned()));
55        }
56        let size: usize = size
57            .try_into()
58            .map_err(|_| Error::Range(c"Over usize".to_owned()))?;
59        Ok(Self {
60            data: DataBlock::new_zeroed(size),
61            mode,
62            range,
63        })
64    }
65}
66
67#[dom_struct]
68pub(crate) struct GPUBuffer {
69    reflector_: Reflector,
70    #[no_trace]
71    channel: WebGPU,
72    label: DomRefCell<USVString>,
73    #[no_trace]
74    buffer: WebGPUBuffer,
75    device: Dom<GPUDevice>,
76    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size>
77    size: GPUSize64,
78    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-usage>
79    usage: GPUFlagsConstant,
80    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-pending_map-slot>
81    #[conditional_malloc_size_of]
82    pending_map: DomRefCell<Option<Rc<Promise>>>,
83    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapping-slot>
84    mapping: DomRefCell<Option<ActiveBufferMapping>>,
85}
86
87impl GPUBuffer {
88    fn new_inherited(
89        channel: WebGPU,
90        buffer: WebGPUBuffer,
91        device: &GPUDevice,
92        size: GPUSize64,
93        usage: GPUFlagsConstant,
94        mapping: Option<ActiveBufferMapping>,
95        label: USVString,
96    ) -> Self {
97        Self {
98            reflector_: Reflector::new(),
99            channel,
100            label: DomRefCell::new(label),
101            device: Dom::from_ref(device),
102            buffer,
103            pending_map: DomRefCell::new(None),
104            size,
105            usage,
106            mapping: DomRefCell::new(mapping),
107        }
108    }
109
110    #[allow(clippy::too_many_arguments)]
111    pub(crate) fn new(
112        global: &GlobalScope,
113        channel: WebGPU,
114        buffer: WebGPUBuffer,
115        device: &GPUDevice,
116        size: GPUSize64,
117        usage: GPUFlagsConstant,
118        mapping: Option<ActiveBufferMapping>,
119        label: USVString,
120        can_gc: CanGc,
121    ) -> DomRoot<Self> {
122        reflect_dom_object(
123            Box::new(GPUBuffer::new_inherited(
124                channel, buffer, device, size, usage, mapping, label,
125            )),
126            global,
127            can_gc,
128        )
129    }
130}
131
132impl GPUBuffer {
133    pub(crate) fn id(&self) -> WebGPUBuffer {
134        self.buffer
135    }
136
137    /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbuffer>
138    pub(crate) fn create(
139        device: &GPUDevice,
140        descriptor: &GPUBufferDescriptor,
141        can_gc: CanGc,
142    ) -> Fallible<DomRoot<GPUBuffer>> {
143        let desc = wgpu_types::BufferDescriptor {
144            label: (&descriptor.parent).convert(),
145            size: descriptor.size as wgpu_types::BufferAddress,
146            usage: wgpu_types::BufferUsages::from_bits_retain(descriptor.usage),
147            mapped_at_creation: descriptor.mappedAtCreation,
148        };
149        let id = device.global().wgpu_id_hub().create_buffer_id();
150
151        device
152            .channel()
153            .0
154            .send(WebGPURequest::CreateBuffer {
155                device_id: device.id().0,
156                buffer_id: id,
157                descriptor: desc,
158            })
159            .expect("Failed to create WebGPU buffer");
160
161        let buffer = WebGPUBuffer(id);
162        let mapping = if descriptor.mappedAtCreation {
163            Some(ActiveBufferMapping::new(
164                GPUMapModeConstants::WRITE,
165                0..descriptor.size,
166            )?)
167        } else {
168            None
169        };
170
171        Ok(GPUBuffer::new(
172            &device.global(),
173            device.channel().clone(),
174            buffer,
175            device,
176            descriptor.size,
177            descriptor.usage,
178            mapping,
179            descriptor.parent.label.clone(),
180            can_gc,
181        ))
182    }
183}
184
185impl Drop for GPUBuffer {
186    fn drop(&mut self) {
187        self.Destroy();
188    }
189}
190
191impl GPUBufferMethods<crate::DomTypeHolder> for GPUBuffer {
192    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap>
193    fn Unmap(&self) {
194        // Step 1
195        let promise = self.pending_map.borrow_mut().take();
196        if let Some(promise) = promise {
197            promise.reject_error(Error::Abort(None), CanGc::note());
198            *self.pending_map.borrow_mut() = Some(promise);
199        }
200        // Step 2
201        let mut mapping = self.mapping.borrow_mut().take();
202        let mapping = if let Some(mapping) = mapping.as_mut() {
203            mapping
204        } else {
205            return;
206        };
207
208        // Step 3
209        mapping.data.clear_views();
210        // Step 5&7
211        if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer {
212            buffer_id: self.id().0,
213            mapping: if mapping.mode >= GPUMapModeConstants::WRITE {
214                Some(Mapping {
215                    data: GenericSharedMemory::from_bytes(mapping.data.data()),
216                    range: mapping.range.clone(),
217                    mode: HostMap::Write,
218                })
219            } else {
220                None
221            },
222        }) {
223            warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e);
224        }
225    }
226
227    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy>
228    fn Destroy(&self) {
229        // Step 1
230        self.Unmap();
231        // Step 2
232        if let Err(e) = self
233            .channel
234            .0
235            .send(WebGPURequest::DestroyBuffer(self.buffer.0))
236        {
237            warn!(
238                "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})",
239                self.buffer.0, e
240            );
241        };
242    }
243
244    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapasync>
245    fn MapAsync(
246        &self,
247        mode: u32,
248        offset: GPUSize64,
249        size: Option<GPUSize64>,
250        comp: InRealm,
251        can_gc: CanGc,
252    ) -> Rc<Promise> {
253        let promise = Promise::new_in_current_realm(comp, can_gc);
254        // Step 2
255        if self.pending_map.borrow().is_some() {
256            promise.reject_error(Error::Operation(None), can_gc);
257            return promise;
258        }
259        // Step 4
260        *self.pending_map.borrow_mut() = Some(promise.clone());
261        // Step 5
262        let host_map = match mode {
263            GPUMapModeConstants::READ => HostMap::Read,
264            GPUMapModeConstants::WRITE => HostMap::Write,
265            _ => {
266                self.device
267                    .dispatch_error(webgpu_traits::Error::Validation(String::from(
268                        "Invalid MapModeFlags",
269                    )));
270                self.map_failure(&promise, can_gc);
271                return promise;
272            },
273        };
274
275        let callback = callback_promise(
276            &promise,
277            self,
278            self.global().task_manager().dom_manipulation_task_source(),
279        );
280        if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync {
281            callback,
282            buffer_id: self.buffer.0,
283            device_id: self.device.id().0,
284            host_map,
285            offset,
286            size,
287        }) {
288            warn!(
289                "Failed to send BufferMapAsync ({:?}) ({})",
290                self.buffer.0, e
291            );
292            self.map_failure(&promise, can_gc);
293            return promise;
294        }
295        // Step 6
296        promise
297    }
298
299    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange>
300    fn GetMappedRange(
301        &self,
302        _cx: JSContext,
303        offset: GPUSize64,
304        size: Option<GPUSize64>,
305        can_gc: CanGc,
306    ) -> Fallible<RootedTraceableBox<HeapArrayBuffer>> {
307        let range_size = if let Some(s) = size {
308            s
309        } else {
310            self.size.saturating_sub(offset)
311        };
312        // Step 2: validation
313        let mut mapping = self
314            .mapping
315            .borrow_mut()
316            .take()
317            .ok_or(Error::Operation(None))?;
318
319        let valid = offset % wgpu_types::MAP_ALIGNMENT == 0 &&
320            range_size % wgpu_types::COPY_BUFFER_ALIGNMENT == 0 &&
321            offset >= mapping.range.start &&
322            offset + range_size <= mapping.range.end;
323        if !valid {
324            self.mapping.borrow_mut().replace(mapping);
325            return Err(Error::Operation(None));
326        }
327
328        // Step 4
329        // only mapping.range is mapped with mapping.range.start at 0
330        // so we need to rebase range to mapped.range
331        let rebased_offset = (offset - mapping.range.start) as usize;
332        let result = mapping
333            .data
334            .view(rebased_offset..rebased_offset + range_size as usize, can_gc)
335            .map(|view| view.array_buffer())
336            .map_err(|()| Error::Operation(None));
337
338        self.mapping.borrow_mut().replace(mapping);
339        result
340    }
341
342    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
343    fn Label(&self) -> USVString {
344        self.label.borrow().clone()
345    }
346
347    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
348    fn SetLabel(&self, value: USVString) {
349        *self.label.borrow_mut() = value;
350    }
351
352    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size>
353    fn Size(&self) -> GPUSize64 {
354        self.size
355    }
356
357    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-usage>
358    fn Usage(&self) -> GPUFlagsConstant {
359        self.usage
360    }
361
362    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapstate>
363    fn MapState(&self) -> GPUBufferMapState {
364        // Step 1&2&3
365        if self.mapping.borrow().is_some() {
366            GPUBufferMapState::Mapped
367        } else if self.pending_map.borrow().is_some() {
368            GPUBufferMapState::Pending
369        } else {
370            GPUBufferMapState::Unmapped
371        }
372    }
373}
374
375impl GPUBuffer {
376    fn map_failure(&self, p: &Rc<Promise>, can_gc: CanGc) {
377        // Step 1
378        if self.pending_map.borrow().as_ref() != Some(p) {
379            assert!(p.is_rejected());
380            return;
381        }
382        // Step 2
383        assert!(p.is_pending());
384        // Step 3
385        self.pending_map.borrow_mut().take();
386        // Step 4
387        let is_lost = self.device.is_lost();
388        if is_lost {
389            p.reject_error(Error::Abort(None), can_gc);
390        } else {
391            p.reject_error(Error::Operation(None), can_gc);
392        }
393    }
394
395    fn map_success(&self, p: &Rc<Promise>, wgpu_mapping: Mapping, can_gc: CanGc) {
396        // Step 1
397        if self.pending_map.borrow().as_ref() != Some(p) {
398            assert!(p.is_rejected());
399            return;
400        }
401
402        // Step 2
403        assert!(p.is_pending());
404
405        // Step 4
406        let mapping = ActiveBufferMapping::new(
407            match wgpu_mapping.mode {
408                HostMap::Read => GPUMapModeConstants::READ,
409                HostMap::Write => GPUMapModeConstants::WRITE,
410            },
411            wgpu_mapping.range,
412        );
413
414        match mapping {
415            Err(error) => {
416                *self.pending_map.borrow_mut() = None;
417                p.reject_error(error.clone(), can_gc);
418            },
419            Ok(mut mapping) => {
420                // Step 5
421                mapping.data.load(&wgpu_mapping.data);
422                // Step 6
423                self.mapping.borrow_mut().replace(mapping);
424                // Step 7
425                self.pending_map.borrow_mut().take();
426                p.resolve_native(&(), can_gc);
427            },
428        }
429    }
430}
431
432impl RoutedPromiseListener<Result<Mapping, BufferAccessError>> for GPUBuffer {
433    fn handle_response(
434        &self,
435        response: Result<Mapping, BufferAccessError>,
436        promise: &Rc<Promise>,
437        can_gc: CanGc,
438    ) {
439        match response {
440            Ok(mapping) => self.map_success(promise, mapping, can_gc),
441            Err(_) => self.map_failure(promise, can_gc),
442        }
443    }
444}