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