Skip to main content

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 js::typedarray::HeapArrayBuffer;
11use script_bindings::cell::DomRefCell;
12use script_bindings::reflector::{Reflector, reflect_dom_object};
13use script_bindings::trace::RootedTraceableBox;
14use servo_base::generic_channel::GenericSharedMemory;
15use webgpu_traits::{Mapping, WebGPU, WebGPUBuffer, WebGPURequest};
16use wgpu_core::device::HostMap;
17use wgpu_core::resource::BufferAccessError;
18
19use crate::conversions::Convert;
20use crate::dom::bindings::buffer_source::DataBlock;
21use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
22    GPUBufferDescriptor, GPUBufferMapState, GPUBufferMethods, GPUFlagsConstant,
23    GPUMapModeConstants, GPUMapModeFlags, GPUSize64,
24};
25use crate::dom::bindings::error::{Error, Fallible};
26use crate::dom::bindings::reflector::DomGlobal;
27use crate::dom::bindings::root::{Dom, DomRoot};
28use crate::dom::bindings::str::USVString;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::promise::Promise;
31use crate::dom::webgpu::gpudevice::GPUDevice;
32use crate::realms::InRealm;
33use crate::routed_promise::{RoutedPromiseListener, callback_promise};
34use crate::script_runtime::{CanGc, JSContext};
35
36#[derive(JSTraceable, MallocSizeOf)]
37pub(crate) struct ActiveBufferMapping {
38    // TODO(sagudev): Use GenericSharedMemory when https://github.com/servo/ipc-channel/pull/356 lands
39    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-data>
40    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-views>
41    pub(crate) data: DataBlock,
42    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-mode>
43    mode: GPUMapModeFlags,
44    /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-range>
45    range: Range<u64>,
46}
47
48impl ActiveBufferMapping {
49    /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-initialize-an-active-buffer-mapping>
50    pub(crate) fn new(mode: GPUMapModeFlags, range: Range<u64>) -> Fallible<Self> {
51        // Step 1
52        let size = range.end - range.start;
53        // Step 2
54        if size > (1 << 53) - 1 {
55            return Err(Error::Range(c"Over MAX_SAFE_INTEGER".to_owned()));
56        }
57        let size: usize = size
58            .try_into()
59            .map_err(|_| Error::Range(c"Over usize".to_owned()))?;
60        Ok(Self {
61            data: DataBlock::new_zeroed(size),
62            mode,
63            range,
64        })
65    }
66}
67
68#[dom_struct]
69pub(crate) struct GPUBuffer {
70    reflector_: Reflector,
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(),
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    }
190}
191
192impl GPUBufferMethods<crate::DomTypeHolder> for GPUBuffer {
193    /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap>
194    fn Unmap(&self) {
195        // Step 1
196        let promise = self.pending_map.borrow_mut().take();
197        if let Some(promise) = promise {
198            promise.reject_error(Error::Abort(None), CanGc::deprecated_note());
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.is_multiple_of(wgpu_types::MAP_ALIGNMENT) &&
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, 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        cx: &mut js::context::JSContext,
436        response: Result<Mapping, BufferAccessError>,
437        promise: &Rc<Promise>,
438    ) {
439        match response {
440            Ok(mapping) => self.map_success(promise, mapping, CanGc::from_cx(cx)),
441            Err(_) => self.map_failure(promise, CanGc::from_cx(cx)),
442        }
443    }
444}