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