1use dom_struct::dom_struct;
6use js::context::{JSContext, NoGC};
7use pixels::{SnapshotAlphaMode, SnapshotPixelFormat};
8use script_bindings::cell::DomRefCell;
9use script_bindings::codegen::GenericBindings::CanvasRenderingContext2DBinding::ImageDataMethods;
10use script_bindings::codegen::GenericBindings::HTMLCanvasElementBinding::HTMLCanvasElementMethods;
11use script_bindings::codegen::GenericBindings::HTMLImageElementBinding::HTMLImageElementMethods;
12use script_bindings::codegen::GenericBindings::HTMLVideoElementBinding::HTMLVideoElementMethods;
13use script_bindings::codegen::GenericBindings::ImageBitmapBinding::ImageBitmapMethods;
14use script_bindings::codegen::GenericBindings::OffscreenCanvasBinding::OffscreenCanvasMethods;
15use script_bindings::reflector::{Reflector, reflect_dom_object};
16use script_webgpu::gpuconvert::{WebGPUConvert, WebGPUTryConvert};
17use servo_base::generic_channel::GenericSharedMemory;
18use webgpu_traits::{WebGPU, WebGPUQueue, WebGPURequest};
19
20use crate::dom::bindings::buffer_source::get_buffer_source_slice;
21use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
22 GPUCopyExternalImageDestInfo, GPUCopyExternalImageSourceInfo, GPUExtent3D, GPUQueueMethods,
23 GPUSize64, GPUTexelCopyBufferLayout, GPUTexelCopyTextureInfo,
24};
25use crate::dom::bindings::codegen::UnionTypes::{
26 ArrayBufferViewOrArrayBuffer as BufferSource,
27 ImageBitmapOrImageDataOrHTMLImageElementOrHTMLVideoElementOrHTMLCanvasElementOrOffscreenCanvas as GPUCopyExternalImageSource,
28};
29use crate::dom::bindings::error::{Error, Fallible};
30use crate::dom::bindings::reflector::DomGlobal;
31use crate::dom::bindings::root::{Dom, DomRoot};
32use crate::dom::bindings::str::USVString;
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::promise::{Promise, RootedPromise};
35use crate::dom::webgpu::gpubuffer::GPUBuffer;
36use crate::dom::webgpu::gpucommandbuffer::GPUCommandBuffer;
37use crate::dom::webgpu::gpudevice::GPUDevice;
38use crate::routed_promise::{RoutedPromiseListener, callback_promise};
39
40#[dom_struct]
41pub(crate) struct GPUQueue {
42 reflector_: Reflector,
43 #[ignore_malloc_size_of = "defined in webgpu"]
44 #[no_trace]
45 channel: WebGPU,
46 device: DomRefCell<Option<Dom<GPUDevice>>>,
47 label: DomRefCell<USVString>,
48 #[no_trace]
49 queue: WebGPUQueue,
50}
51
52impl GPUQueue {
53 fn new_inherited(channel: WebGPU, queue: WebGPUQueue) -> Self {
54 GPUQueue {
55 channel,
56 reflector_: Reflector::new(),
57 device: DomRefCell::new(None),
58 label: DomRefCell::new(USVString::default()),
59 queue,
60 }
61 }
62
63 pub(crate) fn new(
64 cx: &mut JSContext,
65 global: &GlobalScope,
66 channel: WebGPU,
67 queue: WebGPUQueue,
68 ) -> DomRoot<Self> {
69 reflect_dom_object(
70 cx,
71 Box::new(GPUQueue::new_inherited(channel, queue)),
72 global,
73 )
74 }
75}
76
77impl GPUQueue {
78 pub(crate) fn set_device(&self, no_gc: &NoGC, device: &GPUDevice) {
79 *self.device.safe_borrow_mut(no_gc) = Some(Dom::from_ref(device));
80 }
81
82 pub(crate) fn id(&self) -> WebGPUQueue {
83 self.queue
84 }
85}
86
87impl GPUQueueMethods<crate::DomTypeHolder> for GPUQueue {
88 fn Label(&self) -> USVString {
90 self.label.borrow().clone()
91 }
92
93 fn SetLabel(&self, no_gc: &NoGC, value: USVString) {
95 *self.label.safe_borrow_mut(no_gc) = value;
96 }
97
98 fn Submit(&self, command_buffers: Vec<DomRoot<GPUCommandBuffer>>) {
100 let command_buffers = command_buffers.iter().map(|cb| cb.id().0).collect();
101 self.channel
102 .0
103 .send(WebGPURequest::Submit {
104 device_id: self.device.borrow().as_ref().unwrap().id().0,
105 queue_id: self.queue.0,
106 command_buffers,
107 })
108 .unwrap();
109 }
110
111 fn WriteBuffer(
113 &self,
114 cx: &mut JSContext,
115 buffer: &GPUBuffer,
116 buffer_offset: GPUSize64,
117 data: BufferSource,
118 data_offset: GPUSize64,
119 size: Option<GPUSize64>,
120 ) -> Fallible<()> {
121 let (sizeof_element, data_len): (usize, usize) = match &data {
123 BufferSource::ArrayBufferView(d) => {
124 (d.get_array_type().byte_size().unwrap_or(1), d.len())
125 },
126 BufferSource::ArrayBuffer(d) => (1, d.len()),
127 };
128 let data_size: usize = data_len / sizeof_element;
130 debug_assert_eq!(data_len % sizeof_element, 0);
131 let content_size = if let Some(s) = size {
133 s
134 } else {
135 (data_size as GPUSize64)
136 .checked_sub(data_offset)
137 .ok_or(Error::Operation(Some(
138 "Overflow occured when calculating `contentsSize`".into(),
139 )))?
140 };
141
142 if !(data_offset + content_size <= data_size as u64) {
144 return Err(Error::Operation(Some(
145 "`dataOffset` + `contentsSize` is greater than `dataSize`".into(),
146 )));
147 }
148
149 if !((content_size * sizeof_element as u64)
150 .is_multiple_of(wgpu_types::COPY_BUFFER_ALIGNMENT))
151 {
152 return Err(Error::Operation(Some(
153 "`contentSize` as bytes is not a multiple of 4 bytes".into(),
154 )));
155 }
156
157 let byte_start = (data_offset as usize) * sizeof_element;
159 let byte_end = ((data_offset + content_size) as usize) * sizeof_element;
160 let contents = GenericSharedMemory::from_bytes(
161 &get_buffer_source_slice(&data, cx.no_gc())[byte_start..byte_end],
162 );
163 if let Err(e) = self.channel.0.send(WebGPURequest::WriteBuffer {
164 device_id: self.device.borrow().as_ref().unwrap().id().0,
165 queue_id: self.queue.0,
166 buffer_id: buffer.id().0,
167 buffer_offset,
168 data: contents,
169 }) {
170 warn!("Failed to send WriteBuffer({:?}) ({})", buffer.id(), e);
171 return Err(Error::Operation(Some(
172 "Failed to write buffer to GPU".into(),
173 )));
174 }
175
176 Ok(())
177 }
178
179 fn WriteTexture(
181 &self,
182 cx: &mut JSContext,
183 destination: &GPUTexelCopyTextureInfo,
184 data: BufferSource,
185 data_layout: &GPUTexelCopyBufferLayout,
186 size: GPUExtent3D,
187 ) -> Fallible<()> {
188 let bytes = get_buffer_source_slice(&data, cx.no_gc());
189 let len = bytes.len() as u64;
190
191 if !(data_layout.offset <= len) {
192 return Err(Error::Operation(Some(
193 "`dataLayout`'s offset is greater than texture buffer length".into(),
194 )));
195 }
196
197 let texture_cv = destination.try_convert()?;
198 let texture_layout = data_layout.convert();
199 let write_size = (&size).try_convert()?;
200 let final_data = GenericSharedMemory::from_bytes(bytes);
201
202 if let Err(e) = self.channel.0.send(WebGPURequest::WriteTexture {
203 device_id: self.device.borrow().as_ref().unwrap().id().0,
204 queue_id: self.queue.0,
205 texture_cv,
206 data_layout: texture_layout,
207 size: write_size,
208 data: final_data,
209 }) {
210 warn!(
211 "Failed to send WriteTexture({:?}) ({})",
212 destination.texture.id().0,
213 e
214 );
215 return Err(Error::Operation(Some(
216 "Failed to write to GPUTexture".into(),
217 )));
218 }
219
220 Ok(())
221 }
222
223 #[expect(
224 clippy::nonminimal_bool,
225 reason = "Following the spec steps more closely"
226 )]
227 fn CopyExternalImageToTexture(
229 &self,
230 cx: &mut JSContext,
231 source: &GPUCopyExternalImageSourceInfo,
232 destination: &GPUCopyExternalImageDestInfo,
233 copy_size: GPUExtent3D,
234 ) -> Fallible<()> {
235 let source_origin = source.origin.try_convert()?;
237 let destination_tex_info = destination.parent.try_convert()?;
239 let copy_size = copy_size.try_convert()?;
241 let source_image = &source.source;
243 let is_origin_clean = match source_image {
245 GPUCopyExternalImageSource::ImageBitmap(inner) => inner.origin_is_clean(),
246 GPUCopyExternalImageSource::ImageData(_) => true,
247 GPUCopyExternalImageSource::HTMLImageElement(inner) => {
248 inner.same_origin(&GlobalScope::entry().origin())
249 },
250 GPUCopyExternalImageSource::HTMLVideoElement(inner) => inner.origin_is_clean(),
251 GPUCopyExternalImageSource::HTMLCanvasElement(inner) => inner.origin_is_clean(),
252 GPUCopyExternalImageSource::OffscreenCanvas(inner) => inner.origin_is_clean(),
253 };
254 if !is_origin_clean {
255 return Err(Error::Security(Some(
256 "Image source is not origin clean!".to_string(),
257 )));
258 }
259 let (source_image_width, source_image_height) = match source_image {
261 GPUCopyExternalImageSource::ImageBitmap(inner) => (inner.Width(), inner.Height()),
262 GPUCopyExternalImageSource::ImageData(inner) => (inner.Width(), inner.Height()),
263 GPUCopyExternalImageSource::HTMLImageElement(inner) => (inner.Width(), inner.Height()),
264 GPUCopyExternalImageSource::HTMLVideoElement(inner) => (inner.Width(), inner.Height()),
265 GPUCopyExternalImageSource::HTMLCanvasElement(inner) => (inner.Width(), inner.Height()),
266 GPUCopyExternalImageSource::OffscreenCanvas(inner) => {
267 (inner.Width() as u32, inner.Height() as u32)
268 },
269 };
270 if !(source_origin.x + copy_size.width <= source_image_width) {
272 return Err(Error::Operation(Some(
273 "Source origin x + copy width exceeds source image width".to_string(),
274 )));
275 }
276 if !(source_origin.y + copy_size.height <= source_image_height) {
278 return Err(Error::Operation(Some(
279 "Source origin y + copy height exceeds source image height".to_string(),
280 )));
281 }
282 if !(copy_size.depth_or_array_layers <= 1) {
284 return Err(Error::Operation(Some(
285 "Copy depth or array layers must be less than or equal to 1".to_string(),
286 )));
287 }
288 let usable_snapshot = match source_image {
291 GPUCopyExternalImageSource::ImageBitmap(bitmap) => {
292 Some(bitmap.bitmap_data().clone().ok_or_else(|| {
294 Error::InvalidState(Some("ImageBitmap is detached".to_string()))
295 })?)
296 },
297 GPUCopyExternalImageSource::ImageData(data) => {
298 if data.is_detached(cx) {
300 return Err(Error::InvalidState(Some(
301 "ImageData is detached".to_string(),
302 )));
303 }
304 Some(data.get_snapshot(cx.no_gc()))
305 },
306 GPUCopyExternalImageSource::HTMLImageElement(inner) => {
307 if inner.is_usable()? {
308 inner.get_raster_image_data()
309 } else {
310 None
311 }
312 },
313 GPUCopyExternalImageSource::HTMLVideoElement(inner) => {
314 if inner.is_usable() {
315 inner.get_current_frame_data()
316 } else {
317 None
318 }
319 },
320 GPUCopyExternalImageSource::HTMLCanvasElement(inner) => {
321 if inner.is_valid() {
323 inner.get_image_data()
324 } else {
325 return Err(Error::InvalidState(Some(
326 "Canvas has zero area".to_string(),
327 )));
328 }
329 },
330 GPUCopyExternalImageSource::OffscreenCanvas(inner) => {
331 if inner.Width() == 0 || inner.Height() == 0 {
333 return Err(Error::InvalidState(Some(
334 "Canvas has zero area".to_string(),
335 )));
336 } else {
337 inner.get_image_data()
338 }
339 },
340 };
341 let texture_descriptor = destination.parent.texture.wgpu_texture_descriptor();
343 let target_snapshot_format =
344 match texture_descriptor.format {
345 wgpu_types::TextureFormat::Bgra8Unorm |
346 wgpu_types::TextureFormat::Bgra8UnormSrgb => SnapshotPixelFormat::BGRA,
347 wgpu_types::TextureFormat::Rgba8Unorm |
348 wgpu_types::TextureFormat::Rgba8UnormSrgb => SnapshotPixelFormat::RGBA,
349 _ => {
350 return Err(Error::Operation(Some(
351 "Unsupported texture format for copy".to_string(),
352 )));
353 },
354 };
355 let usable_snapshot = usable_snapshot.map(|mut snapshot| {
356 if source.flipY {
357 pixels::flip_y_rgba8_image_inplace(snapshot.size(), snapshot.as_raw_bytes_mut());
358 }
359 snapshot.transform(
360 SnapshotAlphaMode::Transparent {
361 premultiplied: destination.premultipliedAlpha,
362 },
363 target_snapshot_format,
364 );
365 snapshot.to_shared()
366 });
367 if let Err(e) = self
369 .channel
370 .0
371 .send(WebGPURequest::CopyExternalImageToTexture {
372 device_id: self.device.borrow().as_ref().unwrap().id().0,
373 queue_id: self.queue.0,
374 usable_source: usable_snapshot,
375 destination: destination_tex_info,
376 dest_tex_descriptor: texture_descriptor,
377 copy_size,
378 })
379 {
380 warn!(
381 "Failed to send CopyExternalImageToTexture({:?}) ({e})",
382 destination.parent.texture.id().0
383 );
384 return Err(Error::Operation(Some(
385 "Failed to copy external image to texture".into(),
386 )));
387 }
388 Ok(())
389 }
390
391 fn OnSubmittedWorkDone(&self, cx: &mut JSContext) -> RootedPromise {
393 let global = self.global();
394 let promise = Promise::new_rooted(cx, &global);
395 let task_manager = global.task_manager();
396 let task_source = task_manager.dom_manipulation_task_source();
397 let callback = callback_promise(&promise, self, task_source);
398
399 if let Err(e) = self
400 .channel
401 .0
402 .send(WebGPURequest::QueueOnSubmittedWorkDone {
403 sender: callback,
404 queue_id: self.queue.0,
405 })
406 {
407 warn!("QueueOnSubmittedWorkDone failed with {e}")
408 }
409 promise
410 }
411}
412
413impl RoutedPromiseListener<()> for GPUQueue {
414 fn handle_response(
415 &self,
416 cx: &mut js::context::JSContext,
417 _response: (),
418 promise: &RootedPromise,
419 ) {
420 promise.resolve_native(cx, &());
421 }
422}