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    #[ignore_malloc_size_of = "promises are hard"]
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    #[allow(unsafe_code)]
193    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap>
194    fn Unmap(&self) {
195        // Step 1
196        if let Some(promise) = self.pending_map.borrow_mut().take() {
197            promise.reject_error(Error::Abort, CanGc::note());
198        }
199        // Step 2
200        let mut mapping = self.mapping.borrow_mut().take();
201        let mapping = if let Some(mapping) = mapping.as_mut() {
202            mapping
203        } else {
204            return;
205        };
206
207        // Step 3
208        mapping.data.clear_views();
209        // Step 5&7
210        if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer {
211            buffer_id: self.id().0,
212            mapping: if mapping.mode >= GPUMapModeConstants::WRITE {
213                Some(Mapping {
214                    data: IpcSharedMemory::from_bytes(mapping.data.data()),
215                    range: mapping.range.clone(),
216                    mode: HostMap::Write,
217                })
218            } else {
219                None
220            },
221        }) {
222            warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e);
223        }
224    }
225
226    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy>
227    fn Destroy(&self) {
228        // Step 1
229        self.Unmap();
230        // Step 2
231        if let Err(e) = self
232            .channel
233            .0
234            .send(WebGPURequest::DestroyBuffer(self.buffer.0))
235        {
236            warn!(
237                "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})",
238                self.buffer.0, e
239            );
240        };
241    }
242
243    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapasync>
244    fn MapAsync(
245        &self,
246        mode: u32,
247        offset: GPUSize64,
248        size: Option<GPUSize64>,
249        comp: InRealm,
250        can_gc: CanGc,
251    ) -> Rc<Promise> {
252        let promise = Promise::new_in_current_realm(comp, can_gc);
253        // Step 2
254        if self.pending_map.borrow().is_some() {
255            promise.reject_error(Error::Operation, can_gc);
256            return promise;
257        }
258        // Step 4
259        *self.pending_map.borrow_mut() = Some(promise.clone());
260        // Step 5
261        let host_map = match mode {
262            GPUMapModeConstants::READ => HostMap::Read,
263            GPUMapModeConstants::WRITE => HostMap::Write,
264            _ => {
265                self.device
266                    .dispatch_error(webgpu_traits::Error::Validation(String::from(
267                        "Invalid MapModeFlags",
268                    )));
269                self.map_failure(&promise, can_gc);
270                return promise;
271            },
272        };
273
274        let sender = route_promise(
275            &promise,
276            self,
277            self.global().task_manager().dom_manipulation_task_source(),
278        );
279        if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync {
280            sender,
281            buffer_id: self.buffer.0,
282            device_id: self.device.id().0,
283            host_map,
284            offset,
285            size,
286        }) {
287            warn!(
288                "Failed to send BufferMapAsync ({:?}) ({})",
289                self.buffer.0, e
290            );
291            self.map_failure(&promise, can_gc);
292            return promise;
293        }
294        // Step 6
295        promise
296    }
297
298    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange>
299    #[allow(unsafe_code)]
300    fn GetMappedRange(
301        &self,
302        _cx: JSContext,
303        offset: GPUSize64,
304        size: Option<GPUSize64>,
305        can_gc: CanGc,
306    ) -> Fallible<ArrayBuffer> {
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.mapping.borrow_mut();
314        let mapping = mapping.as_mut().ok_or(Error::Operation)?;
315
316        let valid = offset % wgpu_types::MAP_ALIGNMENT == 0 &&
317            range_size % wgpu_types::COPY_BUFFER_ALIGNMENT == 0 &&
318            offset >= mapping.range.start &&
319            offset + range_size <= mapping.range.end;
320        if !valid {
321            return Err(Error::Operation);
322        }
323
324        // Step 4
325        // only mapping.range is mapped with mapping.range.start at 0
326        // so we need to rebase range to mapped.range
327        let rebased_offset = (offset - mapping.range.start) as usize;
328        mapping
329            .data
330            .view(rebased_offset..rebased_offset + range_size as usize, can_gc)
331            .map(|view| view.array_buffer())
332            .map_err(|()| Error::Operation)
333    }
334
335    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
336    fn Label(&self) -> USVString {
337        self.label.borrow().clone()
338    }
339
340    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
341    fn SetLabel(&self, value: USVString) {
342        *self.label.borrow_mut() = value;
343    }
344
345    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size>
346    fn Size(&self) -> GPUSize64 {
347        self.size
348    }
349
350    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-usage>
351    fn Usage(&self) -> GPUFlagsConstant {
352        self.usage
353    }
354
355    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapstate>
356    fn MapState(&self) -> GPUBufferMapState {
357        // Step 1&2&3
358        if self.mapping.borrow().is_some() {
359            GPUBufferMapState::Mapped
360        } else if self.pending_map.borrow().is_some() {
361            GPUBufferMapState::Pending
362        } else {
363            GPUBufferMapState::Unmapped
364        }
365    }
366}
367
368impl GPUBuffer {
369    fn map_failure(&self, p: &Rc<Promise>, can_gc: CanGc) {
370        let mut pending_map = self.pending_map.borrow_mut();
371        // Step 1
372        if pending_map.as_ref() != Some(p) {
373            assert!(p.is_rejected());
374            return;
375        }
376        // Step 2
377        assert!(p.is_pending());
378        // Step 3
379        pending_map.take();
380        // Step 4
381        if self.device.is_lost() {
382            p.reject_error(Error::Abort, can_gc);
383        } else {
384            p.reject_error(Error::Operation, can_gc);
385        }
386    }
387
388    fn map_success(&self, p: &Rc<Promise>, wgpu_mapping: Mapping, can_gc: CanGc) {
389        let mut pending_map = self.pending_map.borrow_mut();
390
391        // Step 1
392        if pending_map.as_ref() != Some(p) {
393            assert!(p.is_rejected());
394            return;
395        }
396
397        // Step 2
398        assert!(p.is_pending());
399
400        // Step 4
401        let mapping = ActiveBufferMapping::new(
402            match wgpu_mapping.mode {
403                HostMap::Read => GPUMapModeConstants::READ,
404                HostMap::Write => GPUMapModeConstants::WRITE,
405            },
406            wgpu_mapping.range,
407        );
408
409        match mapping {
410            Err(error) => {
411                *pending_map = None;
412                p.reject_error(error.clone(), can_gc);
413            },
414            Ok(mut mapping) => {
415                // Step 5
416                mapping.data.load(&wgpu_mapping.data);
417                // Step 6
418                self.mapping.borrow_mut().replace(mapping);
419                // Step 7
420                pending_map.take();
421                p.resolve_native(&(), can_gc);
422            },
423        }
424    }
425}
426
427impl RoutedPromiseListener<Result<Mapping, BufferAccessError>> for GPUBuffer {
428    fn handle_response(
429        &self,
430        response: Result<Mapping, BufferAccessError>,
431        promise: &Rc<Promise>,
432        can_gc: CanGc,
433    ) {
434        match response {
435            Ok(mapping) => self.map_success(promise, mapping, can_gc),
436            Err(_) => self.map_failure(promise, can_gc),
437        }
438    }
439}