1use std::borrow::Cow;
6use std::cell::{Cell, RefCell};
7
8use arrayvec::ArrayVec;
9use base::Epoch;
10use dom_struct::dom_struct;
11use ipc_channel::ipc::{self};
12use pixels::Snapshot;
13use script_bindings::codegen::GenericBindings::WebGPUBinding::GPUTextureFormat;
14use script_bindings::inheritance::Castable;
15use webgpu_traits::{
16 ContextConfiguration, PRESENTATION_BUFFER_COUNT, PendingTexture, WebGPU, WebGPUContextId,
17 WebGPURequest,
18};
19use webrender_api::{ImageFormat, ImageKey};
20use wgpu_core::id;
21
22use super::gpuconvert::convert_texture_descriptor;
23use super::gputexture::GPUTexture;
24use crate::canvas_context::{
25 CanvasContext, CanvasHelpers, HTMLCanvasElementOrOffscreenCanvas,
26 LayoutCanvasRenderingContextHelpers,
27};
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, GPUDeviceMethods, GPUExtent3D, GPUExtent3DDict,
32 GPUObjectDescriptorBase, GPUTextureDescriptor, GPUTextureDimension, GPUTextureUsageConstants,
33};
34use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
35use crate::dom::bindings::error::{Error, Fallible};
36use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
37use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
38use crate::dom::bindings::str::USVString;
39use crate::dom::globalscope::GlobalScope;
40use crate::dom::html::htmlcanvaselement::HTMLCanvasElement;
41use crate::dom::node::{Node, NodeDamage, NodeTraits};
42use crate::script_runtime::CanGc;
43
44fn supported_context_format(format: GPUTextureFormat) -> bool {
46 matches!(
48 format,
49 GPUTextureFormat::Bgra8unorm | GPUTextureFormat::Rgba8unorm
50 )
51}
52
53#[dom_struct]
54pub(crate) struct GPUCanvasContext {
55 reflector_: Reflector,
56 #[ignore_malloc_size_of = "channels are hard"]
57 #[no_trace]
58 channel: WebGPU,
59 canvas: HTMLCanvasElementOrOffscreenCanvas,
61 #[ignore_malloc_size_of = "Defined in webrender"]
62 #[no_trace]
63 webrender_image: ImageKey,
64 #[no_trace]
65 context_id: WebGPUContextId,
66 #[ignore_malloc_size_of = "manual writing is hard"]
67 configuration: RefCell<Option<GPUCanvasConfiguration>>,
69 texture_descriptor: RefCell<Option<GPUTextureDescriptor>>,
71 current_texture: MutNullableDom<GPUTexture>,
73 cleared: Cell<bool>,
76}
77
78impl GPUCanvasContext {
79 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
80 fn new_inherited(
81 global: &GlobalScope,
82 canvas: HTMLCanvasElementOrOffscreenCanvas,
83 channel: WebGPU,
84 ) -> Self {
85 let (sender, receiver) = ipc::channel().unwrap();
86 let size = canvas.size().cast().cast_unit();
87 let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
88 for _ in 0..PRESENTATION_BUFFER_COUNT {
89 buffer_ids.push(global.wgpu_id_hub().create_buffer_id());
90 }
91 if let Err(e) = channel.0.send(WebGPURequest::CreateContext {
92 buffer_ids,
93 size,
94 sender,
95 }) {
96 warn!("Failed to send CreateContext ({:?})", e);
97 }
98 let (external_id, webrender_image) = receiver.recv().unwrap();
99 Self {
100 reflector_: Reflector::new(),
101 channel,
102 canvas,
103 webrender_image,
104 context_id: WebGPUContextId(external_id.0),
105 configuration: RefCell::new(None),
106 texture_descriptor: RefCell::new(None),
107 current_texture: MutNullableDom::default(),
108 cleared: Cell::new(true),
109 }
110 }
111
112 pub(crate) fn new(
113 global: &GlobalScope,
114 canvas: &HTMLCanvasElement,
115 channel: WebGPU,
116 can_gc: CanGc,
117 ) -> DomRoot<Self> {
118 reflect_dom_object(
119 Box::new(GPUCanvasContext::new_inherited(
120 global,
121 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(Dom::from_ref(canvas)),
122 channel,
123 )),
124 global,
125 can_gc,
126 )
127 }
128}
129
130impl GPUCanvasContext {
132 fn texture_descriptor_for_canvas_and_configuration(
134 &self,
135 configuration: &GPUCanvasConfiguration,
136 ) -> GPUTextureDescriptor {
137 let size = self.size();
138 GPUTextureDescriptor {
139 size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
140 width: size.width,
141 height: size.height,
142 depthOrArrayLayers: 1,
143 }),
144 format: configuration.format,
145 usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
148 viewFormats: configuration.viewFormats.clone(),
149 mipLevelCount: 1,
151 sampleCount: 1,
152 parent: GPUObjectDescriptorBase {
153 label: USVString::default(),
154 },
155 dimension: GPUTextureDimension::_2d,
156 }
157 }
158
159 fn expire_current_texture(&self, skip_dirty: bool) {
161 if let Some(current_texture) = self.current_texture.take() {
164 current_texture.Destroy()
170 }
174 if !skip_dirty {
177 self.mark_as_dirty();
179 }
180 }
181
182 fn replace_drawing_buffer(&self) {
184 self.expire_current_texture(false);
186 self.cleared.set(true);
190 }
191}
192
193impl GPUCanvasContext {
195 fn context_configuration(&self) -> Option<ContextConfiguration> {
196 let configuration = self.configuration.borrow();
197 let configuration = configuration.as_ref()?;
198 Some(ContextConfiguration {
199 device_id: configuration.device.id().0,
200 queue_id: configuration.device.queue_id().0,
201 format: match configuration.format {
202 GPUTextureFormat::Bgra8unorm => ImageFormat::BGRA8,
203 GPUTextureFormat::Rgba8unorm => ImageFormat::RGBA8,
204 _ => unreachable!("Configure method should set valid texture format"),
205 },
206 is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque),
207 size: self.size(),
208 })
209 }
210
211 fn pending_texture(&self) -> Option<PendingTexture> {
212 self.current_texture.get().map(|texture| PendingTexture {
213 texture_id: texture.id().0,
214 encoder_id: self.global().wgpu_id_hub().create_command_encoder_id(),
215 configuration: self
216 .context_configuration()
217 .expect("Context should be configured if there is a texture."),
218 })
219 }
220}
221
222impl CanvasContext for GPUCanvasContext {
223 type ID = WebGPUContextId;
224
225 fn context_id(&self) -> WebGPUContextId {
226 self.context_id
227 }
228
229 fn image_key(&self) -> Option<ImageKey> {
230 Some(self.webrender_image)
231 }
232
233 fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
235 if let Err(error) = self.channel.0.send(WebGPURequest::Present {
238 context_id: self.context_id,
239 pending_texture: self.pending_texture(),
240 size: self.size(),
241 canvas_epoch,
242 }) {
243 warn!(
244 "Failed to send WebGPURequest::Present({:?}) ({error})",
245 self.context_id
246 );
247 }
248
249 self.expire_current_texture(true);
251
252 true
253 }
254
255 fn resize(&self) {
257 self.replace_drawing_buffer();
259 let configuration = self.configuration.borrow();
261 if let Some(configuration) = configuration.as_ref() {
263 self.texture_descriptor.replace(Some(
266 self.texture_descriptor_for_canvas_and_configuration(configuration),
267 ));
268 }
269 }
270
271 fn reset_bitmap(&self) {
272 warn!("The GPUCanvasContext 'reset_bitmap' is not implemented yet");
273 }
274
275 fn get_image_data(&self) -> Option<Snapshot> {
277 Some(if self.cleared.get() {
279 Snapshot::cleared(self.size())
280 } else {
281 let (sender, receiver) = ipc::channel().unwrap();
282 self.channel
283 .0
284 .send(WebGPURequest::GetImage {
285 context_id: self.context_id,
286 pending_texture: self.pending_texture(),
288 sender,
289 })
290 .ok()?;
291 receiver.recv().ok()?.to_owned()
292 })
293 }
294
295 fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
296 Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas))
297 }
298
299 fn mark_as_dirty(&self) {
300 if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(ref canvas) = self.canvas {
301 canvas.upcast::<Node>().dirty(NodeDamage::Other);
302 canvas.owner_document().add_dirty_webgpu_context(self);
303 }
304 }
305}
306
307impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, GPUCanvasContext> {
308 fn canvas_data_source(self) -> Option<ImageKey> {
309 (*self.unsafe_get()).image_key()
310 }
311}
312
313impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext {
314 fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
316 RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
317 }
318
319 fn Configure(&self, configuration: &GPUCanvasConfiguration) -> Fallible<()> {
321 let device = &configuration.device;
323
324 let descriptor = self.texture_descriptor_for_canvas_and_configuration(configuration);
326
327 let (mut wgpu_descriptor, _) = convert_texture_descriptor(&descriptor, device)?;
330 wgpu_descriptor.label = Some(Cow::Borrowed(
331 "dummy texture for texture descriptor validation",
332 ));
333
334 if !supported_context_format(configuration.format) {
336 return Err(Error::Type(format!(
337 "Unsupported context format: {:?}",
338 configuration.format
339 )));
340 }
341
342 self.configuration.replace(Some(configuration.clone()));
344
345 self.texture_descriptor.replace(Some(descriptor));
347
348 self.replace_drawing_buffer();
350
351 let texture_id = self.global().wgpu_id_hub().create_texture_id();
353 self.channel
354 .0
355 .send(WebGPURequest::ValidateTextureDescriptor {
356 device_id: device.id().0,
357 texture_id,
358 descriptor: wgpu_descriptor,
359 })
360 .expect("Failed to create WebGPU SwapChain");
361
362 Ok(())
363 }
364
365 fn Unconfigure(&self) {
367 self.configuration.take();
369 self.current_texture.take();
371 self.replace_drawing_buffer();
373 }
374
375 fn GetCurrentTexture(&self) -> Fallible<DomRoot<GPUTexture>> {
377 let configuration = self.configuration.borrow();
379 let Some(configuration) = configuration.as_ref() else {
380 return Err(Error::InvalidState);
381 };
382 let texture_descriptor = self.texture_descriptor.borrow();
384 let texture_descriptor = texture_descriptor.as_ref().unwrap();
385 let device = &configuration.device;
387 let current_texture = if let Some(current_texture) = self.current_texture.get() {
388 current_texture
389 } else {
390 self.replace_drawing_buffer();
393 let current_texture = device.CreateTexture(texture_descriptor)?;
396 self.current_texture.set(Some(¤t_texture));
397
398 self.cleared.set(false);
400
401 current_texture
402 };
403 Ok(current_texture)
405 }
406}
407
408impl Drop for GPUCanvasContext {
409 fn drop(&mut self) {
410 if let Err(e) = self.channel.0.send(WebGPURequest::DestroyContext {
411 context_id: self.context_id,
412 }) {
413 warn!(
414 "Failed to send DestroySwapChain-ImageKey({:?}) ({})",
415 self.webrender_image, e
416 );
417 }
418 }
419}