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    #[allow(unsafe_code)]
102    fn WriteBuffer(
103        &self,
104        buffer: &GPUBuffer,
105        buffer_offset: GPUSize64,
106        data: BufferSource,
107        data_offset: GPUSize64,
108        size: Option<GPUSize64>,
109    ) -> Fallible<()> {
110        // Step 1
111        let sizeof_element: usize = match data {
112            BufferSource::ArrayBufferView(ref d) => d.get_array_type().byte_size().unwrap_or(1),
113            BufferSource::ArrayBuffer(_) => 1,
114        };
115        let data = match data {
116            BufferSource::ArrayBufferView(d) => d.to_vec(),
117            BufferSource::ArrayBuffer(d) => d.to_vec(),
118        };
119        // Step 2
120        let data_size: usize = data.len() / sizeof_element;
121        debug_assert_eq!(data.len() % sizeof_element, 0);
122        // Step 3
123        let content_size = if let Some(s) = size {
124            s
125        } else {
126            (data_size as GPUSize64)
127                .checked_sub(data_offset)
128                .ok_or(Error::Operation)?
129        };
130
131        // Step 4
132        let valid = data_offset + content_size <= data_size as u64 &&
133            content_size * sizeof_element as u64 % wgpu_types::COPY_BUFFER_ALIGNMENT == 0;
134        if !valid {
135            return Err(Error::Operation);
136        }
137
138        // Step 5&6
139        let contents = IpcSharedMemory::from_bytes(
140            &data[(data_offset as usize) * sizeof_element..
141                ((data_offset + content_size) as usize) * sizeof_element],
142        );
143        if let Err(e) = self.channel.0.send(WebGPURequest::WriteBuffer {
144            device_id: self.device.borrow().as_ref().unwrap().id().0,
145            queue_id: self.queue.0,
146            buffer_id: buffer.id().0,
147            buffer_offset,
148            data: contents,
149        }) {
150            warn!("Failed to send WriteBuffer({:?}) ({})", buffer.id(), e);
151            return Err(Error::Operation);
152        }
153
154        Ok(())
155    }
156
157    /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writetexture>
158    fn WriteTexture(
159        &self,
160        destination: &GPUImageCopyTexture,
161        data: BufferSource,
162        data_layout: &GPUImageDataLayout,
163        size: GPUExtent3D,
164    ) -> Fallible<()> {
165        let (bytes, len) = match data {
166            BufferSource::ArrayBufferView(d) => (d.to_vec(), d.len() as u64),
167            BufferSource::ArrayBuffer(d) => (d.to_vec(), d.len() as u64),
168        };
169        let valid = data_layout.offset <= len;
170
171        if !valid {
172            return Err(Error::Operation);
173        }
174
175        let texture_cv = destination.try_convert()?;
176        let texture_layout = data_layout.convert();
177        let write_size = (&size).try_convert()?;
178        let final_data = IpcSharedMemory::from_bytes(&bytes);
179
180        if let Err(e) = self.channel.0.send(WebGPURequest::WriteTexture {
181            device_id: self.device.borrow().as_ref().unwrap().id().0,
182            queue_id: self.queue.0,
183            texture_cv,
184            data_layout: texture_layout,
185            size: write_size,
186            data: final_data,
187        }) {
188            warn!(
189                "Failed to send WriteTexture({:?}) ({})",
190                destination.texture.id().0,
191                e
192            );
193            return Err(Error::Operation);
194        }
195
196        Ok(())
197    }
198
199    /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-onsubmittedworkdone>
200    fn OnSubmittedWorkDone(&self, can_gc: CanGc) -> Rc<Promise> {
201        let global = self.global();
202        let promise = Promise::new(&global, can_gc);
203        let task_source = global.task_manager().dom_manipulation_task_source();
204        let sender = route_promise(&promise, self, task_source);
205
206        if let Err(e) = self
207            .channel
208            .0
209            .send(WebGPURequest::QueueOnSubmittedWorkDone {
210                sender,
211                queue_id: self.queue.0,
212            })
213        {
214            warn!("QueueOnSubmittedWorkDone failed with {e}")
215        }
216        promise
217    }
218}
219
220impl RoutedPromiseListener<()> for GPUQueue {
221    fn handle_response(&self, _response: (), promise: &Rc<Promise>, can_gc: CanGc) {
222        promise.resolve_native(&(), can_gc);
223    }
224}