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::GPUCanvasContextBinding::GPUCanvasContextMethods;
29use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods;
30use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
31 GPUCanvasAlphaMode, GPUCanvasConfiguration as RootedGPUCanvasConfiguration, GPUExtent3D,
32 GPUExtent3DDict, GPUObjectDescriptorBase, 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 #[ignore_malloc_size_of = "manual writing is hard"]
113 configuration: RefCell<Option<GPUCanvasConfiguration>>,
115 texture_descriptor: RefCell<Option<GPUTextureDescriptor>>,
117 current_texture: MutNullableDom<GPUTexture>,
119 cleared: Cell<bool>,
122 droppable: DroppableGPUCanvasContext,
123}
124
125impl GPUCanvasContext {
126 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
127 fn new_inherited(
128 global: &GlobalScope,
129 canvas: HTMLCanvasElementOrOffscreenCanvas,
130 channel: WebGPU,
131 ) -> Self {
132 let (sender, receiver) = generic_channel::channel().unwrap();
133 let size = canvas.size().cast().cast_unit();
134 let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
135 for _ in 0..PRESENTATION_BUFFER_COUNT {
136 buffer_ids.push(global.wgpu_id_hub().create_buffer_id());
137 }
138 if let Err(error) = channel.0.send(WebGPURequest::CreateContext {
139 buffer_ids,
140 size,
141 sender,
142 }) {
143 warn!("Failed to send CreateContext ({error:?})");
144 }
145 let context_id = receiver.recv().unwrap();
146
147 Self {
148 reflector_: Reflector::new(),
149 canvas,
150 configuration: RefCell::new(None),
151 texture_descriptor: RefCell::new(None),
152 current_texture: MutNullableDom::default(),
153 cleared: Cell::new(true),
154 droppable: DroppableGPUCanvasContext {
155 context_id,
156 channel,
157 },
158 }
159 }
160
161 pub(crate) fn new(
162 cx: &mut JSContext,
163 global: &GlobalScope,
164 canvas: &HTMLCanvasElement,
165 channel: WebGPU,
166 ) -> DomRoot<Self> {
167 reflect_weak_referenceable_dom_object(
168 cx,
169 Rc::new(GPUCanvasContext::new_inherited(
170 global,
171 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(Dom::from_ref(canvas)),
172 channel,
173 )),
174 global,
175 )
176 }
177}
178
179impl GPUCanvasContext {
181 pub(crate) fn set_image_key(&self, image_key: ImageKey) {
182 if let Err(error) = self.droppable.channel.0.send(WebGPURequest::SetImageKey {
183 context_id: self.context_id(),
184 image_key,
185 }) {
186 warn!(
187 "Failed to send WebGPURequest::Present({:?}) ({error})",
188 self.context_id()
189 );
190 }
191 }
192
193 pub(crate) fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
195 if let Err(error) = self.droppable.channel.0.send(WebGPURequest::Present {
198 context_id: self.context_id(),
199 pending_texture: self.pending_texture(),
200 size: self.size(),
201 canvas_epoch,
202 }) {
203 warn!(
204 "Failed to send WebGPURequest::Present({:?}) ({error})",
205 self.context_id()
206 );
207 }
208
209 self.expire_current_texture(true);
211
212 true
213 }
214
215 fn texture_descriptor_for_canvas_and_configuration(
217 &self,
218 configuration: &RootedGPUCanvasConfiguration,
219 ) -> GPUTextureDescriptor {
220 let size = self.size();
221 GPUTextureDescriptor {
222 size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
223 width: size.width,
224 height: size.height,
225 depthOrArrayLayers: 1,
226 }),
227 format: configuration.format,
228 usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
231 viewFormats: configuration.viewFormats.clone(),
232 mipLevelCount: 1,
234 sampleCount: 1,
235 parent: GPUObjectDescriptorBase {
236 label: USVString::default(),
237 },
238 dimension: GPUTextureDimension::_2d,
239 }
240 }
241
242 fn expire_current_texture(&self, skip_dirty: bool) {
244 if let Some(current_texture) = self.current_texture.take() {
247 current_texture.Destroy()
253 }
257 if !skip_dirty {
260 self.mark_as_dirty();
262 }
263 }
264
265 fn replace_drawing_buffer(&self) {
267 self.expire_current_texture(false);
269 self.cleared.set(true);
273 }
274}
275
276impl GPUCanvasContext {
278 fn context_configuration(&self) -> Option<ContextConfiguration> {
279 let configuration = self.configuration.borrow();
280 let configuration = configuration.as_ref()?;
281 Some(ContextConfiguration {
282 device_id: configuration.device.id().0,
283 queue_id: configuration.device.queue_id().0,
284 format: match configuration.format {
285 GPUTextureFormat::Bgra8unorm => ImageFormat::BGRA8,
286 GPUTextureFormat::Rgba8unorm => ImageFormat::RGBA8,
287 _ => unreachable!("Configure method should set valid texture format"),
288 },
289 is_opaque: matches!(configuration.alpha_mode, GPUCanvasAlphaMode::Opaque),
290 size: self.size(),
291 })
292 }
293
294 fn pending_texture(&self) -> Option<PendingTexture> {
295 self.current_texture.get().map(|texture| PendingTexture {
296 texture_id: texture.id().0,
297 encoder_id: self.global().wgpu_id_hub().create_command_encoder_id(),
298 command_buffer_id: self.global().wgpu_id_hub().create_command_buffer_id(),
299 configuration: self
300 .context_configuration()
301 .expect("Context should be configured if there is a texture."),
302 })
303 }
304}
305
306impl CanvasContext for GPUCanvasContext {
307 type ID = WebGPUContextId;
308
309 fn context_id(&self) -> WebGPUContextId {
310 self.droppable.context_id
311 }
312
313 fn resize(&self) {
315 self.replace_drawing_buffer();
317 let configuration = self.configuration.borrow();
319 if let Some(configuration) = configuration.as_ref() {
321 self.texture_descriptor.replace(Some(
324 self.texture_descriptor_for_canvas_and_configuration(&configuration.root()),
325 ));
326 }
327 }
328
329 fn reset_bitmap(&self) {
330 warn!("The GPUCanvasContext 'reset_bitmap' is not implemented yet");
331 }
332
333 fn get_image_data(&self) -> Option<Snapshot> {
335 Some(if self.cleared.get() {
337 Snapshot::cleared(self.size())
338 } else {
339 let (sender, receiver) = generic_channel::channel().unwrap();
340 self.droppable
341 .channel
342 .0
343 .send(WebGPURequest::GetImage {
344 context_id: self.context_id(),
345 pending_texture: self.pending_texture(),
347 sender,
348 })
349 .ok()?;
350 receiver.recv().ok()?.to_owned()
351 })
352 }
353
354 fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
355 Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas))
356 }
357
358 fn mark_as_dirty(&self) {
359 self.canvas.mark_as_dirty();
360 }
361}
362
363impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext {
364 fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
366 RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
367 }
368
369 fn Configure(&self, configuration: &RootedGPUCanvasConfiguration) -> Fallible<()> {
371 let device = &configuration.device;
373
374 let descriptor = self.texture_descriptor_for_canvas_and_configuration(configuration);
376
377 let (mut wgpu_descriptor, _) =
380 convert_texture_descriptor::<crate::DomTypeHolder>(&descriptor, device)?;
381 wgpu_descriptor.label = Some(Cow::Borrowed(
382 "dummy texture for texture descriptor validation",
383 ));
384
385 if !supported_context_format(configuration.format) {
387 return Err(Error::Type(cformat!(
388 "Unsupported context format: {:?}",
389 configuration.format
390 )));
391 }
392
393 if configuration.usage & GPUTextureUsageConstants::TRANSIENT_ATTACHMENT != 0 {
395 return Err(Error::Type(
396 c"configuration.usage includes the TRANSIENT_ATTACHMENT bit".into(),
397 ));
398 }
399
400 self.configuration.replace(Some(configuration.into()));
402
403 self.texture_descriptor.replace(Some(descriptor));
405
406 self.replace_drawing_buffer();
408
409 let texture_id = self.global().wgpu_id_hub().create_texture_id();
412 self.droppable
413 .channel
414 .0
415 .send(WebGPURequest::ValidateTextureDescriptor {
416 device_id: device.id().0,
417 texture_id,
418 descriptor: wgpu_descriptor,
419 })
420 .expect("Failed to create WebGPU SwapChain");
421
422 Ok(())
423 }
424
425 fn Unconfigure(&self) {
427 self.configuration.take();
429 self.current_texture.take();
431 self.replace_drawing_buffer();
433 }
434
435 fn GetConfiguration(&self) -> Option<RootedGPUCanvasConfiguration> {
437 self.configuration
438 .borrow()
439 .as_ref()
440 .map(|configuration| configuration.root())
441 }
442
443 fn GetCurrentTexture(&self, cx: &mut JSContext) -> Fallible<DomRoot<GPUTexture>> {
445 let configuration = self.configuration.borrow();
447 let Some(configuration) = configuration.as_ref() else {
448 return Err(Error::InvalidState(Some(
449 "GPUCanvasContext is not configured".into(),
450 )));
451 };
452 let texture_descriptor = self.texture_descriptor.borrow();
454 let texture_descriptor = texture_descriptor.as_ref().unwrap();
455 let device = &configuration.device;
457 let current_texture = if let Some(current_texture) = self.current_texture.get() {
458 current_texture
459 } else {
460 self.replace_drawing_buffer();
463 let current_texture = device.CreateTexture(cx, texture_descriptor)?;
466 self.current_texture.set(Some(¤t_texture));
467
468 self.cleared.set(false);
470
471 current_texture
472 };
473 Ok(current_texture)
475 }
476}