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, GPUImageCopyTexture, GPUImageDataLayout, GPUQueueMethods, GPUSize64,
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    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(None))?
129        };
130
131        // Step 4
132        let valid = data_offset + content_size <= data_size as u64 &&
133            (content_size * sizeof_element as u64)
134                .is_multiple_of(wgpu_types::COPY_BUFFER_ALIGNMENT);
135        if !valid {
136            return Err(Error::Operation(None));
137        }
138
139        // Step 5&6
140        let contents = GenericSharedMemory::from_bytes(
141            &data[(data_offset as usize) * sizeof_element..
142                ((data_offset + content_size) as usize) * sizeof_element],
143        );
144        if let Err(e) = self.channel.0.send(WebGPURequest::WriteBuffer {
145            device_id: self.device.borrow().as_ref().unwrap().id().0,
146            queue_id: self.queue.0,
147            buffer_id: buffer.id().0,
148            buffer_offset,
149            data: contents,
150        }) {
151            warn!("Failed to send WriteBuffer({:?}) ({})", buffer.id(), e);
152            return Err(Error::Operation(None));
153        }
154
155        Ok(())
156    }
157
158    /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writetexture>
159    fn WriteTexture(
160        &self,
161        destination: &GPUImageCopyTexture,
162        data: BufferSource,
163        data_layout: &GPUImageDataLayout,
164        size: GPUExtent3D,
165    ) -> Fallible<()> {
166        let (bytes, len) = match data {
167            BufferSource::ArrayBufferView(d) => (d.to_vec(), d.len() as u64),
168            BufferSource::ArrayBuffer(d) => (d.to_vec(), d.len() as u64),
169        };
170        let valid = data_layout.offset <= len;
171
172        if !valid {
173            return Err(Error::Operation(None));
174        }
175
176        let texture_cv = destination.try_convert()?;
177        let texture_layout = data_layout.convert();
178        let write_size = (&size).try_convert()?;
179        let final_data = GenericSharedMemory::from_bytes(&bytes);
180
181        if let Err(e) = self.channel.0.send(WebGPURequest::WriteTexture {
182            device_id: self.device.borrow().as_ref().unwrap().id().0,
183            queue_id: self.queue.0,
184            texture_cv,
185            data_layout: texture_layout,
186            size: write_size,
187            data: final_data,
188        }) {
189            warn!(
190                "Failed to send WriteTexture({:?}) ({})",
191                destination.texture.id().0,
192                e
193            );
194            return Err(Error::Operation(None));
195        }
196
197        Ok(())
198    }
199
200    /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-onsubmittedworkdone>
201    fn OnSubmittedWorkDone(&self, can_gc: CanGc) -> Rc<Promise> {
202        let global = self.global();
203        let promise = Promise::new(&global, can_gc);
204        let task_source = global.task_manager().dom_manipulation_task_source();
205        let callback = callback_promise(&promise, self, task_source);
206
207        if let Err(e) = self
208            .channel
209            .0
210            .send(WebGPURequest::QueueOnSubmittedWorkDone {
211                sender: callback,
212                queue_id: self.queue.0,
213            })
214        {
215            warn!("QueueOnSubmittedWorkDone failed with {e}")
216        }
217        promise
218    }
219}
220
221impl RoutedPromiseListener<()> for GPUQueue {
222    fn handle_response(
223        &self,
224        cx: &mut js::context::JSContext,
225        _response: (),
226        promise: &Rc<Promise>,
227    ) {
228        promise.resolve_native(&(), CanGc::from_cx(cx));
229    }
230}