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