1use std::ptr::NonNull;
8use std::sync::{Arc, Mutex};
9
10use arrayvec::ArrayVec;
11use euclid::default::Size2D;
12use log::warn;
13use paint_api::{
14 CrossProcessPaintApi, ExternalImageSource, SerializableImageData, WebRenderExternalImageApi,
15};
16use pixels::{SharedSnapshot, Snapshot, SnapshotAlphaMode, SnapshotPixelFormat};
17use rustc_hash::FxHashMap;
18use servo_base::Epoch;
19use servo_base::generic_channel::GenericSender;
20use webgpu_traits::id::{
21 self, BufferId, CommandBufferId, CommandEncoderId, DeviceId, QueueId, TextureId,
22};
23use webgpu_traits::{
24 BufferDescriptor, BufferUsages, CommandBufferDescriptor, CommandEncoderDescriptor,
25 ContextConfiguration, Extent3d, HostMap, Origin3d, PRESENTATION_BUFFER_COUNT, PendingTexture,
26 TexelCopyBufferInfo, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect,
27 WebGPUContextId, WebGPUMsg,
28};
29use webrender_api::units::DeviceIntSize;
30use webrender_api::{
31 ExternalImageData, ExternalImageId, ExternalImageType, ImageDescriptor, ImageDescriptorFlags,
32 ImageFormat, ImageKey,
33};
34use wgpu_core::global::Global;
35use wgpu_core::resource::{BufferAccessError, BufferMapOperation, CreateBufferError};
36use wgpu_types::COPY_BYTES_PER_ROW_ALIGNMENT;
37
38pub type WebGpuExternalImageMap = Arc<Mutex<FxHashMap<WebGPUContextId, ContextData>>>;
39
40const fn image_data(context_id: WebGPUContextId) -> ExternalImageData {
41 ExternalImageData {
42 id: ExternalImageId(context_id.0),
43 channel_index: 0,
44 image_type: ExternalImageType::Buffer,
45 normalized_uvs: false,
46 }
47}
48
49#[derive(Clone, Copy, Debug)]
51struct Buffer {
52 device_id: DeviceId,
53 queue_id: QueueId,
54 size: u64,
55}
56
57impl Buffer {
58 fn has_compatible_config(&self, config: &ContextConfiguration) -> bool {
60 config.device_id == self.device_id && self.size == config.buffer_size()
61 }
62}
63
64#[derive(Debug)]
66struct MappedBuffer {
67 buffer: Buffer,
68 data: NonNull<u8>,
69 len: u64,
70 image_size: Size2D<u32>,
71 image_format: ImageFormat,
72 is_opaque: bool,
73}
74
75unsafe impl Send for MappedBuffer {}
77
78impl MappedBuffer {
79 const fn slice(&'_ self) -> &'_ [u8] {
80 unsafe { std::slice::from_raw_parts(self.data.as_ptr(), self.len as usize) }
82 }
83
84 fn stride(&self) -> u32 {
85 (self.image_size.width * self.image_format.bytes_per_pixel() as u32)
86 .next_multiple_of(COPY_BYTES_PER_ROW_ALIGNMENT)
87 }
88}
89
90#[derive(Debug)]
91enum StagingBufferState {
92 Unassigned,
95 Available(Buffer),
97 Mapping(Buffer),
99 Mapped(MappedBuffer),
101}
102
103#[derive(Debug)]
105struct StagingBuffer {
106 global: Arc<Global>,
107 buffer_id: BufferId,
108 state: StagingBufferState,
109}
110
111unsafe impl Sync for StagingBuffer {}
114
115impl StagingBuffer {
116 fn new(global: Arc<Global>, buffer_id: BufferId) -> Self {
117 Self {
118 global,
119 buffer_id,
120 state: StagingBufferState::Unassigned,
121 }
122 }
123
124 const fn is_mapped(&self) -> bool {
125 matches!(self.state, StagingBufferState::Mapped(..))
126 }
127
128 fn is_available_and_has_compatible_config(&self, config: &ContextConfiguration) -> bool {
131 let StagingBufferState::Available(buffer) = &self.state else {
132 return false;
133 };
134 buffer.has_compatible_config(config)
135 }
136
137 const fn needs_assignment(&self) -> bool {
139 matches!(
140 self.state,
141 StagingBufferState::Unassigned | StagingBufferState::Available(_)
142 )
143 }
144
145 fn ensure_available(&mut self, config: &ContextConfiguration) -> Result<(), CreateBufferError> {
147 let recreate = match &self.state {
148 StagingBufferState::Unassigned => true,
149 StagingBufferState::Available(buffer) |
150 StagingBufferState::Mapping(buffer) |
151 StagingBufferState::Mapped(MappedBuffer { buffer, .. }) => {
152 if buffer.has_compatible_config(config) {
153 let _ = self.global.buffer_unmap(self.buffer_id);
154 false
155 } else {
156 self.global.buffer_drop(self.buffer_id);
157 true
158 }
159 },
160 };
161 if recreate {
162 let buffer_size = config.buffer_size();
163 let (_, error) = self.global.device_create_buffer(
164 config.device_id,
165 &BufferDescriptor {
166 label: None,
167 size: buffer_size,
168 usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
169 mapped_at_creation: false,
170 },
171 Some(self.buffer_id),
172 );
173 if let Some(error) = error {
174 return Err(error);
175 };
176 self.state = StagingBufferState::Available(Buffer {
177 device_id: config.device_id,
178 queue_id: config.queue_id,
179 size: buffer_size,
180 });
181 }
182 Ok(())
183 }
184
185 fn prepare_load_texture_command_buffer(
190 &mut self,
191 texture_id: TextureId,
192 encoder_id: CommandEncoderId,
193 command_buffer_id: CommandBufferId,
194 config: &ContextConfiguration,
195 ) -> Result<CommandBufferId, Box<dyn std::error::Error>> {
196 self.ensure_available(config)?;
197 let StagingBufferState::Available(buffer) = &self.state else {
198 unreachable!("Should be made available by `ensure_available`")
199 };
200 let device_id = buffer.device_id;
201 let command_descriptor = CommandEncoderDescriptor { label: None };
202 let (encoder_id, error) = self.global.device_create_command_encoder(
203 device_id,
204 &command_descriptor,
205 Some(encoder_id),
206 );
207 if let Some(error) = error {
208 return Err(error.into());
209 };
210 let buffer_info = TexelCopyBufferInfo {
211 buffer: self.buffer_id,
212 layout: TexelCopyBufferLayout {
213 offset: 0,
214 bytes_per_row: Some(config.stride()),
215 rows_per_image: None,
216 },
217 };
218 let texture_info = TexelCopyTextureInfo {
219 texture: texture_id,
220 mip_level: 0,
221 origin: Origin3d::ZERO,
222 aspect: TextureAspect::All,
223 };
224 let copy_size = Extent3d {
225 width: config.size.width,
226 height: config.size.height,
227 depth_or_array_layers: 1,
228 };
229 self.global.command_encoder_copy_texture_to_buffer(
230 encoder_id,
231 &texture_info,
232 &buffer_info,
233 ©_size,
234 )?;
235 let (command_buffer_id, error) = self.global.command_encoder_finish(
236 encoder_id,
237 &CommandBufferDescriptor::default(),
238 Some(command_buffer_id),
239 );
240 if let Some((_, error)) = error {
241 return Err(error.into());
242 };
243 Ok(command_buffer_id)
244 }
245
246 fn unmap(&mut self) {
248 match self.state {
249 StagingBufferState::Unassigned | StagingBufferState::Available(_) => {},
250 StagingBufferState::Mapping(buffer) |
251 StagingBufferState::Mapped(MappedBuffer { buffer, .. }) => {
252 let _ = self.global.buffer_unmap(self.buffer_id);
253 self.state = StagingBufferState::Available(buffer)
254 },
255 }
256 }
257
258 fn snapshot(&self) -> Option<Snapshot> {
260 let StagingBufferState::Mapped(mapped) = &self.state else {
261 return None;
262 };
263 let format = match mapped.image_format {
264 ImageFormat::RGBA8 => SnapshotPixelFormat::RGBA,
265 ImageFormat::BGRA8 => SnapshotPixelFormat::BGRA,
266 _ => unreachable!("GPUCanvasContext does not support other formats per spec"),
267 };
268 let alpha_mode = if mapped.is_opaque {
269 SnapshotAlphaMode::AsOpaque {
270 premultiplied: false,
271 }
272 } else {
273 SnapshotAlphaMode::Transparent {
274 premultiplied: true,
275 }
276 };
277 let padded_byte_width = mapped.stride();
278 let data = mapped.slice();
279 let bytes_per_pixel = mapped.image_format.bytes_per_pixel() as usize;
280 let mut result_unpadded =
281 Vec::<u8>::with_capacity(mapped.image_size.area() as usize * bytes_per_pixel);
282 for row in 0..mapped.image_size.height {
283 let start = (row * padded_byte_width).try_into().ok()?;
284 result_unpadded
285 .extend(&data[start..start + mapped.image_size.width as usize * bytes_per_pixel]);
286 }
287 let mut snapshot =
288 Snapshot::from_vec(mapped.image_size, format, alpha_mode, result_unpadded);
289 if mapped.is_opaque {
290 snapshot.transform(SnapshotAlphaMode::Opaque, snapshot.format())
291 }
292 Some(snapshot)
293 }
294}
295
296impl Drop for StagingBuffer {
297 fn drop(&mut self) {
298 match self.state {
299 StagingBufferState::Unassigned => {},
300 StagingBufferState::Available(_) |
301 StagingBufferState::Mapping(_) |
302 StagingBufferState::Mapped(_) => {
303 self.global.buffer_drop(self.buffer_id);
304 },
305 }
306 }
307}
308
309pub struct WebGpuExternalImages {
310 pub image_map: WebGpuExternalImageMap,
311 pub locked_ids: FxHashMap<WebGPUContextId, PresentationStagingBuffer>,
312}
313
314impl WebGpuExternalImages {
315 pub fn new(image_map: WebGpuExternalImageMap) -> Self {
316 Self {
317 image_map,
318 locked_ids: Default::default(),
319 }
320 }
321}
322
323impl WebRenderExternalImageApi for WebGpuExternalImages {
324 fn lock(&mut self, id: u64) -> (ExternalImageSource<'_>, Size2D<i32>) {
325 let id = WebGPUContextId(id);
326 let presentation = {
327 let mut webgpu_contexts = self.image_map.lock().unwrap();
328 webgpu_contexts
329 .get_mut(&id)
330 .and_then(|context_data| context_data.presentation.clone())
331 };
332 let Some(presentation) = presentation else {
333 return (ExternalImageSource::Invalid, Size2D::zero());
334 };
335 self.locked_ids.insert(id, presentation);
336 let presentation = self.locked_ids.get(&id).unwrap();
337 let StagingBufferState::Mapped(mapped_buffer) = &presentation.staging_buffer.state else {
338 unreachable!("Presentation staging buffer should be mapped")
339 };
340 let size = mapped_buffer.image_size;
341 (
342 ExternalImageSource::RawData(mapped_buffer.slice()),
343 size.cast().cast_unit(),
344 )
345 }
346
347 fn unlock(&mut self, id: u64) {
348 let id = WebGPUContextId(id);
349 let Some(presentation) = self.locked_ids.remove(&id) else {
350 return;
351 };
352 let mut webgpu_contexts = self.image_map.lock().unwrap();
353 if let Some(context_data) = webgpu_contexts.get_mut(&id) {
354 presentation.maybe_destroy(context_data);
356 } else {
357 drop(presentation);
360 }
361 }
362}
363
364#[derive(Clone)]
368pub struct PresentationStagingBuffer {
369 epoch: Epoch,
370 staging_buffer: Arc<StagingBuffer>,
371}
372
373impl PresentationStagingBuffer {
374 fn new(epoch: Epoch, staging_buffer: StagingBuffer) -> Self {
375 Self {
376 epoch,
377 staging_buffer: Arc::new(staging_buffer),
378 }
379 }
380
381 fn maybe_destroy(self, context_data: &mut ContextData) {
384 if let Some(mut staging_buffer) = Arc::into_inner(self.staging_buffer) {
385 staging_buffer.unmap();
386 context_data.return_staging_buffer(staging_buffer);
387 }
388 }
389}
390
391pub struct ContextData {
393 image_key: Option<ImageKey>,
395 size: DeviceIntSize,
397 inactive_staging_buffers: ArrayVec<StagingBuffer, PRESENTATION_BUFFER_COUNT>,
402 presentation: Option<PresentationStagingBuffer>,
406 next_epoch: Epoch,
408}
409
410impl ContextData {
411 fn new(
412 global: &Arc<Global>,
413 buffer_ids: ArrayVec<id::BufferId, PRESENTATION_BUFFER_COUNT>,
414 size: DeviceIntSize,
415 ) -> Self {
416 Self {
417 image_key: None,
418 size,
419 inactive_staging_buffers: buffer_ids
420 .iter()
421 .map(|buffer_id| StagingBuffer::new(global.clone(), *buffer_id))
422 .collect(),
423 presentation: None,
424 next_epoch: Epoch(1),
425 }
426 }
427
428 fn get_or_make_available_buffer(
430 &'_ mut self,
431 config: &ContextConfiguration,
432 ) -> Option<StagingBuffer> {
433 self.inactive_staging_buffers
434 .iter()
435 .position(|staging_buffer| {
437 staging_buffer.is_available_and_has_compatible_config(config)
438 })
439 .or_else(|| {
441 self.inactive_staging_buffers
442 .iter()
443 .position(|staging_buffer| staging_buffer.needs_assignment())
444 })
445 .or_else(|| {
447 if self.inactive_staging_buffers.is_empty() {
448 None
449 } else {
450 Some(0)
451 }
452 })
453 .and_then(|index| {
454 let mut staging_buffer = self.inactive_staging_buffers.remove(index);
455 if staging_buffer.ensure_available(config).is_ok() {
456 Some(staging_buffer)
457 } else {
458 self.inactive_staging_buffers.push(staging_buffer);
460 None
461 }
462 })
463 }
464
465 fn destroy(
468 mut self,
469 script_sender: &GenericSender<WebGPUMsg>,
470 paint_api: &CrossProcessPaintApi,
471 ) {
472 for staging_buffer in self.inactive_staging_buffers {
474 if let Err(error) = script_sender.send(WebGPUMsg::FreeBuffer(staging_buffer.buffer_id))
475 {
476 warn!(
477 "Unable to send FreeBuffer({:?}) ({error})",
478 staging_buffer.buffer_id
479 );
480 };
481 }
482 if let Some(image_key) = self.image_key.take() {
483 paint_api.delete_image(image_key);
484 }
485 }
486
487 fn next_epoch(&mut self) -> Epoch {
489 let epoch = self.next_epoch;
490 self.next_epoch.next();
491 epoch
492 }
493
494 fn replace_presentation(&mut self, presentation: PresentationStagingBuffer) {
497 let stale_presentation = if presentation.epoch >=
498 self.presentation
499 .as_ref()
500 .map(|p| p.epoch)
501 .unwrap_or_default()
502 {
503 self.presentation.replace(presentation)
504 } else {
505 Some(presentation)
506 };
507 if let Some(stale_presentation) = stale_presentation {
508 stale_presentation.maybe_destroy(self);
509 }
510 }
511
512 fn clear_presentation(&mut self) {
513 if let Some(stale_presentation) = self.presentation.take() {
514 stale_presentation.maybe_destroy(self);
515 }
516 }
517
518 fn return_staging_buffer(&mut self, staging_buffer: StagingBuffer) {
519 self.inactive_staging_buffers.push(staging_buffer)
520 }
521}
522
523impl crate::WGPU {
524 pub(crate) fn create_context(
525 &self,
526 context_id: WebGPUContextId,
527 size: DeviceIntSize,
528 buffer_ids: ArrayVec<id::BufferId, PRESENTATION_BUFFER_COUNT>,
529 ) {
530 let context_data = ContextData::new(&self.global, buffer_ids, size);
531 assert!(
532 self.wgpu_image_map
533 .lock()
534 .unwrap()
535 .insert(context_id, context_data)
536 .is_none(),
537 "Context should be created only once!"
538 );
539 }
540
541 pub(crate) fn set_image_key(&self, context_id: WebGPUContextId, image_key: ImageKey) {
542 let mut webgpu_contexts = self.wgpu_image_map.lock().unwrap();
543 let context_data = webgpu_contexts.get_mut(&context_id).unwrap();
544
545 if let Some(old_image_key) = context_data.image_key.replace(image_key) {
546 self.paint_api.delete_image(old_image_key);
547 }
548
549 self.paint_api.add_image(
550 image_key,
551 ImageDescriptor {
552 format: ImageFormat::BGRA8,
553 size: context_data.size,
554 stride: None,
555 offset: 0,
556 flags: ImageDescriptorFlags::empty(),
557 },
558 SerializableImageData::External(image_data(context_id)),
559 false,
560 );
561 }
562
563 pub(crate) fn get_image(
564 &self,
565 context_id: WebGPUContextId,
566 pending_texture: Option<PendingTexture>,
567 sender: GenericSender<SharedSnapshot>,
568 ) {
569 let mut webgpu_contexts = self.wgpu_image_map.lock().unwrap();
570 let context_data = webgpu_contexts.get_mut(&context_id).unwrap();
571 if let Some(PendingTexture {
572 texture_id,
573 encoder_id,
574 command_buffer_id,
575 configuration,
576 }) = pending_texture
577 {
578 let Some(staging_buffer) = context_data.get_or_make_available_buffer(&configuration)
579 else {
580 warn!("Failure obtaining available staging buffer");
581 sender
582 .send(SharedSnapshot::cleared(configuration.size))
583 .unwrap();
584 return;
585 };
586
587 let epoch = context_data.next_epoch();
588 let wgpu_image_map = self.wgpu_image_map.clone();
589 let sender = sender;
590 drop(webgpu_contexts);
591 self.texture_download(
592 texture_id,
593 encoder_id,
594 command_buffer_id,
595 staging_buffer,
596 configuration,
597 move |staging_buffer| {
598 let mut webgpu_contexts = wgpu_image_map.lock().unwrap();
599 let context_data = webgpu_contexts.get_mut(&context_id).unwrap();
600 sender
601 .send(
602 staging_buffer
603 .snapshot()
604 .as_ref()
605 .map(Snapshot::to_shared)
606 .unwrap_or_else(|| SharedSnapshot::cleared(configuration.size)),
607 )
608 .unwrap();
609 if staging_buffer.is_mapped() {
610 context_data.replace_presentation(PresentationStagingBuffer::new(
611 epoch,
612 staging_buffer,
613 ));
614 } else {
615 context_data.return_staging_buffer(staging_buffer);
617 }
618 },
619 );
620 } else {
621 sender
622 .send(
623 context_data
624 .presentation
625 .as_ref()
626 .and_then(|presentation_staging_buffer| {
627 presentation_staging_buffer.staging_buffer.snapshot()
628 })
629 .unwrap_or_else(Snapshot::empty)
630 .to_shared(),
631 )
632 .unwrap();
633 }
634 }
635
636 pub(crate) fn present(
639 &self,
640 context_id: WebGPUContextId,
641 pending_texture: Option<PendingTexture>,
642 size: Size2D<u32>,
643 canvas_epoch: Epoch,
644 ) {
645 let mut webgpu_contexts = self.wgpu_image_map.lock().unwrap();
646 let context_data = webgpu_contexts.get_mut(&context_id).unwrap();
647
648 let Some(image_key) = context_data.image_key else {
649 return;
650 };
651
652 let Some(PendingTexture {
653 texture_id,
654 encoder_id,
655 command_buffer_id,
656 configuration,
657 }) = pending_texture
658 else {
659 context_data.clear_presentation();
660 self.paint_api.update_image(
661 image_key,
662 ImageDescriptor {
663 format: ImageFormat::BGRA8,
664 size: size.cast_unit().cast(),
665 stride: None,
666 offset: 0,
667 flags: ImageDescriptorFlags::empty(),
668 },
669 SerializableImageData::External(image_data(context_id)),
670 Some(canvas_epoch),
671 );
672 return;
673 };
674 let Some(staging_buffer) = context_data.get_or_make_available_buffer(&configuration) else {
675 warn!("Failure obtaining available staging buffer");
676 context_data.clear_presentation();
677 self.paint_api.update_image(
678 image_key,
679 configuration.into(),
680 SerializableImageData::External(image_data(context_id)),
681 Some(canvas_epoch),
682 );
683 return;
684 };
685 let epoch = context_data.next_epoch();
686 let wgpu_image_map = self.wgpu_image_map.clone();
687 let paint_api = self.paint_api.clone();
688 drop(webgpu_contexts);
689 self.texture_download(
690 texture_id,
691 encoder_id,
692 command_buffer_id,
693 staging_buffer,
694 configuration,
695 move |staging_buffer| {
696 let mut webgpu_contexts = wgpu_image_map.lock().unwrap();
697 let context_data = webgpu_contexts.get_mut(&context_id).unwrap();
698 if staging_buffer.is_mapped() {
699 context_data.replace_presentation(PresentationStagingBuffer::new(
700 epoch,
701 staging_buffer,
702 ));
703 } else {
704 context_data.return_staging_buffer(staging_buffer);
705 context_data.clear_presentation();
706 }
707 paint_api.update_image(
709 image_key,
710 configuration.into(),
711 SerializableImageData::External(image_data(context_id)),
712 Some(canvas_epoch),
713 );
714 },
715 );
716 }
717
718 fn texture_download(
725 &self,
726 texture_id: TextureId,
727 encoder_id: CommandEncoderId,
728 command_buffer_id: CommandBufferId,
729 mut staging_buffer: StagingBuffer,
730 config: ContextConfiguration,
731 callback: impl FnOnce(StagingBuffer) + Send + 'static,
732 ) {
733 let Ok(command_buffer_id) = staging_buffer.prepare_load_texture_command_buffer(
734 texture_id,
735 encoder_id,
736 command_buffer_id,
737 &config,
738 ) else {
739 return callback(staging_buffer);
740 };
741 let StagingBufferState::Available(buffer) = &staging_buffer.state else {
742 unreachable!("`prepare_load_texture_command_buffer` should make buffer available")
743 };
744 let buffer_id = staging_buffer.buffer_id;
745 let buffer_size = buffer.size;
746 {
747 let _guard = self.poller.lock();
748 let result = self
749 .global
750 .queue_submit(buffer.queue_id, &[command_buffer_id]);
751 if result.is_err() {
752 return callback(staging_buffer);
753 }
754 }
755 staging_buffer.state = match staging_buffer.state {
756 StagingBufferState::Available(buffer) => StagingBufferState::Mapping(buffer),
757 _ => unreachable!("`prepare_load_texture_command_buffer` should make buffer available"),
758 };
759 let map_callback = {
760 let token = self.poller.token();
761 Box::new(move |result: Result<(), BufferAccessError>| {
762 drop(token);
763 staging_buffer.state = match staging_buffer.state {
764 StagingBufferState::Mapping(buffer) => {
765 if let Ok((data, len)) = result.and_then(|_| {
766 staging_buffer.global.buffer_get_mapped_range(
767 staging_buffer.buffer_id,
768 0,
769 Some(buffer.size),
770 )
771 }) {
772 StagingBufferState::Mapped(MappedBuffer {
773 buffer,
774 data,
775 len,
776 image_size: config.size,
777 image_format: config.format,
778 is_opaque: config.is_opaque,
779 })
780 } else {
781 StagingBufferState::Available(buffer)
782 }
783 },
784 _ => {
785 unreachable!("Mapping buffer should have StagingBufferState::Mapping state")
786 },
787 };
788 callback(staging_buffer);
789 })
790 };
791 let map_op = BufferMapOperation {
792 host: HostMap::Read,
793 callback: Some(map_callback),
794 };
795 let _ = self
797 .global
798 .buffer_map_async(buffer_id, 0, Some(buffer_size), map_op);
799 self.poller.wake();
800 }
801
802 pub(crate) fn destroy_context(&mut self, context_id: WebGPUContextId) {
803 self.wgpu_image_map
804 .lock()
805 .unwrap()
806 .remove(&context_id)
807 .unwrap()
808 .destroy(&self.script_sender, &self.paint_api);
809 }
810}