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 GPUTextureFormat, GPUTextureUsageConstants,
16};
17use script_bindings::reflector::{Reflector, reflect_weak_referenceable_dom_object};
18use servo_base::{Epoch, generic_channel};
19use webgpu_traits::{
20 ContextConfiguration, PRESENTATION_BUFFER_COUNT, PendingTexture, WebGPU, WebGPUContextId,
21 WebGPURequest,
22};
23use webrender_api::{ImageFormat, ImageKey};
24use wgpu_core::id;
25
26use super::gpuconvert::convert_texture_descriptor;
27use super::gputexture::GPUTexture;
28use crate::canvas_context::{CanvasContext, CanvasHelpers, HTMLCanvasElementOrOffscreenCanvas};
29use crate::dom::bindings::codegen::Bindings::GPUCanvasContextBinding::GPUCanvasContextMethods;
30use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods;
31use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
32 GPUCanvasAlphaMode, GPUCanvasConfiguration, GPUDeviceMethods, GPUExtent3D, GPUExtent3DDict,
33 GPUObjectDescriptorBase, GPUTextureDescriptor, GPUTextureDimension,
34};
35use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas as RootedHTMLCanvasElementOrOffscreenCanvas;
36use crate::dom::bindings::error::{Error, Fallible};
37use crate::dom::bindings::reflector::DomGlobal;
38use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
39use crate::dom::bindings::str::USVString;
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::htmlcanvaselement::HTMLCanvasElement;
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#[dom_struct]
74pub(crate) struct GPUCanvasContext {
75 reflector_: Reflector,
76 canvas: HTMLCanvasElementOrOffscreenCanvas,
78 #[ignore_malloc_size_of = "manual writing is hard"]
79 configuration: RefCell<Option<GPUCanvasConfiguration>>,
81 texture_descriptor: RefCell<Option<GPUTextureDescriptor>>,
83 current_texture: MutNullableDom<GPUTexture>,
85 cleared: Cell<bool>,
88 droppable: DroppableGPUCanvasContext,
89}
90
91impl GPUCanvasContext {
92 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
93 fn new_inherited(
94 global: &GlobalScope,
95 canvas: HTMLCanvasElementOrOffscreenCanvas,
96 channel: WebGPU,
97 ) -> Self {
98 let (sender, receiver) = generic_channel::channel().unwrap();
99 let size = canvas.size().cast().cast_unit();
100 let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
101 for _ in 0..PRESENTATION_BUFFER_COUNT {
102 buffer_ids.push(global.wgpu_id_hub().create_buffer_id());
103 }
104 if let Err(error) = channel.0.send(WebGPURequest::CreateContext {
105 buffer_ids,
106 size,
107 sender,
108 }) {
109 warn!("Failed to send CreateContext ({error:?})");
110 }
111 let context_id = receiver.recv().unwrap();
112
113 Self {
114 reflector_: Reflector::new(),
115 canvas,
116 configuration: RefCell::new(None),
117 texture_descriptor: RefCell::new(None),
118 current_texture: MutNullableDom::default(),
119 cleared: Cell::new(true),
120 droppable: DroppableGPUCanvasContext {
121 context_id,
122 channel,
123 },
124 }
125 }
126
127 pub(crate) fn new(
128 cx: &mut JSContext,
129 global: &GlobalScope,
130 canvas: &HTMLCanvasElement,
131 channel: WebGPU,
132 ) -> DomRoot<Self> {
133 reflect_weak_referenceable_dom_object(
134 cx,
135 Rc::new(GPUCanvasContext::new_inherited(
136 global,
137 HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(Dom::from_ref(canvas)),
138 channel,
139 )),
140 global,
141 )
142 }
143}
144
145impl GPUCanvasContext {
147 pub(crate) fn set_image_key(&self, image_key: ImageKey) {
148 if let Err(error) = self.droppable.channel.0.send(WebGPURequest::SetImageKey {
149 context_id: self.context_id(),
150 image_key,
151 }) {
152 warn!(
153 "Failed to send WebGPURequest::Present({:?}) ({error})",
154 self.context_id()
155 );
156 }
157 }
158
159 pub(crate) fn update_rendering(&self, canvas_epoch: Epoch) -> bool {
161 if let Err(error) = self.droppable.channel.0.send(WebGPURequest::Present {
164 context_id: self.context_id(),
165 pending_texture: self.pending_texture(),
166 size: self.size(),
167 canvas_epoch,
168 }) {
169 warn!(
170 "Failed to send WebGPURequest::Present({:?}) ({error})",
171 self.context_id()
172 );
173 }
174
175 self.expire_current_texture(true);
177
178 true
179 }
180
181 fn texture_descriptor_for_canvas_and_configuration(
183 &self,
184 configuration: &GPUCanvasConfiguration,
185 ) -> GPUTextureDescriptor {
186 let size = self.size();
187 GPUTextureDescriptor {
188 size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
189 width: size.width,
190 height: size.height,
191 depthOrArrayLayers: 1,
192 }),
193 format: configuration.format,
194 usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
197 viewFormats: configuration.viewFormats.clone(),
198 mipLevelCount: 1,
200 sampleCount: 1,
201 parent: GPUObjectDescriptorBase {
202 label: USVString::default(),
203 },
204 dimension: GPUTextureDimension::_2d,
205 }
206 }
207
208 fn expire_current_texture(&self, skip_dirty: bool) {
210 if let Some(current_texture) = self.current_texture.take() {
213 current_texture.Destroy()
219 }
223 if !skip_dirty {
226 self.mark_as_dirty();
228 }
229 }
230
231 fn replace_drawing_buffer(&self) {
233 self.expire_current_texture(false);
235 self.cleared.set(true);
239 }
240}
241
242impl GPUCanvasContext {
244 fn context_configuration(&self) -> Option<ContextConfiguration> {
245 let configuration = self.configuration.borrow();
246 let configuration = configuration.as_ref()?;
247 Some(ContextConfiguration {
248 device_id: configuration.device.id().0,
249 queue_id: configuration.device.queue_id().0,
250 format: match configuration.format {
251 GPUTextureFormat::Bgra8unorm => ImageFormat::BGRA8,
252 GPUTextureFormat::Rgba8unorm => ImageFormat::RGBA8,
253 _ => unreachable!("Configure method should set valid texture format"),
254 },
255 is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque),
256 size: self.size(),
257 })
258 }
259
260 fn pending_texture(&self) -> Option<PendingTexture> {
261 self.current_texture.get().map(|texture| PendingTexture {
262 texture_id: texture.id().0,
263 encoder_id: self.global().wgpu_id_hub().create_command_encoder_id(),
264 command_buffer_id: self.global().wgpu_id_hub().create_command_buffer_id(),
265 configuration: self
266 .context_configuration()
267 .expect("Context should be configured if there is a texture."),
268 })
269 }
270}
271
272impl CanvasContext for GPUCanvasContext {
273 type ID = WebGPUContextId;
274
275 fn context_id(&self) -> WebGPUContextId {
276 self.droppable.context_id
277 }
278
279 fn resize(&self) {
281 self.replace_drawing_buffer();
283 let configuration = self.configuration.borrow();
285 if let Some(configuration) = configuration.as_ref() {
287 self.texture_descriptor.replace(Some(
290 self.texture_descriptor_for_canvas_and_configuration(configuration),
291 ));
292 }
293 }
294
295 fn reset_bitmap(&self) {
296 warn!("The GPUCanvasContext 'reset_bitmap' is not implemented yet");
297 }
298
299 fn get_image_data(&self) -> Option<Snapshot> {
301 Some(if self.cleared.get() {
303 Snapshot::cleared(self.size())
304 } else {
305 let (sender, receiver) = generic_channel::channel().unwrap();
306 self.droppable
307 .channel
308 .0
309 .send(WebGPURequest::GetImage {
310 context_id: self.context_id(),
311 pending_texture: self.pending_texture(),
313 sender,
314 })
315 .ok()?;
316 receiver.recv().ok()?.to_owned()
317 })
318 }
319
320 fn canvas(&self) -> Option<RootedHTMLCanvasElementOrOffscreenCanvas> {
321 Some(RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas))
322 }
323
324 fn mark_as_dirty(&self) {
325 self.canvas.mark_as_dirty();
326 }
327}
328
329impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext {
330 fn Canvas(&self) -> RootedHTMLCanvasElementOrOffscreenCanvas {
332 RootedHTMLCanvasElementOrOffscreenCanvas::from(&self.canvas)
333 }
334
335 fn Configure(&self, configuration: &GPUCanvasConfiguration) -> Fallible<()> {
337 let device = &configuration.device;
339
340 let descriptor = self.texture_descriptor_for_canvas_and_configuration(configuration);
342
343 let (mut wgpu_descriptor, _) = convert_texture_descriptor(&descriptor, device)?;
346 wgpu_descriptor.label = Some(Cow::Borrowed(
347 "dummy texture for texture descriptor validation",
348 ));
349
350 if !supported_context_format(configuration.format) {
352 return Err(Error::Type(cformat!(
353 "Unsupported context format: {:?}",
354 configuration.format
355 )));
356 }
357
358 if configuration.usage & GPUTextureUsageConstants::TRANSIENT_ATTACHMENT != 0 {
360 return Err(Error::Type(
361 c"configuration.usage includes the TRANSIENT_ATTACHMENT bit".into(),
362 ));
363 }
364
365 self.configuration.replace(Some(configuration.clone()));
367
368 self.texture_descriptor.replace(Some(descriptor));
370
371 self.replace_drawing_buffer();
373
374 let texture_id = self.global().wgpu_id_hub().create_texture_id();
377 self.droppable
378 .channel
379 .0
380 .send(WebGPURequest::ValidateTextureDescriptor {
381 device_id: device.id().0,
382 texture_id,
383 descriptor: wgpu_descriptor,
384 })
385 .expect("Failed to create WebGPU SwapChain");
386
387 Ok(())
388 }
389
390 fn Unconfigure(&self) {
392 self.configuration.take();
394 self.current_texture.take();
396 self.replace_drawing_buffer();
398 }
399
400 fn GetConfiguration(&self) -> Option<GPUCanvasConfiguration> {
402 self.configuration.borrow().clone()
403 }
404
405 fn GetCurrentTexture(&self, cx: &mut JSContext) -> Fallible<DomRoot<GPUTexture>> {
407 let configuration = self.configuration.borrow();
409 let Some(configuration) = configuration.as_ref() else {
410 return Err(Error::InvalidState(None));
411 };
412 let texture_descriptor = self.texture_descriptor.borrow();
414 let texture_descriptor = texture_descriptor.as_ref().unwrap();
415 let device = &configuration.device;
417 let current_texture = if let Some(current_texture) = self.current_texture.get() {
418 current_texture
419 } else {
420 self.replace_drawing_buffer();
423 let current_texture = device.CreateTexture(cx, texture_descriptor)?;
426 self.current_texture.set(Some(¤t_texture));
427
428 self.cleared.set(false);
430
431 current_texture
432 };
433 Ok(current_texture)
435 }
436}