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