script/dom/webgpu/
gpuqueue.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::rc::Rc;
6
7use dom_struct::dom_struct;
8use ipc_channel::ipc::IpcSharedMemory;
9use webgpu_traits::{WebGPU, WebGPUQueue, WebGPURequest};
10
11use crate::conversions::{Convert, TryConvert};
12use crate::dom::bindings::cell::DomRefCell;
13use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
14    GPUExtent3D, GPUImageCopyTexture, GPUImageDataLayout, GPUQueueMethods, GPUSize64,
15};
16use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer as BufferSource;
17use crate::dom::bindings::error::{Error, Fallible};
18use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
19use crate::dom::bindings::root::{Dom, DomRoot};
20use crate::dom::bindings::str::USVString;
21use crate::dom::globalscope::GlobalScope;
22use crate::dom::promise::Promise;
23use crate::dom::webgpu::gpubuffer::GPUBuffer;
24use crate::dom::webgpu::gpucommandbuffer::GPUCommandBuffer;
25use crate::dom::webgpu::gpudevice::GPUDevice;
26use crate::routed_promise::{RoutedPromiseListener, route_promise};
27use crate::script_runtime::CanGc;
28
29#[dom_struct]
30pub(crate) struct GPUQueue {
31    reflector_: Reflector,
32    #[ignore_malloc_size_of = "defined in webgpu"]
33    #[no_trace]
34    channel: WebGPU,
35    device: DomRefCell<Option<Dom<GPUDevice>>>,
36    label: DomRefCell<USVString>,
37    #[no_trace]
38    queue: WebGPUQueue,
39}
40
41impl GPUQueue {
42    fn new_inherited(channel: WebGPU, queue: WebGPUQueue) -> Self {
43        GPUQueue {
44            channel,
45            reflector_: Reflector::new(),
46            device: DomRefCell::new(None),
47            label: DomRefCell::new(USVString::default()),
48            queue,
49        }
50    }
51
52    pub(crate) fn new(
53        global: &GlobalScope,
54        channel: WebGPU,
55        queue: WebGPUQueue,
56        can_gc: CanGc,
57    ) -> DomRoot<Self> {
58        reflect_dom_object(
59            Box::new(GPUQueue::new_inherited(channel, queue)),
60            global,
61            can_gc,
62        )
63    }
64}
65
66impl GPUQueue {
67    pub(crate) fn set_device(&self, device: &GPUDevice) {
68        *self.device.borrow_mut() = Some(Dom::from_ref(device));
69    }
70
71    pub(crate) fn id(&self) -> WebGPUQueue {
72        self.queue
73    }
74}
75
76impl GPUQueueMethods<crate::DomTypeHolder> for GPUQueue {
77    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
78    fn Label(&self) -> USVString {
79        self.label.borrow().clone()
80    }
81
82    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
83    fn SetLabel(&self, value: USVString) {
84        *self.label.borrow_mut() = value;
85    }
86
87    /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-submit>
88    fn Submit(&self, command_buffers: Vec<DomRoot<GPUCommandBuffer>>) {
89        let command_buffers = command_buffers.iter().map(|cb| cb.id().0).collect();
90        self.channel
91            .0
92            .send(WebGPURequest::Submit {
93                device_id: self.device.borrow().as_ref().unwrap().id().0,
94                queue_id: self.queue.0,
95                command_buffers,
96            })
97            .unwrap();
98    }
99
100    /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writebuffer>
101    fn WriteBuffer(
102        &self,
103        buffer: &GPUBuffer,
104        buffer_offset: GPUSize64,
105        data: BufferSource,
106        data_offset: GPUSize64,
107        size: Option<GPUSize64>,
108    ) -> Fallible<()> {
109        // Step 1
110        let sizeof_element: usize = match data {
111            BufferSource::ArrayBufferView(ref d) => d.get_array_type().byte_size().unwrap_or(1),
112            BufferSource::ArrayBuffer(_) => 1,
113        };
114        let data = match data {
115            BufferSource::ArrayBufferView(d) => d.to_vec(),
116            BufferSource::ArrayBuffer(d) => d.to_vec(),
117        };
118        // Step 2
119        let data_size: usize = data.len() / sizeof_element;
120        debug_assert_eq!(data.len() % sizeof_element, 0);
121        // Step 3
122        let content_size = if let Some(s) = size {
123            s
124        } else {
125            (data_size as GPUSize64)
126                .checked_sub(data_offset)
127                .ok_or(Error::Operation)?
128        };
129
130        // Step 4
131        let valid = data_offset + content_size <= data_size as u64 &&
132            content_size * sizeof_element as u64 % wgpu_types::COPY_BUFFER_ALIGNMENT == 0;
133        if !valid {
134            return Err(Error::Operation);
135        }
136
137        // Step 5&6
138        let contents = IpcSharedMemory::from_bytes(
139            &data[(data_offset as usize) * sizeof_element..
140                ((data_offset + content_size) as usize) * sizeof_element],
141        );
142        if let Err(e) = self.channel.0.send(WebGPURequest::WriteBuffer {
143            device_id: self.device.borrow().as_ref().unwrap().id().0,
144            queue_id: self.queue.0,
145            buffer_id: buffer.id().0,
146            buffer_offset,
147            data: contents,
148        }) {
149            warn!("Failed to send WriteBuffer({:?}) ({})", buffer.id(), e);
150            return Err(Error::Operation);
151        }
152
153        Ok(())
154    }
155
156    /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writetexture>
157    fn WriteTexture(
158        &self,
159        destination: &GPUImageCopyTexture,
160        data: BufferSource,
161        data_layout: &GPUImageDataLayout,
162        size: GPUExtent3D,
163    ) -> Fallible<()> {
164        let (bytes, len) = match data {
165            BufferSource::ArrayBufferView(d) => (d.to_vec(), d.len() as u64),
166            BufferSource::ArrayBuffer(d) => (d.to_vec(), d.len() as u64),
167        };
168        let valid = data_layout.offset <= len;
169
170        if !valid {
171            return Err(Error::Operation);
172        }
173
174        let texture_cv = destination.try_convert()?;
175        let texture_layout = data_layout.convert();
176        let write_size = (&size).try_convert()?;
177        let final_data = IpcSharedMemory::from_bytes(&bytes);
178
179        if let Err(e) = self.channel.0.send(WebGPURequest::WriteTexture {
180            device_id: self.device.borrow().as_ref().unwrap().id().0,
181            queue_id: self.queue.0,
182            texture_cv,
183            data_layout: texture_layout,
184            size: write_size,
185            data: final_data,
186        }) {
187            warn!(
188                "Failed to send WriteTexture({:?}) ({})",
189                destination.texture.id().0,
190                e
191            );
192            return Err(Error::Operation);
193        }
194
195        Ok(())
196    }
197
198    /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-onsubmittedworkdone>
199    fn OnSubmittedWorkDone(&self, can_gc: CanGc) -> Rc<Promise> {
200        let global = self.global();
201        let promise = Promise::new(&global, can_gc);
202        let task_source = global.task_manager().dom_manipulation_task_source();
203        let sender = route_promise(&promise, self, task_source);
204
205        if let Err(e) = self
206            .channel
207            .0
208            .send(WebGPURequest::QueueOnSubmittedWorkDone {
209                sender,
210                queue_id: self.queue.0,
211            })
212        {
213            warn!("QueueOnSubmittedWorkDone failed with {e}")
214        }
215        promise
216    }
217}
218
219impl RoutedPromiseListener<()> for GPUQueue {
220    fn handle_response(&self, _response: (), promise: &Rc<Promise>, can_gc: CanGc) {
221        promise.resolve_native(&(), can_gc);
222    }
223}