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::{CanvasContext, CanvasHelpers, HTMLCanvasElementOrOffscreenCanvas};
24use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods;
25use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods;
26use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
27 GPUCanvasAlphaMode, GPUCanvasConfiguration, GPUDeviceMethods, GPUExtent3D, GPUExtent3DDict,
28 GPUObjectDescriptorBase, GPUTextureDescriptor, GPUTextureDimension, GPUTextureUsageConstants,
29};
30use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
31use crate::dom::bindings::error::{Error, Fallible};
32use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
33use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
34use crate::dom::bindings::str::USVString;
35use crate::dom::globalscope::GlobalScope;
36use crate::dom::htmlcanvaselement::HTMLCanvasElement;
37use crate::script_runtime::CanGc;
38
39fn supported_context_format(format: GPUTextureFormat) -> bool {
41 matches!(
43 format,
44 GPUTextureFormat::Bgra8unorm | GPUTextureFormat::Rgba8unorm
45 )
46}
47
48#[dom_struct]
49pub(crate) struct GPUCanvasContext {
50 reflector_: Reflector,
51 #[ignore_malloc_size_of = "channels are hard"]
52 #[no_trace]
53 channel: WebGPU,
54 canvas: HTMLCanvasElementOrOffscreenCanvas,
56 #[no_trace]
57 context_id: WebGPUContextId,
58 #[ignore_malloc_size_of = "manual writing is hard"]
59 configuration: RefCell<Option<GPUCanvasConfiguration>>,
61 texture_descriptor: RefCell<Option<GPUTextureDescriptor>>,
63 current_texture: MutNullableDom<GPUTexture>,
65 cleared: Cell<bool>,
68}
69
70impl GPUCanvasContext {
71 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
72 fn new_inherited(
73 global: &GlobalScope,
74 canvas: HTMLCanvasElementOrOffscreenCanvas,
75 channel: WebGPU,
76 ) -> Self {
77 let (sender, receiver) = ipc::channel().unwrap();
78 let size = canvas.size().cast().cast_unit();
79 let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
80 for _ in 0..PRESENTATION_BUFFER_COUNT {
81 buffer_ids.push(global.wgpu_id_hub().create_buffer_id());
82 }
83 if let Err(error) = channel.0.send(WebGPURequest::CreateContext {
84 buffer_ids,
85 size,
86 sender,
87 }) {
88 warn!("Failed to send CreateContext ({error:?})");
89 }
90 let context_id = receiver.recv().unwrap();
91
92 Self {
93 reflector_: Reflector::new(),
94 channel,
95 canvas,
96 context_id,
97 configuration: RefCell::new(None),
98 texture_descriptor: RefCell::new(None),
99 current_texture: MutNullableDom::default(),
100 cleared: Cell::new(true),
101 }
102 }
103
104 pub(crate) fn new(
105 global: &GlobalScope,
106 canvas: &HTMLCanvasElement,
107 channel: WebGPU,
108 can_gc: CanGc,
109 ) -> DomRoot<Self> {
110 reflect_dom_object(
111 Box::new(GPUCanvasContext::new_inherited(
112 global,
113 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(Dom::from_ref(canvas)),
114 channel,
115 )),
116 global,
117 can_gc,
118 )
119 }
120}
121
122impl GPUCanvasContext {
124 pub(crate) fn set_image_key(&self, image_key: ImageKey) {
125 if let Err(error) = self.channel.0.send(WebGPURequest::SetImageKey {
126 context_id: self.context_id,
127 image_key,
128 }) {
129 warn!(
130 "Failed to send WebGPURequest::Present({:?}) ({error})",
131 self.context_id
132 );
133 }
134 }
135
136 pub(crate) fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
138 if let Err(error) = self.channel.0.send(WebGPURequest::Present {
141 context_id: self.context_id,
142 pending_texture: self.pending_texture(),
143 size: self.size(),
144 canvas_epoch,
145 }) {
146 warn!(
147 "Failed to send WebGPURequest::Present({:?}) ({error})",
148 self.context_id
149 );
150 }
151
152 self.expire_current_texture(true);
154
155 true
156 }
157
158 fn texture_descriptor_for_canvas_and_configuration(
160 &self,
161 configuration: &GPUCanvasConfiguration,
162 ) -> GPUTextureDescriptor {
163 let size = self.size();
164 GPUTextureDescriptor {
165 size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
166 width: size.width,
167 height: size.height,
168 depthOrArrayLayers: 1,
169 }),
170 format: configuration.format,
171 usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
174 viewFormats: configuration.viewFormats.clone(),
175 mipLevelCount: 1,
177 sampleCount: 1,
178 parent: GPUObjectDescriptorBase {
179 label: USVString::default(),
180 },
181 dimension: GPUTextureDimension::_2d,
182 }
183 }
184
185 fn expire_current_texture(&self, skip_dirty: bool) {
187 if let Some(current_texture) = self.current_texture.take() {
190 current_texture.Destroy()
196 }
200 if !skip_dirty {
203 self.mark_as_dirty();
205 }
206 }
207
208 fn replace_drawing_buffer(&self) {
210 self.expire_current_texture(false);
212 self.cleared.set(true);
216 }
217}
218
219impl GPUCanvasContext {
221 fn context_configuration(&self) -> Option<ContextConfiguration> {
222 let configuration = self.configuration.borrow();
223 let configuration = configuration.as_ref()?;
224 Some(ContextConfiguration {
225 device_id: configuration.device.id().0,
226 queue_id: configuration.device.queue_id().0,
227 format: match configuration.format {
228 GPUTextureFormat::Bgra8unorm => ImageFormat::BGRA8,
229 GPUTextureFormat::Rgba8unorm => ImageFormat::RGBA8,
230 _ => unreachable!("Configure method should set valid texture format"),
231 },
232 is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque),
233 size: self.size(),
234 })
235 }
236
237 fn pending_texture(&self) -> Option<PendingTexture> {
238 self.current_texture.get().map(|texture| PendingTexture {
239 texture_id: texture.id().0,
240 encoder_id: self.global().wgpu_id_hub().create_command_encoder_id(),
241 configuration: self
242 .context_configuration()
243 .expect("Context should be configured if there is a texture."),
244 })
245 }
246}
247
248impl CanvasContext for GPUCanvasContext {
249 type ID = WebGPUContextId;
250
251 fn context_id(&self) -> WebGPUContextId {
252 self.context_id
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 self.canvas.mark_as_dirty();
301 }
302}
303
304impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext {
305 fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
307 RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
308 }
309
310 fn Configure(&self, configuration: &GPUCanvasConfiguration) -> Fallible<()> {
312 let device = &configuration.device;
314
315 let descriptor = self.texture_descriptor_for_canvas_and_configuration(configuration);
317
318 let (mut wgpu_descriptor, _) = convert_texture_descriptor(&descriptor, device)?;
321 wgpu_descriptor.label = Some(Cow::Borrowed(
322 "dummy texture for texture descriptor validation",
323 ));
324
325 if !supported_context_format(configuration.format) {
327 return Err(Error::Type(format!(
328 "Unsupported context format: {:?}",
329 configuration.format
330 )));
331 }
332
333 self.configuration.replace(Some(configuration.clone()));
335
336 self.texture_descriptor.replace(Some(descriptor));
338
339 self.replace_drawing_buffer();
341
342 let texture_id = self.global().wgpu_id_hub().create_texture_id();
344 self.channel
345 .0
346 .send(WebGPURequest::ValidateTextureDescriptor {
347 device_id: device.id().0,
348 texture_id,
349 descriptor: wgpu_descriptor,
350 })
351 .expect("Failed to create WebGPU SwapChain");
352
353 Ok(())
354 }
355
356 fn Unconfigure(&self) {
358 self.configuration.take();
360 self.current_texture.take();
362 self.replace_drawing_buffer();
364 }
365
366 fn GetCurrentTexture(&self) -> Fallible<DomRoot<GPUTexture>> {
368 let configuration = self.configuration.borrow();
370 let Some(configuration) = configuration.as_ref() else {
371 return Err(Error::InvalidState(None));
372 };
373 let texture_descriptor = self.texture_descriptor.borrow();
375 let texture_descriptor = texture_descriptor.as_ref().unwrap();
376 let device = &configuration.device;
378 let current_texture = if let Some(current_texture) = self.current_texture.get() {
379 current_texture
380 } else {
381 self.replace_drawing_buffer();
384 let current_texture = device.CreateTexture(texture_descriptor)?;
387 self.current_texture.set(Some(¤t_texture));
388
389 self.cleared.set(false);
391
392 current_texture
393 };
394 Ok(current_texture)
396 }
397}
398
399impl Drop for GPUCanvasContext {
400 fn drop(&mut self) {
401 if let Err(error) = self.channel.0.send(WebGPURequest::DestroyContext {
402 context_id: self.context_id,
403 }) {
404 warn!(
405 "Failed to send DestroyContext({:?}): {error}",
406 self.context_id
407 );
408 }
409 }
410}