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