1use std::borrow::Cow;
6use std::cell::{Cell, RefCell};
7
8use arrayvec::ArrayVec;
9use base::{Epoch, generic_channel};
10use dom_struct::dom_struct;
11use pixels::Snapshot;
12use script_bindings::codegen::GenericBindings::WebGPUBinding::GPUTextureFormat;
13use webgpu_traits::{
14 ContextConfiguration, PRESENTATION_BUFFER_COUNT, PendingTexture, WebGPU, WebGPUContextId,
15 WebGPURequest,
16};
17use webrender_api::{ImageFormat, ImageKey};
18use wgpu_core::id;
19
20use super::gpuconvert::convert_texture_descriptor;
21use super::gputexture::GPUTexture;
22use crate::canvas_context::{CanvasContext, CanvasHelpers, HTMLCanvasElementOrOffscreenCanvas};
23use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods;
24use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods;
25use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
26 GPUCanvasAlphaMode, GPUCanvasConfiguration, GPUDeviceMethods, GPUExtent3D, GPUExtent3DDict,
27 GPUObjectDescriptorBase, GPUTextureDescriptor, GPUTextureDimension, GPUTextureUsageConstants,
28};
29use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
30use crate::dom::bindings::error::{Error, Fallible};
31use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
32use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
33use crate::dom::bindings::str::USVString;
34use crate::dom::globalscope::GlobalScope;
35use crate::dom::htmlcanvaselement::HTMLCanvasElement;
36use crate::script_runtime::CanGc;
37
38fn supported_context_format(format: GPUTextureFormat) -> bool {
40 matches!(
42 format,
43 GPUTextureFormat::Bgra8unorm | GPUTextureFormat::Rgba8unorm
44 )
45}
46
47#[derive(JSTraceable, MallocSizeOf)]
48struct DroppableGPUCanvasContext {
49 #[no_trace]
50 context_id: WebGPUContextId,
51 #[ignore_malloc_size_of = "channels are hard"]
52 #[no_trace]
53 channel: WebGPU,
54}
55
56impl Drop for DroppableGPUCanvasContext {
57 fn drop(&mut self) {
58 if let Err(error) = self.channel.0.send(WebGPURequest::DestroyContext {
59 context_id: self.context_id,
60 }) {
61 warn!(
62 "Failed to send DestroyContext({:?}): {error}",
63 self.context_id,
64 );
65 }
66 }
67}
68
69#[dom_struct]
70pub(crate) struct GPUCanvasContext {
71 reflector_: Reflector,
72 canvas: HTMLCanvasElementOrOffscreenCanvas,
74 #[ignore_malloc_size_of = "manual writing is hard"]
75 configuration: RefCell<Option<GPUCanvasConfiguration>>,
77 texture_descriptor: RefCell<Option<GPUTextureDescriptor>>,
79 current_texture: MutNullableDom<GPUTexture>,
81 cleared: Cell<bool>,
84 droppable: DroppableGPUCanvasContext,
85}
86
87impl GPUCanvasContext {
88 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
89 fn new_inherited(
90 global: &GlobalScope,
91 canvas: HTMLCanvasElementOrOffscreenCanvas,
92 channel: WebGPU,
93 ) -> Self {
94 let (sender, receiver) = generic_channel::channel().unwrap();
95 let size = canvas.size().cast().cast_unit();
96 let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
97 for _ in 0..PRESENTATION_BUFFER_COUNT {
98 buffer_ids.push(global.wgpu_id_hub().create_buffer_id());
99 }
100 if let Err(error) = channel.0.send(WebGPURequest::CreateContext {
101 buffer_ids,
102 size,
103 sender,
104 }) {
105 warn!("Failed to send CreateContext ({error:?})");
106 }
107 let context_id = receiver.recv().unwrap();
108
109 Self {
110 reflector_: Reflector::new(),
111 canvas,
112 configuration: RefCell::new(None),
113 texture_descriptor: RefCell::new(None),
114 current_texture: MutNullableDom::default(),
115 cleared: Cell::new(true),
116 droppable: DroppableGPUCanvasContext {
117 context_id,
118 channel,
119 },
120 }
121 }
122
123 pub(crate) fn new(
124 global: &GlobalScope,
125 canvas: &HTMLCanvasElement,
126 channel: WebGPU,
127 can_gc: CanGc,
128 ) -> DomRoot<Self> {
129 reflect_dom_object(
130 Box::new(GPUCanvasContext::new_inherited(
131 global,
132 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(Dom::from_ref(canvas)),
133 channel,
134 )),
135 global,
136 can_gc,
137 )
138 }
139}
140
141impl GPUCanvasContext {
143 pub(crate) fn set_image_key(&self, image_key: ImageKey) {
144 if let Err(error) = self.droppable.channel.0.send(WebGPURequest::SetImageKey {
145 context_id: self.context_id(),
146 image_key,
147 }) {
148 warn!(
149 "Failed to send WebGPURequest::Present({:?}) ({error})",
150 self.context_id()
151 );
152 }
153 }
154
155 pub(crate) fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
157 if let Err(error) = self.droppable.channel.0.send(WebGPURequest::Present {
160 context_id: self.context_id(),
161 pending_texture: self.pending_texture(),
162 size: self.size(),
163 canvas_epoch,
164 }) {
165 warn!(
166 "Failed to send WebGPURequest::Present({:?}) ({error})",
167 self.context_id()
168 );
169 }
170
171 self.expire_current_texture(true);
173
174 true
175 }
176
177 fn texture_descriptor_for_canvas_and_configuration(
179 &self,
180 configuration: &GPUCanvasConfiguration,
181 ) -> GPUTextureDescriptor {
182 let size = self.size();
183 GPUTextureDescriptor {
184 size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
185 width: size.width,
186 height: size.height,
187 depthOrArrayLayers: 1,
188 }),
189 format: configuration.format,
190 usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
193 viewFormats: configuration.viewFormats.clone(),
194 mipLevelCount: 1,
196 sampleCount: 1,
197 parent: GPUObjectDescriptorBase {
198 label: USVString::default(),
199 },
200 dimension: GPUTextureDimension::_2d,
201 }
202 }
203
204 fn expire_current_texture(&self, skip_dirty: bool) {
206 if let Some(current_texture) = self.current_texture.take() {
209 current_texture.Destroy()
215 }
219 if !skip_dirty {
222 self.mark_as_dirty();
224 }
225 }
226
227 fn replace_drawing_buffer(&self) {
229 self.expire_current_texture(false);
231 self.cleared.set(true);
235 }
236}
237
238impl GPUCanvasContext {
240 fn context_configuration(&self) -> Option<ContextConfiguration> {
241 let configuration = self.configuration.borrow();
242 let configuration = configuration.as_ref()?;
243 Some(ContextConfiguration {
244 device_id: configuration.device.id().0,
245 queue_id: configuration.device.queue_id().0,
246 format: match configuration.format {
247 GPUTextureFormat::Bgra8unorm => ImageFormat::BGRA8,
248 GPUTextureFormat::Rgba8unorm => ImageFormat::RGBA8,
249 _ => unreachable!("Configure method should set valid texture format"),
250 },
251 is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque),
252 size: self.size(),
253 })
254 }
255
256 fn pending_texture(&self) -> Option<PendingTexture> {
257 self.current_texture.get().map(|texture| PendingTexture {
258 texture_id: texture.id().0,
259 encoder_id: self.global().wgpu_id_hub().create_command_encoder_id(),
260 configuration: self
261 .context_configuration()
262 .expect("Context should be configured if there is a texture."),
263 })
264 }
265}
266
267impl CanvasContext for GPUCanvasContext {
268 type ID = WebGPUContextId;
269
270 fn context_id(&self) -> WebGPUContextId {
271 self.droppable.context_id
272 }
273
274 fn resize(&self) {
276 self.replace_drawing_buffer();
278 let configuration = self.configuration.borrow();
280 if let Some(configuration) = configuration.as_ref() {
282 self.texture_descriptor.replace(Some(
285 self.texture_descriptor_for_canvas_and_configuration(configuration),
286 ));
287 }
288 }
289
290 fn reset_bitmap(&self) {
291 warn!("The GPUCanvasContext 'reset_bitmap' is not implemented yet");
292 }
293
294 fn get_image_data(&self) -> Option<Snapshot> {
296 Some(if self.cleared.get() {
298 Snapshot::cleared(self.size())
299 } else {
300 let (sender, receiver) = generic_channel::channel().unwrap();
301 self.droppable
302 .channel
303 .0
304 .send(WebGPURequest::GetImage {
305 context_id: self.context_id(),
306 pending_texture: self.pending_texture(),
308 sender,
309 })
310 .ok()?;
311 receiver.recv().ok()?.to_owned()
312 })
313 }
314
315 fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
316 Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas))
317 }
318
319 fn mark_as_dirty(&self) {
320 self.canvas.mark_as_dirty();
321 }
322}
323
324impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext {
325 fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
327 RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
328 }
329
330 fn Configure(&self, configuration: &GPUCanvasConfiguration) -> Fallible<()> {
332 let device = &configuration.device;
334
335 let descriptor = self.texture_descriptor_for_canvas_and_configuration(configuration);
337
338 let (mut wgpu_descriptor, _) = convert_texture_descriptor(&descriptor, device)?;
341 wgpu_descriptor.label = Some(Cow::Borrowed(
342 "dummy texture for texture descriptor validation",
343 ));
344
345 if !supported_context_format(configuration.format) {
347 return Err(Error::Type(format!(
348 "Unsupported context format: {:?}",
349 configuration.format
350 )));
351 }
352
353 self.configuration.replace(Some(configuration.clone()));
355
356 self.texture_descriptor.replace(Some(descriptor));
358
359 self.replace_drawing_buffer();
361
362 let texture_id = self.global().wgpu_id_hub().create_texture_id();
364 self.droppable
365 .channel
366 .0
367 .send(WebGPURequest::ValidateTextureDescriptor {
368 device_id: device.id().0,
369 texture_id,
370 descriptor: wgpu_descriptor,
371 })
372 .expect("Failed to create WebGPU SwapChain");
373
374 Ok(())
375 }
376
377 fn Unconfigure(&self) {
379 self.configuration.take();
381 self.current_texture.take();
383 self.replace_drawing_buffer();
385 }
386
387 fn GetCurrentTexture(&self) -> Fallible<DomRoot<GPUTexture>> {
389 let configuration = self.configuration.borrow();
391 let Some(configuration) = configuration.as_ref() else {
392 return Err(Error::InvalidState(None));
393 };
394 let texture_descriptor = self.texture_descriptor.borrow();
396 let texture_descriptor = texture_descriptor.as_ref().unwrap();
397 let device = &configuration.device;
399 let current_texture = if let Some(current_texture) = self.current_texture.get() {
400 current_texture
401 } else {
402 self.replace_drawing_buffer();
405 let current_texture = device.CreateTexture(texture_descriptor)?;
408 self.current_texture.set(Some(¤t_texture));
409
410 self.cleared.set(false);
412
413 current_texture
414 };
415 Ok(current_texture)
417 }
418}