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