1use std::borrow::Cow;
6use std::cell::{Cell, RefCell};
7use std::rc::Rc;
8
9use arrayvec::ArrayVec;
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use pixels::Snapshot;
13use script_bindings::cformat;
14use script_bindings::codegen::GenericBindings::WebGPUBinding::{
15 GPUDeviceMethods, GPUTextureFormat, GPUTextureUsageConstants,
16};
17use script_bindings::reflector::{Reflector, reflect_weak_referenceable_dom_object};
18use script_webgpu::gpuconvert::convert_texture_descriptor;
19use servo_base::{Epoch, generic_channel};
20use webgpu_traits::{
21 ContextConfiguration, PRESENTATION_BUFFER_COUNT, PendingTexture, WebGPU, WebGPUContextId,
22 WebGPURequest, id,
23};
24use webrender_api::{ImageFormat, ImageKey};
25
26use super::gputexture::GPUTexture;
27use crate::canvas_context::{CanvasContext, CanvasHelpers, HTMLCanvasElementOrOffscreenCanvas};
28use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods;
29use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
30 GPUCanvasAlphaMode, GPUCanvasConfiguration as RootedGPUCanvasConfiguration,
31 GPUCanvasContextMethods, GPUExtent3D, GPUExtent3DDict, GPUObjectDescriptorBase,
32 GPUTextureDescriptor, GPUTextureDimension,
33};
34use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
35use crate::dom::bindings::error::{Error, Fallible};
36use crate::dom::bindings::reflector::DomGlobal;
37use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
38use crate::dom::bindings::str::USVString;
39use crate::dom::globalscope::GlobalScope;
40use crate::dom::htmlcanvaselement::HTMLCanvasElement;
41use crate::dom::webgpu::gpudevice::GPUDevice;
42
43fn supported_context_format(format: GPUTextureFormat) -> bool {
45 matches!(
47 format,
48 GPUTextureFormat::Bgra8unorm | GPUTextureFormat::Rgba8unorm
49 )
50}
51
52#[derive(JSTraceable, MallocSizeOf)]
53struct DroppableGPUCanvasContext {
54 #[no_trace]
55 context_id: WebGPUContextId,
56 #[no_trace]
57 channel: WebGPU,
58}
59
60impl Drop for DroppableGPUCanvasContext {
61 fn drop(&mut self) {
62 if let Err(error) = self.channel.0.send(WebGPURequest::DestroyContext {
63 context_id: self.context_id,
64 }) {
65 warn!(
66 "Failed to send DestroyContext({:?}): {error}",
67 self.context_id,
68 );
69 }
70 }
71}
72
73#[derive(JSTraceable, MallocSizeOf)]
74#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
75struct GPUCanvasConfiguration {
76 alpha_mode: GPUCanvasAlphaMode,
77 device: Dom<GPUDevice>,
78 format: GPUTextureFormat,
79 usage: u32,
80 view_formats: Vec<GPUTextureFormat>,
81}
82
83impl From<&RootedGPUCanvasConfiguration> for GPUCanvasConfiguration {
84 fn from(value: &RootedGPUCanvasConfiguration) -> GPUCanvasConfiguration {
85 GPUCanvasConfiguration {
86 alpha_mode: value.alphaMode,
87 device: value.device.as_traced(),
88 format: value.format,
89 usage: value.usage,
90 view_formats: value.viewFormats.clone(),
91 }
92 }
93}
94
95impl GPUCanvasConfiguration {
96 fn root(&self) -> RootedGPUCanvasConfiguration {
97 RootedGPUCanvasConfiguration {
98 alphaMode: self.alpha_mode,
99 device: self.device.as_rooted(),
100 format: self.format,
101 usage: self.usage,
102 viewFormats: self.view_formats.clone(),
103 }
104 }
105}
106
107#[dom_struct]
108pub(crate) struct GPUCanvasContext {
109 reflector_: Reflector,
110 canvas: HTMLCanvasElementOrOffscreenCanvas,
112 configuration: RefCell<Option<GPUCanvasConfiguration>>,
114 texture_descriptor: RefCell<Option<GPUTextureDescriptor>>,
116 current_texture: MutNullableDom<GPUTexture>,
118 cleared: Cell<bool>,
121 droppable: DroppableGPUCanvasContext,
122}
123
124impl GPUCanvasContext {
125 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
126 fn new_inherited(
127 global: &GlobalScope,
128 canvas: HTMLCanvasElementOrOffscreenCanvas,
129 channel: WebGPU,
130 ) -> Self {
131 let (sender, receiver) = generic_channel::channel().unwrap();
132 let size = canvas.size().cast().cast_unit();
133 let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
134 for _ in 0..PRESENTATION_BUFFER_COUNT {
135 buffer_ids.push(global.wgpu_id_hub().create_buffer_id());
136 }
137 if let Err(error) = channel.0.send(WebGPURequest::CreateContext {
138 buffer_ids,
139 size,
140 sender,
141 }) {
142 warn!("Failed to send CreateContext ({error:?})");
143 }
144 let context_id = receiver.recv().unwrap();
145
146 Self {
147 reflector_: Reflector::new(),
148 canvas,
149 configuration: RefCell::new(None),
150 texture_descriptor: RefCell::new(None),
151 current_texture: MutNullableDom::default(),
152 cleared: Cell::new(true),
153 droppable: DroppableGPUCanvasContext {
154 context_id,
155 channel,
156 },
157 }
158 }
159
160 pub(crate) fn new(
161 cx: &mut JSContext,
162 global: &GlobalScope,
163 canvas: &HTMLCanvasElement,
164 channel: WebGPU,
165 ) -> DomRoot<Self> {
166 reflect_weak_referenceable_dom_object(
167 cx,
168 Rc::new(GPUCanvasContext::new_inherited(
169 global,
170 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(Dom::from_ref(canvas)),
171 channel,
172 )),
173 global,
174 )
175 }
176}
177
178impl GPUCanvasContext {
180 pub(crate) fn set_image_key(&self, image_key: ImageKey) {
181 if let Err(error) = self.droppable.channel.0.send(WebGPURequest::SetImageKey {
182 context_id: self.context_id(),
183 image_key,
184 }) {
185 warn!(
186 "Failed to send WebGPURequest::Present({:?}) ({error})",
187 self.context_id()
188 );
189 }
190 }
191
192 pub(crate) fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
194 if let Err(error) = self.droppable.channel.0.send(WebGPURequest::Present {
197 context_id: self.context_id(),
198 pending_texture: self.pending_texture(),
199 size: self.size(),
200 canvas_epoch,
201 }) {
202 warn!(
203 "Failed to send WebGPURequest::Present({:?}) ({error})",
204 self.context_id()
205 );
206 }
207
208 self.expire_current_texture(true);
210
211 true
212 }
213
214 fn texture_descriptor_for_canvas_and_configuration(
216 &self,
217 configuration: &RootedGPUCanvasConfiguration,
218 ) -> GPUTextureDescriptor {
219 let size = self.size();
220 GPUTextureDescriptor {
221 size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
222 width: size.width,
223 height: size.height,
224 depthOrArrayLayers: 1,
225 }),
226 format: configuration.format,
227 usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
230 viewFormats: configuration.viewFormats.clone(),
231 mipLevelCount: 1,
233 sampleCount: 1,
234 parent: GPUObjectDescriptorBase {
235 label: USVString::default(),
236 },
237 dimension: GPUTextureDimension::_2d,
238 }
239 }
240
241 fn expire_current_texture(&self, skip_dirty: bool) {
243 if let Some(current_texture) = self.current_texture.take() {
246 current_texture.Destroy()
252 }
256 if !skip_dirty {
259 self.mark_as_dirty();
261 }
262 }
263
264 fn replace_drawing_buffer(&self) {
266 self.expire_current_texture(false);
268 self.cleared.set(true);
272 }
273}
274
275impl GPUCanvasContext {
277 fn context_configuration(&self) -> Option<ContextConfiguration> {
278 let configuration = self.configuration.borrow();
279 let configuration = configuration.as_ref()?;
280 Some(ContextConfiguration {
281 device_id: configuration.device.id().0,
282 queue_id: configuration.device.queue_id().0,
283 format: match configuration.format {
284 GPUTextureFormat::Bgra8unorm => ImageFormat::BGRA8,
285 GPUTextureFormat::Rgba8unorm => ImageFormat::RGBA8,
286 _ => unreachable!("Configure method should set valid texture format"),
287 },
288 is_opaque: matches!(configuration.alpha_mode, GPUCanvasAlphaMode::Opaque),
289 size: self.size(),
290 })
291 }
292
293 fn pending_texture(&self) -> Option<PendingTexture> {
294 self.current_texture.get().map(|texture| PendingTexture {
295 texture_id: texture.id().0,
296 encoder_id: self.global().wgpu_id_hub().create_command_encoder_id(),
297 command_buffer_id: self.global().wgpu_id_hub().create_command_buffer_id(),
298 configuration: self
299 .context_configuration()
300 .expect("Context should be configured if there is a texture."),
301 })
302 }
303}
304
305impl CanvasContext for GPUCanvasContext {
306 type ID = WebGPUContextId;
307
308 fn context_id(&self) -> WebGPUContextId {
309 self.droppable.context_id
310 }
311
312 fn resize(&self) {
314 self.replace_drawing_buffer();
316 let configuration = self.configuration.borrow();
318 if let Some(configuration) = configuration.as_ref() {
320 self.texture_descriptor.replace(Some(
323 self.texture_descriptor_for_canvas_and_configuration(&configuration.root()),
324 ));
325 }
326 }
327
328 fn reset_bitmap(&self) {
329 warn!("The GPUCanvasContext 'reset_bitmap' is not implemented yet");
330 }
331
332 fn get_image_data(&self) -> Option<Snapshot> {
334 Some(if self.cleared.get() {
336 Snapshot::cleared(self.size())
337 } else {
338 let (sender, receiver) = generic_channel::channel().unwrap();
339 self.droppable
340 .channel
341 .0
342 .send(WebGPURequest::GetImage {
343 context_id: self.context_id(),
344 pending_texture: self.pending_texture(),
346 sender,
347 })
348 .ok()?;
349 receiver.recv().ok()?.to_owned()
350 })
351 }
352
353 fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
354 Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas))
355 }
356
357 fn mark_as_dirty(&self) {
358 self.canvas.mark_as_dirty();
359 }
360}
361
362impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext {
363 fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
365 RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
366 }
367
368 fn Configure(&self, configuration: &RootedGPUCanvasConfiguration) -> Fallible<()> {
370 let device = &configuration.device;
372
373 let descriptor = self.texture_descriptor_for_canvas_and_configuration(configuration);
375
376 let (mut wgpu_descriptor, _) =
379 convert_texture_descriptor::<crate::DomTypeHolder>(&descriptor, device)?;
380 wgpu_descriptor.label = Some(Cow::Borrowed(
381 "dummy texture for texture descriptor validation",
382 ));
383
384 if !supported_context_format(configuration.format) {
386 return Err(Error::Type(cformat!(
387 "Unsupported context format: {:?}",
388 configuration.format
389 )));
390 }
391
392 if configuration.usage & GPUTextureUsageConstants::TRANSIENT_ATTACHMENT != 0 {
394 return Err(Error::Type(
395 c"configuration.usage includes the TRANSIENT_ATTACHMENT bit".into(),
396 ));
397 }
398
399 self.configuration.replace(Some(configuration.into()));
401
402 self.texture_descriptor.replace(Some(descriptor));
404
405 self.replace_drawing_buffer();
407
408 let texture_id = self.global().wgpu_id_hub().create_texture_id();
411 self.droppable
412 .channel
413 .0
414 .send(WebGPURequest::ValidateTextureDescriptor {
415 device_id: device.id().0,
416 texture_id,
417 descriptor: wgpu_descriptor,
418 })
419 .expect("Failed to create WebGPU SwapChain");
420
421 Ok(())
422 }
423
424 fn Unconfigure(&self) {
426 self.configuration.take();
428 self.current_texture.take();
430 self.replace_drawing_buffer();
432 }
433
434 fn GetConfiguration(&self) -> Option<RootedGPUCanvasConfiguration> {
436 self.configuration
437 .borrow()
438 .as_ref()
439 .map(|configuration| configuration.root())
440 }
441
442 fn GetCurrentTexture(&self, cx: &mut JSContext) -> Fallible<DomRoot<GPUTexture>> {
444 let configuration = self.configuration.borrow();
446 let Some(configuration) = configuration.as_ref() else {
447 return Err(Error::InvalidState(Some(
448 "GPUCanvasContext is not configured".into(),
449 )));
450 };
451 let texture_descriptor = self.texture_descriptor.borrow();
453 let texture_descriptor = texture_descriptor.as_ref().unwrap();
454 let device = &configuration.device;
456 let current_texture = if let Some(current_texture) = self.current_texture.get() {
457 current_texture
458 } else {
459 self.replace_drawing_buffer();
462 let current_texture = device.CreateTexture(cx, texture_descriptor)?;
465 self.current_texture.set(Some(¤t_texture));
466
467 self.cleared.set(false);
469
470 current_texture
471 };
472 Ok(current_texture)
474 }
475}