Skip to main content

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