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 dom_struct::dom_struct;
10use ipc_channel::ipc::IpcSharedMemory;
11use js::typedarray::ArrayBuffer;
12use webgpu_traits::{Mapping, WebGPU, WebGPUBuffer, WebGPURequest};
13use wgpu_core::device::HostMap;
14use wgpu_core::resource::BufferAccessError;
15
16use crate::conversions::Convert;
17use crate::dom::bindings::buffer_source::DataBlock;
18use crate::dom::bindings::cell::DomRefCell;
19use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
20    GPUBufferDescriptor, GPUBufferMapState, GPUBufferMethods, GPUFlagsConstant,
21    GPUMapModeConstants, GPUMapModeFlags, GPUSize64,
22};
23use crate::dom::bindings::error::{Error, Fallible};
24use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
25use crate::dom::bindings::root::{Dom, DomRoot};
26use crate::dom::bindings::str::USVString;
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::promise::Promise;
29use crate::dom::webgpu::gpudevice::GPUDevice;
30use crate::realms::InRealm;
31use crate::routed_promise::{RoutedPromiseListener, route_promise};
32use crate::script_runtime::{CanGc, JSContext};
33
34#[derive(JSTraceable, MallocSizeOf)]
35pub(crate) struct ActiveBufferMapping {
36    // TODO(sagudev): Use IpcSharedMemory when https://github.com/servo/ipc-channel/pull/356 lands
37    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-data>
38    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-views>
39    pub(crate) data: DataBlock,
40    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-mode>
41    mode: GPUMapModeFlags,
42    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-range>
43    range: Range<u64>,
44}
45
46impl ActiveBufferMapping {
47    /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-initialize-an-active-buffer-mapping>
48    pub(crate) fn new(mode: GPUMapModeFlags, range: Range<u64>) -> Fallible<Self> {
49        // Step 1
50        let size = range.end - range.start;
51        // Step 2
52        if size > (1 << 53) - 1 {
53            return Err(Error::Range("Over MAX_SAFE_INTEGER".to_string()));
54        }
55        let size: usize = size
56            .try_into()
57            .map_err(|_| Error::Range("Over usize".to_string()))?;
58        Ok(Self {
59            data: DataBlock::new_zeroed(size),
60            mode,
61            range,
62        })
63    }
64}
65
66#[dom_struct]
67pub(crate) struct GPUBuffer {
68    reflector_: Reflector,
69    #[ignore_malloc_size_of = "defined in webgpu"]
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        if let Some(promise) = self.pending_map.borrow_mut().take() {
196            promise.reject_error(Error::Abort, CanGc::note());
197        }
198        // Step 2
199        let mut mapping = self.mapping.borrow_mut().take();
200        let mapping = if let Some(mapping) = mapping.as_mut() {
201            mapping
202        } else {
203            return;
204        };
205
206        // Step 3
207        mapping.data.clear_views();
208        // Step 5&7
209        if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer {
210            buffer_id: self.id().0,
211            mapping: if mapping.mode >= GPUMapModeConstants::WRITE {
212                Some(Mapping {
213                    data: IpcSharedMemory::from_bytes(mapping.data.data()),
214                    range: mapping.range.clone(),
215                    mode: HostMap::Write,
216                })
217            } else {
218                None
219            },
220        }) {
221            warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e);
222        }
223    }
224
225    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy>
226    fn Destroy(&self) {
227        // Step 1
228        self.Unmap();
229        // Step 2
230        if let Err(e) = self
231            .channel
232            .0
233            .send(WebGPURequest::DestroyBuffer(self.buffer.0))
234        {
235            warn!(
236                "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})",
237                self.buffer.0, e
238            );
239        };
240    }
241
242    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapasync>
243    fn MapAsync(
244        &self,
245        mode: u32,
246        offset: GPUSize64,
247        size: Option<GPUSize64>,
248        comp: InRealm,
249        can_gc: CanGc,
250    ) -> Rc<Promise> {
251        let promise = Promise::new_in_current_realm(comp, can_gc);
252        // Step 2
253        if self.pending_map.borrow().is_some() {
254            promise.reject_error(Error::Operation, can_gc);
255            return promise;
256        }
257        // Step 4
258        *self.pending_map.borrow_mut() = Some(promise.clone());
259        // Step 5
260        let host_map = match mode {
261            GPUMapModeConstants::READ => HostMap::Read,
262            GPUMapModeConstants::WRITE => HostMap::Write,
263            _ => {
264                self.device
265                    .dispatch_error(webgpu_traits::Error::Validation(String::from(
266                        "Invalid MapModeFlags",
267                    )));
268                self.map_failure(&promise, can_gc);
269                return promise;
270            },
271        };
272
273        let sender = route_promise(
274            &promise,
275            self,
276            self.global().task_manager().dom_manipulation_task_source(),
277        );
278        if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync {
279            sender,
280            buffer_id: self.buffer.0,
281            device_id: self.device.id().0,
282            host_map,
283            offset,
284            size,
285        }) {
286            warn!(
287                "Failed to send BufferMapAsync ({:?}) ({})",
288                self.buffer.0, e
289            );
290            self.map_failure(&promise, can_gc);
291            return promise;
292        }
293        // Step 6
294        promise
295    }
296
297    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange>
298    fn GetMappedRange(
299        &self,
300        _cx: JSContext,
301        offset: GPUSize64,
302        size: Option<GPUSize64>,
303        can_gc: CanGc,
304    ) -> Fallible<ArrayBuffer> {
305        let range_size = if let Some(s) = size {
306            s
307        } else {
308            self.size.saturating_sub(offset)
309        };
310        // Step 2: validation
311        let mut mapping = self.mapping.borrow_mut();
312        let mapping = mapping.as_mut().ok_or(Error::Operation)?;
313
314        let valid = offset % wgpu_types::MAP_ALIGNMENT == 0 &&
315            range_size % wgpu_types::COPY_BUFFER_ALIGNMENT == 0 &&
316            offset >= mapping.range.start &&
317            offset + range_size <= mapping.range.end;
318        if !valid {
319            return Err(Error::Operation);
320        }
321
322        // Step 4
323        // only mapping.range is mapped with mapping.range.start at 0
324        // so we need to rebase range to mapped.range
325        let rebased_offset = (offset - mapping.range.start) as usize;
326        mapping
327            .data
328            .view(rebased_offset..rebased_offset + range_size as usize, can_gc)
329            .map(|view| view.array_buffer())
330            .map_err(|()| Error::Operation)
331    }
332
333    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
334    fn Label(&self) -> USVString {
335        self.label.borrow().clone()
336    }
337
338    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
339    fn SetLabel(&self, value: USVString) {
340        *self.label.borrow_mut() = value;
341    }
342
343    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size>
344    fn Size(&self) -> GPUSize64 {
345        self.size
346    }
347
348    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-usage>
349    fn Usage(&self) -> GPUFlagsConstant {
350        self.usage
351    }
352
353    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapstate>
354    fn MapState(&self) -> GPUBufferMapState {
355        // Step 1&2&3
356        if self.mapping.borrow().is_some() {
357            GPUBufferMapState::Mapped
358        } else if self.pending_map.borrow().is_some() {
359            GPUBufferMapState::Pending
360        } else {
361            GPUBufferMapState::Unmapped
362        }
363    }
364}
365
366impl GPUBuffer {
367    fn map_failure(&self, p: &Rc<Promise>, can_gc: CanGc) {
368        let mut pending_map = self.pending_map.borrow_mut();
369        // Step 1
370        if pending_map.as_ref() != Some(p) {
371            assert!(p.is_rejected());
372            return;
373        }
374        // Step 2
375        assert!(p.is_pending());
376        // Step 3
377        pending_map.take();
378        // Step 4
379        if self.device.is_lost() {
380            p.reject_error(Error::Abort, can_gc);
381        } else {
382            p.reject_error(Error::Operation, can_gc);
383        }
384    }
385
386    fn map_success(&self, p: &Rc<Promise>, wgpu_mapping: Mapping, can_gc: CanGc) {
387        let mut pending_map = self.pending_map.borrow_mut();
388
389        // Step 1
390        if pending_map.as_ref() != Some(p) {
391            assert!(p.is_rejected());
392            return;
393        }
394
395        // Step 2
396        assert!(p.is_pending());
397
398        // Step 4
399        let mapping = ActiveBufferMapping::new(
400            match wgpu_mapping.mode {
401                HostMap::Read => GPUMapModeConstants::READ,
402                HostMap::Write => GPUMapModeConstants::WRITE,
403            },
404            wgpu_mapping.range,
405        );
406
407        match mapping {
408            Err(error) => {
409                *pending_map = None;
410                p.reject_error(error.clone(), can_gc);
411            },
412            Ok(mut mapping) => {
413                // Step 5
414                mapping.data.load(&wgpu_mapping.data);
415                // Step 6
416                self.mapping.borrow_mut().replace(mapping);
417                // Step 7
418                pending_map.take();
419                p.resolve_native(&(), can_gc);
420            },
421        }
422    }
423}
424
425impl RoutedPromiseListener<Result<Mapping, BufferAccessError>> for GPUBuffer {
426    fn handle_response(
427        &self,
428        response: Result<Mapping, BufferAccessError>,
429        promise: &Rc<Promise>,
430        can_gc: CanGc,
431    ) {
432        match response {
433            Ok(mapping) => self.map_success(promise, mapping, can_gc),
434            Err(_) => self.map_failure(promise, can_gc),
435        }
436    }
437}