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