1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::num::NonZero;
4#[cfg(feature = "winit-surface")]
5use std::panic::{AssertUnwindSafe, catch_unwind};
6use std::sync::Arc;
7
8use repose_core::color::{ChromaSiting, ColorInfo, PixelFormat};
9use repose_core::request_frame;
10use repose_core::{
11 Brush, FontStyle, GlyphRasterConfig, PresentModePref, RenderBackend, Scene, SceneNode,
12 StrokeCap, Transform, Vec2,
13};
14use wgpu::Instance;
15
16mod slug;
17
18mod commands;
19pub use commands::apply_render_commands;
20
21pub mod offscreen;
22
23mod callback;
24pub use callback::{Callback, CallbackResources, ScreenDescriptor, WgpuCallback};
25
26mod depth_composite;
27pub use depth_composite::DepthComposite;
28
29#[derive(Clone)]
30struct UploadRing {
31 buf: wgpu::Buffer,
32 cap: u64,
33 head: u64,
34 usage: wgpu::BufferUsages,
35}
36
37impl UploadRing {
38 fn new(device: &wgpu::Device, label: &str, cap: u64, usage: wgpu::BufferUsages) -> Self {
39 let buf = device.create_buffer(&wgpu::BufferDescriptor {
40 label: Some(label),
41 size: cap,
42 usage,
43 mapped_at_creation: false,
44 });
45 Self {
46 buf,
47 cap,
48 head: 0,
49 usage,
50 }
51 }
52
53 fn reset(&mut self) {
54 self.head = 0;
55 }
56
57 fn grow_to_fit(&mut self, device: &wgpu::Device, needed: u64) {
58 let start = (self.head + 3) & !3;
59 let aligned_needed = (needed + 3) & !3;
60 if start + needed <= self.cap {
62 return;
63 }
64 let required = start + needed;
65 let mut new_cap = required.next_power_of_two().max(self.cap * 2).max(256);
66 new_cap = (new_cap + 3) & !3;
67 if new_cap < aligned_needed {
68 new_cap = aligned_needed.next_power_of_two();
69 }
70 self.buf = device.create_buffer(&wgpu::BufferDescriptor {
71 label: Some("upload ring (grown)"),
72 size: new_cap,
73 usage: self.usage,
74 mapped_at_creation: false,
75 });
76 self.cap = new_cap;
77 if start + needed > self.cap {
78 self.head = 0;
79 }
80 }
81
82 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> (u64, u64) {
83 let len = bytes.len() as u64;
84 let start = (self.head + 3) & !3; let end = start + len;
86 if end > self.cap {
87 log::error!(
89 "UploadRing overflow: start={start} len={len} cap={} - growing",
90 self.cap
91 );
92 if len > self.cap {
93 return (0, 0);
96 }
97 let wrapped_start = 0;
99 let wrapped_end = len;
100 if wrapped_end <= self.cap {
101 queue.write_buffer(&self.buf, wrapped_start, bytes);
102 self.head = wrapped_end;
103 return (wrapped_start, len);
104 }
105 return (0, 0);
106 }
107 queue.write_buffer(&self.buf, start, bytes);
108 self.head = end;
109 (start, len)
110 }
111}
112
113struct InstancedPipe<I: bytemuck::Pod> {
114 ring: UploadRing,
115 _marker: std::marker::PhantomData<I>,
116}
117
118impl<I: bytemuck::Pod> InstancedPipe<I> {
119 fn new(ring: UploadRing) -> Self {
120 Self {
121 ring,
122 _marker: std::marker::PhantomData,
123 }
124 }
125
126 fn upload(
127 &mut self,
128 device: &wgpu::Device,
129 queue: &wgpu::Queue,
130 data: &[I],
131 ) -> Option<(u64, u32)> {
132 if data.is_empty() {
133 return None;
134 }
135 let bytes = bytemuck::cast_slice(data);
136 self.ring.grow_to_fit(device, bytes.len() as u64);
137 let (off, wrote) = self.ring.alloc_write(queue, bytes);
138 if wrote as usize != bytes.len() {
139 log::error!(
140 "upload skipped: batch {}B exceeds ring {}B",
141 bytes.len(),
142 self.ring.cap
143 );
144 return None;
145 }
146 Some((off, data.len() as u32))
147 }
148
149 fn reset(&mut self) {
150 self.ring.reset();
151 }
152}
153
154#[repr(C)]
155#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
156struct Globals {
157 ndc_to_px: [f32; 2],
158 _pad: [f32; 2],
159}
160
161fn make_globals(target_w: f32, target_h: f32) -> Globals {
162 Globals {
163 ndc_to_px: [target_w * 0.5, target_h * 0.5],
164 _pad: [0.0, 0.0],
165 }
166}
167
168pub struct WgpuSceneRenderer {
169 pub device: wgpu::Device,
170 pub queue: wgpu::Queue,
171 pub output_format: wgpu::TextureFormat,
172 pub output_width: u32,
173 pub output_height: u32,
174 pub pixels_per_point: f32,
176
177 surface_pipes: Pipelines,
180 layer_pipes: Pipelines,
181
182 rects: InstancedPipe<RectInstance>,
184 borders: InstancedPipe<BorderInstance>,
185 ellipses: InstancedPipe<EllipseInstance>,
186 ellipse_borders: InstancedPipe<EllipseBorderInstance>,
187 arcs: InstancedPipe<ArcInstance>,
188 glyph_mask: InstancedPipe<GlyphInstance>,
189 glyph_color: InstancedPipe<GlyphInstance>,
190
191 image_bind_layout_rgba: wgpu::BindGroupLayout,
193 image_bind_layout_nv12: wgpu::BindGroupLayout,
194 image_sampler: wgpu::Sampler,
195 layer_sampler: wgpu::Sampler,
196 layer_sampler_linear: wgpu::Sampler,
197
198 blur_ring: UploadRing,
200
201 text_bind_layout: wgpu::BindGroupLayout,
202
203 clip_ring: UploadRing,
205
206 projective_ring: UploadRing,
209
210 blend_ring: UploadRing,
212
213 slug_enabled: bool,
215 slug_ring: UploadRing,
216 slug_cache: slug::GlyphSlugCache,
217
218 nv12: InstancedPipe<Nv12Instance>,
220
221 mesh_verts: UploadRing,
223 mesh_indices: UploadRing,
224 mesh_uniform_buf: wgpu::Buffer,
225 mesh_bind_layout: wgpu::BindGroupLayout,
226 mesh_bind: wgpu::BindGroup,
227 mesh_uniform_head: u64,
228 mesh_clip_stack: Vec<(u64, u32, u64, u32, u64, bool)>,
232
233 flatten_layer_ids: Vec<u32>,
237
238 blend_snapshots: std::collections::HashMap<u32, BlendSnapshot>,
242 blend_copies: Vec<(u32, PassTarget, repose_core::Rect)>,
246
247 msaa_samples: u32,
248
249 depth_stencil_tex: wgpu::Texture,
251 depth_stencil_view: wgpu::TextureView,
252
253 msaa_tex: Option<wgpu::Texture>,
255 msaa_view: Option<wgpu::TextureView>,
256
257 globals_buf: wgpu::Buffer,
258 globals_bind: wgpu::BindGroup,
259
260 atlas_mask: AtlasA8,
262 atlas_color: AtlasRGBA,
263
264 next_image_handle: u64,
266 images: HashMap<u64, ImageTex>,
267 retained: HashMap<u64, RetainedImage>,
268
269 next_coverage_handle: u64,
272 coverages: HashMap<u64, CoverageTex>,
273
274 frame_index: u64,
276 image_bytes_total: u64,
277 image_evict_after_frames: u64,
278 image_budget_bytes: u64,
279
280 layer_pool: HashMap<u32, LayerTarget>,
283
284 working_space: bool,
288 ws_tex: Option<wgpu::Texture>,
289 ws_view: Option<wgpu::TextureView>,
290 ws_bind: Option<wgpu::BindGroup>,
291 display_pipeline: Option<wgpu::RenderPipeline>,
292 display_layout: Option<wgpu::BindGroupLayout>,
293
294 pub callback_resources: CallbackResources,
295}
296
297pub struct WgpuSurfaceBackend {
298 pub surface: Option<wgpu::Surface<'static>>,
299 pub surface_config: Option<wgpu::SurfaceConfiguration>,
300 pub renderer: WgpuSceneRenderer,
301}
302
303impl std::ops::Deref for WgpuSurfaceBackend {
304 type Target = WgpuSceneRenderer;
305 fn deref(&self) -> &Self::Target {
306 &self.renderer
307 }
308}
309impl std::ops::DerefMut for WgpuSurfaceBackend {
310 fn deref_mut(&mut self) -> &mut Self::Target {
311 &mut self.renderer
312 }
313}
314
315#[cfg(feature = "winit-surface")]
316pub type WgpuBackend = WgpuSurfaceBackend;
317
318impl Drop for WgpuSceneRenderer {
319 fn drop(&mut self) {
320 let _ = self.device.poll(wgpu::PollType::Poll);
321 #[cfg(not(target_arch = "wasm32"))]
322 {
323 let _ = self.device.poll(wgpu::PollType::Wait {
324 submission_index: None,
325 timeout: Some(std::time::Duration::from_millis(100)),
326 });
327 }
328 }
329}
330
331#[derive(Clone)]
332struct LayerTarget {
333 texture: wgpu::Texture,
334 view: wgpu::TextureView,
335 bind: wgpu::BindGroup,
336 bind_linear: wgpu::BindGroup,
337 depth_stencil_view: wgpu::TextureView,
338 width: u32,
339 height: u32,
340 rect_px: (f32, f32, f32, f32),
341}
342
343#[derive(Clone)]
347struct BlendSnapshot {
348 texture: wgpu::Texture,
349 bind: wgpu::BindGroup,
350 width: u32,
351 height: u32,
352}
353
354#[derive(Clone, Copy)]
356enum PassTarget {
357 Surface,
358 Layer(u32),
359}
360
361struct Pipelines {
366 rects: wgpu::RenderPipeline,
367 borders: wgpu::RenderPipeline,
368 ellipses: wgpu::RenderPipeline,
369 ellipse_borders: wgpu::RenderPipeline,
370 arcs: wgpu::RenderPipeline,
371 text_mask: wgpu::RenderPipeline,
372 text_color: wgpu::RenderPipeline,
373 image_rgba: wgpu::RenderPipeline,
374 coverage: wgpu::RenderPipeline,
378 image_nv12: wgpu::RenderPipeline,
379 blur: wgpu::RenderPipeline,
380 blur_content: wgpu::RenderPipeline,
381 clip_bin: wgpu::RenderPipeline,
382 clip_dec: wgpu::RenderPipeline,
383 slug: Option<wgpu::RenderPipeline>,
384 mesh: wgpu::RenderPipeline,
388 mesh_overlay: wgpu::RenderPipeline,
391 mesh_add: wgpu::RenderPipeline,
396 mesh_multiply: wgpu::RenderPipeline,
397 mesh_screen: wgpu::RenderPipeline,
398 mesh_darken: wgpu::RenderPipeline,
399 mesh_lighten: wgpu::RenderPipeline,
400 blend_layer: wgpu::RenderPipeline,
404 mesh_clip_inc: wgpu::RenderPipeline,
406 mesh_clip_dec: wgpu::RenderPipeline,
408 projective_layer: wgpu::RenderPipeline,
412}
413
414impl Pipelines {
415 fn create(
416 device: &wgpu::Device,
417 format: wgpu::TextureFormat,
418 sample_count: u32,
419 globals_layout: &wgpu::BindGroupLayout,
420 text_bind_layout: &wgpu::BindGroupLayout,
421 image_bind_layout_nv12: &wgpu::BindGroupLayout,
422 clip_pipeline_layout: &wgpu::PipelineLayout,
423 stencil_for_content: &wgpu::DepthStencilState,
424 stencil_for_clip_inc: &wgpu::DepthStencilState,
425 stencil_for_clip_dec: &wgpu::DepthStencilState,
426 clip_color_target: &wgpu::ColorTargetState,
427 clip_vertex_layout: &wgpu::VertexBufferLayout,
428 mesh_bind_layout: &wgpu::BindGroupLayout,
429 ) -> Self {
430 let msaa_state = wgpu::MultisampleState {
431 count: sample_count,
432 mask: !0,
433 alpha_to_coverage_enabled: false,
434 };
435
436 macro_rules! make_content_pipeline {
437 ($name:ident, $shader:literal, $inst_type:ty, $attrs:expr) => {
438 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
439 label: Some(concat!($shader, ".wgsl")),
440 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(concat!(
441 "shaders/", $shader, ".wgsl"
442 )))),
443 });
444 let pipeline_layout =
445 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
446 label: Some(concat!($shader, " pipeline layout")),
447 bind_group_layouts: &[Some(globals_layout)],
448 immediate_size: 0,
449 });
450 let $name = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
451 label: Some(concat!($shader, " pipeline")),
452 layout: Some(&pipeline_layout),
453 vertex: wgpu::VertexState {
454 module: &shader_module,
455 entry_point: Some("vs_main"),
456 buffers: &[Some(wgpu::VertexBufferLayout {
457 array_stride: std::mem::size_of::<$inst_type>() as u64,
458 step_mode: wgpu::VertexStepMode::Instance,
459 attributes: $attrs,
460 })],
461 compilation_options: wgpu::PipelineCompilationOptions::default(),
462 },
463 fragment: Some(wgpu::FragmentState {
464 module: &shader_module,
465 entry_point: Some("fs_main"),
466 targets: &[Some(wgpu::ColorTargetState {
467 format,
468 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
469 write_mask: wgpu::ColorWrites::ALL,
470 })],
471 compilation_options: wgpu::PipelineCompilationOptions::default(),
472 }),
473 primitive: wgpu::PrimitiveState::default(),
474 depth_stencil: Some(stencil_for_content.clone()),
475 multisample: msaa_state,
476 multiview_mask: None,
477 cache: None,
478 });
479 };
480 }
481
482 let rect_attrs: &[wgpu::VertexAttribute] = &[
483 wgpu::VertexAttribute {
484 shader_location: 0,
485 offset: 0,
486 format: wgpu::VertexFormat::Float32x4,
487 },
488 wgpu::VertexAttribute {
489 shader_location: 1,
490 offset: 16,
491 format: wgpu::VertexFormat::Float32x4,
492 },
493 wgpu::VertexAttribute {
494 shader_location: 2,
495 offset: 32,
496 format: wgpu::VertexFormat::Uint32,
497 },
498 wgpu::VertexAttribute {
499 shader_location: 3,
500 offset: 36,
501 format: wgpu::VertexFormat::Uint32,
502 },
503 wgpu::VertexAttribute {
504 shader_location: 4,
505 offset: 48,
506 format: wgpu::VertexFormat::Float32x4,
507 },
508 wgpu::VertexAttribute {
509 shader_location: 5,
510 offset: 64,
511 format: wgpu::VertexFormat::Float32x4,
512 },
513 wgpu::VertexAttribute {
514 shader_location: 6,
515 offset: 80,
516 format: wgpu::VertexFormat::Float32x2,
517 },
518 wgpu::VertexAttribute {
519 shader_location: 7,
520 offset: 88,
521 format: wgpu::VertexFormat::Float32x2,
522 },
523 wgpu::VertexAttribute {
524 shader_location: 8,
525 offset: 96,
526 format: wgpu::VertexFormat::Uint32,
527 },
528 wgpu::VertexAttribute {
529 shader_location: 9,
530 offset: 112,
531 format: wgpu::VertexFormat::Float32x4,
532 },
533 ];
534 let border_attrs: &[wgpu::VertexAttribute] = &[
535 wgpu::VertexAttribute {
536 shader_location: 0,
537 offset: 0,
538 format: wgpu::VertexFormat::Float32x4,
539 },
540 wgpu::VertexAttribute {
541 shader_location: 1,
542 offset: 16,
543 format: wgpu::VertexFormat::Float32x4,
544 },
545 wgpu::VertexAttribute {
546 shader_location: 2,
547 offset: 32,
548 format: wgpu::VertexFormat::Float32,
549 },
550 wgpu::VertexAttribute {
551 shader_location: 3,
552 offset: 36,
553 format: wgpu::VertexFormat::Uint32,
554 },
555 wgpu::VertexAttribute {
556 shader_location: 4,
557 offset: 48,
558 format: wgpu::VertexFormat::Uint32,
559 },
560 wgpu::VertexAttribute {
561 shader_location: 5,
562 offset: 52,
563 format: wgpu::VertexFormat::Float32x4,
564 },
565 wgpu::VertexAttribute {
566 shader_location: 6,
567 offset: 68,
568 format: wgpu::VertexFormat::Float32x4,
569 },
570 wgpu::VertexAttribute {
571 shader_location: 7,
572 offset: 84,
573 format: wgpu::VertexFormat::Float32x2,
574 },
575 wgpu::VertexAttribute {
576 shader_location: 8,
577 offset: 92,
578 format: wgpu::VertexFormat::Float32x2,
579 },
580 wgpu::VertexAttribute {
581 shader_location: 9,
582 offset: 100,
583 format: wgpu::VertexFormat::Uint32,
584 },
585 wgpu::VertexAttribute {
586 shader_location: 10,
587 offset: 116,
588 format: wgpu::VertexFormat::Float32x4,
589 },
590 ];
591 let ellipse_attrs: &[wgpu::VertexAttribute] = &[
592 wgpu::VertexAttribute {
593 shader_location: 0,
594 offset: 0,
595 format: wgpu::VertexFormat::Float32x4,
596 },
597 wgpu::VertexAttribute {
598 shader_location: 1,
599 offset: 16,
600 format: wgpu::VertexFormat::Uint32,
601 },
602 wgpu::VertexAttribute {
603 shader_location: 2,
604 offset: 20,
605 format: wgpu::VertexFormat::Uint32,
606 },
607 wgpu::VertexAttribute {
608 shader_location: 3,
609 offset: 32,
610 format: wgpu::VertexFormat::Float32x4,
611 },
612 wgpu::VertexAttribute {
613 shader_location: 4,
614 offset: 48,
615 format: wgpu::VertexFormat::Float32x4,
616 },
617 wgpu::VertexAttribute {
618 shader_location: 5,
619 offset: 64,
620 format: wgpu::VertexFormat::Float32x2,
621 },
622 wgpu::VertexAttribute {
623 shader_location: 6,
624 offset: 72,
625 format: wgpu::VertexFormat::Float32x2,
626 },
627 wgpu::VertexAttribute {
628 shader_location: 7,
629 offset: 80,
630 format: wgpu::VertexFormat::Uint32,
631 },
632 wgpu::VertexAttribute {
633 shader_location: 8,
634 offset: 96,
635 format: wgpu::VertexFormat::Float32x4,
636 },
637 ];
638 let ellipse_border_attrs: &[wgpu::VertexAttribute] = &[
639 wgpu::VertexAttribute {
640 shader_location: 0,
641 offset: 0,
642 format: wgpu::VertexFormat::Float32x4,
643 },
644 wgpu::VertexAttribute {
645 shader_location: 1,
646 offset: 16,
647 format: wgpu::VertexFormat::Float32,
648 },
649 wgpu::VertexAttribute {
650 shader_location: 2,
651 offset: 20,
652 format: wgpu::VertexFormat::Float32,
653 },
654 wgpu::VertexAttribute {
655 shader_location: 3,
656 offset: 24,
657 format: wgpu::VertexFormat::Uint32,
658 },
659 wgpu::VertexAttribute {
660 shader_location: 4,
661 offset: 28,
662 format: wgpu::VertexFormat::Uint32,
663 },
664 wgpu::VertexAttribute {
665 shader_location: 5,
666 offset: 32,
667 format: wgpu::VertexFormat::Float32x4,
668 },
669 wgpu::VertexAttribute {
670 shader_location: 6,
671 offset: 48,
672 format: wgpu::VertexFormat::Float32x4,
673 },
674 wgpu::VertexAttribute {
675 shader_location: 7,
676 offset: 64,
677 format: wgpu::VertexFormat::Float32x2,
678 },
679 wgpu::VertexAttribute {
680 shader_location: 8,
681 offset: 72,
682 format: wgpu::VertexFormat::Float32x2,
683 },
684 wgpu::VertexAttribute {
685 shader_location: 9,
686 offset: 80,
687 format: wgpu::VertexFormat::Uint32,
688 },
689 wgpu::VertexAttribute {
690 shader_location: 10,
691 offset: 96,
692 format: wgpu::VertexFormat::Float32x4,
693 },
694 ];
695
696 make_content_pipeline!(rects, "rect", RectInstance, rect_attrs);
697 make_content_pipeline!(borders, "border", BorderInstance, border_attrs);
698 make_content_pipeline!(ellipses, "ellipse", EllipseInstance, ellipse_attrs);
699 make_content_pipeline!(
700 ellipse_borders,
701 "ellipse_border",
702 EllipseBorderInstance,
703 ellipse_border_attrs
704 );
705
706 let arc_attrs: &[wgpu::VertexAttribute] = &[
707 wgpu::VertexAttribute {
708 shader_location: 0,
709 offset: 0,
710 format: wgpu::VertexFormat::Float32x4,
711 },
712 wgpu::VertexAttribute {
713 shader_location: 1,
714 offset: 16,
715 format: wgpu::VertexFormat::Float32,
716 },
717 wgpu::VertexAttribute {
718 shader_location: 2,
719 offset: 20,
720 format: wgpu::VertexFormat::Float32,
721 },
722 wgpu::VertexAttribute {
723 shader_location: 3,
724 offset: 24,
725 format: wgpu::VertexFormat::Float32,
726 },
727 wgpu::VertexAttribute {
728 shader_location: 4,
729 offset: 28,
730 format: wgpu::VertexFormat::Float32,
731 },
732 wgpu::VertexAttribute {
733 shader_location: 5,
734 offset: 32,
735 format: wgpu::VertexFormat::Uint32,
736 },
737 wgpu::VertexAttribute {
738 shader_location: 6,
739 offset: 36,
740 format: wgpu::VertexFormat::Uint32,
741 },
742 wgpu::VertexAttribute {
743 shader_location: 7,
744 offset: 48,
745 format: wgpu::VertexFormat::Float32x4,
746 },
747 wgpu::VertexAttribute {
748 shader_location: 8,
749 offset: 64,
750 format: wgpu::VertexFormat::Float32x4,
751 },
752 wgpu::VertexAttribute {
753 shader_location: 9,
754 offset: 80,
755 format: wgpu::VertexFormat::Float32x2,
756 },
757 wgpu::VertexAttribute {
758 shader_location: 10,
759 offset: 88,
760 format: wgpu::VertexFormat::Float32x2,
761 },
762 wgpu::VertexAttribute {
763 shader_location: 11,
764 offset: 96,
765 format: wgpu::VertexFormat::Uint32,
766 },
767 wgpu::VertexAttribute {
768 shader_location: 12,
769 offset: 100,
770 format: wgpu::VertexFormat::Float32,
771 },
772 wgpu::VertexAttribute {
773 shader_location: 13,
774 offset: 112,
775 format: wgpu::VertexFormat::Float32x4,
776 },
777 ];
778
779 make_content_pipeline!(arcs, "arc", ArcInstance, arc_attrs);
780
781 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
783 label: Some("text.wgsl"),
784 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
785 });
786 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
788 label: Some("text_color.wgsl"),
789 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
790 "shaders/text_color.wgsl"
791 ))),
792 });
793 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
794 label: Some("text pipeline layout"),
795 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
796 immediate_size: 0,
797 });
798 let glyph_vertex = wgpu::VertexBufferLayout {
799 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
800 step_mode: wgpu::VertexStepMode::Instance,
801 attributes: &[
802 wgpu::VertexAttribute {
803 shader_location: 0,
804 offset: 0,
805 format: wgpu::VertexFormat::Float32x4,
806 },
807 wgpu::VertexAttribute {
808 shader_location: 1,
809 offset: 16,
810 format: wgpu::VertexFormat::Float32x4,
811 },
812 wgpu::VertexAttribute {
813 shader_location: 2,
814 offset: 32,
815 format: wgpu::VertexFormat::Float32x4,
816 },
817 wgpu::VertexAttribute {
818 shader_location: 3,
819 offset: 48,
820 format: wgpu::VertexFormat::Float32x4,
821 },
822 ],
823 };
824 let text_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
825 label: Some("text pipeline (mask)"),
826 layout: Some(&text_pipeline_layout),
827 vertex: wgpu::VertexState {
828 module: &text_mask_shader,
829 entry_point: Some("vs_main"),
830 buffers: &[Some(glyph_vertex.clone())],
831 compilation_options: wgpu::PipelineCompilationOptions::default(),
832 },
833 fragment: Some(wgpu::FragmentState {
834 module: &text_mask_shader,
835 entry_point: Some("fs_main"),
836 targets: &[Some(wgpu::ColorTargetState {
837 format,
838 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
839 write_mask: wgpu::ColorWrites::ALL,
840 })],
841 compilation_options: wgpu::PipelineCompilationOptions::default(),
842 }),
843 primitive: wgpu::PrimitiveState::default(),
844 depth_stencil: Some(stencil_for_content.clone()),
845 multisample: msaa_state,
846 multiview_mask: None,
847 cache: None,
848 });
849 let text_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
850 label: Some("text pipeline (color)"),
851 layout: Some(&text_pipeline_layout),
852 vertex: wgpu::VertexState {
853 module: &text_color_shader,
854 entry_point: Some("vs_main"),
855 buffers: &[Some(glyph_vertex.clone())],
856 compilation_options: wgpu::PipelineCompilationOptions::default(),
857 },
858 fragment: Some(wgpu::FragmentState {
859 module: &text_color_shader,
860 entry_point: Some("fs_main"),
861 targets: &[Some(wgpu::ColorTargetState {
862 format,
863 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
864 write_mask: wgpu::ColorWrites::ALL,
865 })],
866 compilation_options: wgpu::PipelineCompilationOptions::default(),
867 }),
868 primitive: wgpu::PrimitiveState::default(),
869 depth_stencil: Some(stencil_for_content.clone()),
870 multisample: msaa_state,
871 multiview_mask: None,
872 cache: None,
873 });
874 let image_rgba = text_color.clone();
876
877 let coverage_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
881 label: Some("coverage.wgsl"),
882 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/coverage.wgsl"))),
883 });
884 let coverage = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
885 label: Some("coverage pipeline (tinted a8)"),
886 layout: Some(&text_pipeline_layout),
887 vertex: wgpu::VertexState {
888 module: &coverage_shader,
889 entry_point: Some("vs_main"),
890 buffers: &[Some(glyph_vertex.clone())],
891 compilation_options: wgpu::PipelineCompilationOptions::default(),
892 },
893 fragment: Some(wgpu::FragmentState {
894 module: &coverage_shader,
895 entry_point: Some("fs_main"),
896 targets: &[Some(wgpu::ColorTargetState {
897 format,
898 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
899 write_mask: wgpu::ColorWrites::ALL,
900 })],
901 compilation_options: wgpu::PipelineCompilationOptions::default(),
902 }),
903 primitive: wgpu::PrimitiveState::default(),
904 depth_stencil: Some(stencil_for_content.clone()),
905 multisample: msaa_state,
906 multiview_mask: None,
907 cache: None,
908 });
909
910 let blur_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
912 label: Some("blur_shadow.wgsl"),
913 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
914 "shaders/blur_shadow.wgsl"
915 ))),
916 });
917 let blur_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
918 label: Some("blur pipeline layout"),
919 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
920 immediate_size: 0,
921 });
922 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
923 label: Some("blur pipeline"),
924 layout: Some(&blur_pipeline_layout),
925 vertex: wgpu::VertexState {
926 module: &blur_shader,
927 entry_point: Some("vs_main"),
928 buffers: &[Some(wgpu::VertexBufferLayout {
929 array_stride: std::mem::size_of::<BlurInstance>() as u64,
930 step_mode: wgpu::VertexStepMode::Instance,
931 attributes: &[
932 wgpu::VertexAttribute {
933 shader_location: 0,
934 offset: 0,
935 format: wgpu::VertexFormat::Float32x4,
936 },
937 wgpu::VertexAttribute {
938 shader_location: 1,
939 offset: 16,
940 format: wgpu::VertexFormat::Float32x4,
941 },
942 wgpu::VertexAttribute {
943 shader_location: 2,
944 offset: 32,
945 format: wgpu::VertexFormat::Float32x4,
946 },
947 wgpu::VertexAttribute {
948 shader_location: 3,
949 offset: 48,
950 format: wgpu::VertexFormat::Float32x2,
951 },
952 wgpu::VertexAttribute {
953 shader_location: 4,
954 offset: 56,
955 format: wgpu::VertexFormat::Float32x4,
956 },
957 ],
958 })],
959 compilation_options: wgpu::PipelineCompilationOptions::default(),
960 },
961 fragment: Some(wgpu::FragmentState {
962 module: &blur_shader,
963 entry_point: Some("fs_main"),
964 targets: &[Some(wgpu::ColorTargetState {
965 format,
966 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
967 write_mask: wgpu::ColorWrites::ALL,
968 })],
969 compilation_options: wgpu::PipelineCompilationOptions::default(),
970 }),
971 primitive: wgpu::PrimitiveState::default(),
972 depth_stencil: Some(stencil_for_content.clone()),
973 multisample: msaa_state,
974 multiview_mask: None,
975 cache: None,
976 });
977
978 let blur_content_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
980 label: Some("blur_content.wgsl"),
981 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
982 "shaders/blur_content.wgsl"
983 ))),
984 });
985 let blur_content = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
986 label: Some("blur content pipeline"),
987 layout: Some(&blur_pipeline_layout),
988 vertex: wgpu::VertexState {
989 module: &blur_content_shader,
990 entry_point: Some("vs_main"),
991 buffers: &[Some(wgpu::VertexBufferLayout {
992 array_stride: std::mem::size_of::<BlurInstance>() as u64,
993 step_mode: wgpu::VertexStepMode::Instance,
994 attributes: &[
995 wgpu::VertexAttribute {
996 shader_location: 0,
997 offset: 0,
998 format: wgpu::VertexFormat::Float32x4,
999 },
1000 wgpu::VertexAttribute {
1001 shader_location: 1,
1002 offset: 16,
1003 format: wgpu::VertexFormat::Float32x4,
1004 },
1005 wgpu::VertexAttribute {
1006 shader_location: 2,
1007 offset: 32,
1008 format: wgpu::VertexFormat::Float32x4,
1009 },
1010 wgpu::VertexAttribute {
1011 shader_location: 3,
1012 offset: 48,
1013 format: wgpu::VertexFormat::Float32x2,
1014 },
1015 wgpu::VertexAttribute {
1016 shader_location: 4,
1017 offset: 56,
1018 format: wgpu::VertexFormat::Float32x4,
1019 },
1020 ],
1021 })],
1022 compilation_options: wgpu::PipelineCompilationOptions::default(),
1023 },
1024 fragment: Some(wgpu::FragmentState {
1025 module: &blur_content_shader,
1026 entry_point: Some("fs_main"),
1027 targets: &[Some(wgpu::ColorTargetState {
1028 format,
1029 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1030 write_mask: wgpu::ColorWrites::ALL,
1031 })],
1032 compilation_options: wgpu::PipelineCompilationOptions::default(),
1033 }),
1034 primitive: wgpu::PrimitiveState::default(),
1035 depth_stencil: Some(stencil_for_content.clone()),
1036 multisample: msaa_state,
1037 multiview_mask: None,
1038 cache: None,
1039 });
1040
1041 let image_nv12_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1043 label: Some("image_nv12.wgsl"),
1044 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1045 "shaders/image_nv12.wgsl"
1046 ))),
1047 });
1048 let image_nv12_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1049 label: Some("image nv12 pipeline layout"),
1050 bind_group_layouts: &[Some(globals_layout), Some(image_bind_layout_nv12)],
1051 immediate_size: 0,
1052 });
1053 let image_nv12 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1054 label: Some("image nv12 pipeline"),
1055 layout: Some(&image_nv12_layout),
1056 vertex: wgpu::VertexState {
1057 module: &image_nv12_shader,
1058 entry_point: Some("vs_main"),
1059 buffers: &[Some(wgpu::VertexBufferLayout {
1060 array_stride: std::mem::size_of::<Nv12Instance>() as u64,
1061 step_mode: wgpu::VertexStepMode::Instance,
1062 attributes: &[
1063 wgpu::VertexAttribute {
1064 shader_location: 0,
1065 offset: 0,
1066 format: wgpu::VertexFormat::Float32x4,
1067 },
1068 wgpu::VertexAttribute {
1069 shader_location: 1,
1070 offset: 16,
1071 format: wgpu::VertexFormat::Float32x4,
1072 },
1073 wgpu::VertexAttribute {
1074 shader_location: 2,
1075 offset: 32,
1076 format: wgpu::VertexFormat::Float32x4,
1077 },
1078 wgpu::VertexAttribute {
1079 shader_location: 3,
1080 offset: 48,
1081 format: wgpu::VertexFormat::Float32,
1082 },
1083 wgpu::VertexAttribute {
1084 shader_location: 4,
1085 offset: 52,
1086 format: wgpu::VertexFormat::Float32x4,
1087 },
1088 ],
1089 })],
1090 compilation_options: wgpu::PipelineCompilationOptions::default(),
1091 },
1092 fragment: Some(wgpu::FragmentState {
1093 module: &image_nv12_shader,
1094 entry_point: Some("fs_main"),
1095 targets: &[Some(wgpu::ColorTargetState {
1096 format,
1097 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1098 write_mask: wgpu::ColorWrites::ALL,
1099 })],
1100 compilation_options: wgpu::PipelineCompilationOptions::default(),
1101 }),
1102 primitive: wgpu::PrimitiveState::default(),
1103 depth_stencil: Some(stencil_for_content.clone()),
1104 multisample: msaa_state,
1105 multiview_mask: None,
1106 cache: None,
1107 });
1108
1109 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1111 label: Some("clip_round_rect_bin.wgsl"),
1112 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1113 "shaders/clip_round_rect_bin.wgsl"
1114 ))),
1115 });
1116 let clip_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1117 label: Some("clip pipeline (bin)"),
1118 layout: Some(clip_pipeline_layout),
1119 vertex: wgpu::VertexState {
1120 module: &clip_shader_bin,
1121 entry_point: Some("vs_main"),
1122 buffers: &[Some(clip_vertex_layout.clone())],
1123 compilation_options: wgpu::PipelineCompilationOptions::default(),
1124 },
1125 fragment: Some(wgpu::FragmentState {
1126 module: &clip_shader_bin,
1127 entry_point: Some("fs_main"),
1128 targets: &[Some(clip_color_target.clone())],
1129 compilation_options: wgpu::PipelineCompilationOptions::default(),
1130 }),
1131 primitive: wgpu::PrimitiveState::default(),
1132 depth_stencil: Some(stencil_for_clip_inc.clone()),
1133 multisample: wgpu::MultisampleState {
1134 count: sample_count,
1135 mask: !0,
1136 alpha_to_coverage_enabled: false,
1137 },
1138 multiview_mask: None,
1139 cache: None,
1140 });
1141 let clip_dec = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1142 label: Some("clip pipeline (dec)"),
1143 layout: Some(clip_pipeline_layout),
1144 vertex: wgpu::VertexState {
1145 module: &clip_shader_bin,
1146 entry_point: Some("vs_main"),
1147 buffers: &[Some(clip_vertex_layout.clone())],
1148 compilation_options: wgpu::PipelineCompilationOptions::default(),
1149 },
1150 fragment: Some(wgpu::FragmentState {
1151 module: &clip_shader_bin,
1152 entry_point: Some("fs_main"),
1153 targets: &[Some(clip_color_target.clone())],
1154 compilation_options: wgpu::PipelineCompilationOptions::default(),
1155 }),
1156 primitive: wgpu::PrimitiveState::default(),
1157 depth_stencil: Some(stencil_for_clip_dec.clone()),
1158 multisample: wgpu::MultisampleState {
1159 count: sample_count,
1160 mask: !0,
1161 alpha_to_coverage_enabled: false,
1162 },
1163 multiview_mask: None,
1164 cache: None,
1165 });
1166
1167 let slug = Some(slug::create_pipeline(
1168 device,
1169 format,
1170 sample_count,
1171 stencil_for_content,
1172 ));
1173
1174 let mesh_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1176 label: Some("mesh.wgsl"),
1177 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/mesh.wgsl"))),
1178 });
1179 let mesh_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1180 label: Some("mesh pipeline layout"),
1181 bind_group_layouts: &[Some(globals_layout), Some(mesh_bind_layout)],
1182 immediate_size: 0,
1183 });
1184 let mesh_vertex_layout = wgpu::VertexBufferLayout {
1185 array_stride: std::mem::size_of::<MeshVertex>() as u64,
1186 step_mode: wgpu::VertexStepMode::Vertex,
1187 attributes: &[
1188 wgpu::VertexAttribute {
1189 shader_location: 0,
1190 offset: 0,
1191 format: wgpu::VertexFormat::Float32x2,
1192 },
1193 wgpu::VertexAttribute {
1194 shader_location: 1,
1195 offset: 8,
1196 format: wgpu::VertexFormat::Float32x4,
1197 },
1198 wgpu::VertexAttribute {
1199 shader_location: 2,
1200 offset: 24,
1201 format: wgpu::VertexFormat::Float32x2,
1202 },
1203 ],
1204 };
1205 let make_mesh_pipeline =
1206 |label: &str, depth: &wgpu::DepthStencilState, color: &wgpu::ColorTargetState| {
1207 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1208 label: Some(label),
1209 layout: Some(&mesh_pipeline_layout),
1210 vertex: wgpu::VertexState {
1211 module: &mesh_shader,
1212 entry_point: Some("vs_main"),
1213 buffers: &[Some(mesh_vertex_layout.clone())],
1214 compilation_options: wgpu::PipelineCompilationOptions::default(),
1215 },
1216 fragment: Some(wgpu::FragmentState {
1217 module: &mesh_shader,
1218 entry_point: Some("fs_main"),
1219 targets: &[Some(color.clone())],
1220 compilation_options: wgpu::PipelineCompilationOptions::default(),
1221 }),
1222 primitive: wgpu::PrimitiveState {
1223 topology: wgpu::PrimitiveTopology::TriangleList,
1224 ..Default::default()
1225 },
1226 depth_stencil: Some(depth.clone()),
1227 multisample: msaa_state,
1228 multiview_mask: None,
1229 cache: None,
1230 })
1231 };
1232 let mesh_color_target = wgpu::ColorTargetState {
1233 format,
1234 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1235 write_mask: wgpu::ColorWrites::ALL,
1236 };
1237 let mut stencil_for_mesh = stencil_for_content.clone();
1238 stencil_for_mesh.stencil.front.compare = wgpu::CompareFunction::Equal;
1239 stencil_for_mesh.stencil.back.compare = wgpu::CompareFunction::Equal;
1240 let mesh = make_mesh_pipeline("mesh pipeline", &stencil_for_mesh, &mesh_color_target);
1241 let mesh_overlay = make_mesh_pipeline(
1242 "mesh overlay pipeline",
1243 stencil_for_content,
1244 &mesh_color_target,
1245 );
1246 let mesh_clip_inc = make_mesh_pipeline(
1247 "mesh clip (inc) pipeline",
1248 stencil_for_clip_inc,
1249 clip_color_target,
1250 );
1251 let mesh_clip_dec = make_mesh_pipeline(
1252 "mesh clip (dec) pipeline",
1253 stencil_for_clip_dec,
1254 clip_color_target,
1255 );
1256 let mesh_blend_target = |blend: wgpu::BlendState| wgpu::ColorTargetState {
1257 format,
1258 blend: Some(blend),
1259 write_mask: wgpu::ColorWrites::ALL,
1260 };
1261 let premult_alpha = wgpu::BlendComponent::OVER;
1262 let mesh_add = make_mesh_pipeline(
1263 "mesh pipeline (add)",
1264 &stencil_for_mesh,
1265 &mesh_blend_target(wgpu::BlendState {
1266 color: wgpu::BlendComponent {
1267 src_factor: wgpu::BlendFactor::One,
1268 dst_factor: wgpu::BlendFactor::One,
1269 operation: wgpu::BlendOperation::Add,
1270 },
1271 alpha: premult_alpha,
1272 }),
1273 );
1274 let mesh_multiply = make_mesh_pipeline(
1275 "mesh pipeline (multiply)",
1276 &stencil_for_mesh,
1277 &mesh_blend_target(wgpu::BlendState {
1278 color: wgpu::BlendComponent {
1279 src_factor: wgpu::BlendFactor::Dst,
1280 dst_factor: wgpu::BlendFactor::Zero,
1281 operation: wgpu::BlendOperation::Add,
1282 },
1283 alpha: premult_alpha,
1284 }),
1285 );
1286 let mesh_screen = make_mesh_pipeline(
1287 "mesh pipeline (screen)",
1288 &stencil_for_mesh,
1289 &mesh_blend_target(wgpu::BlendState {
1290 color: wgpu::BlendComponent {
1291 src_factor: wgpu::BlendFactor::One,
1292 dst_factor: wgpu::BlendFactor::OneMinusSrc,
1293 operation: wgpu::BlendOperation::Add,
1294 },
1295 alpha: premult_alpha,
1296 }),
1297 );
1298 let mesh_darken = make_mesh_pipeline(
1299 "mesh pipeline (darken)",
1300 &stencil_for_mesh,
1301 &mesh_blend_target(wgpu::BlendState {
1302 color: wgpu::BlendComponent {
1303 src_factor: wgpu::BlendFactor::One,
1304 dst_factor: wgpu::BlendFactor::One,
1305 operation: wgpu::BlendOperation::Min,
1306 },
1307 alpha: premult_alpha,
1308 }),
1309 );
1310 let mesh_lighten = make_mesh_pipeline(
1311 "mesh pipeline (lighten)",
1312 &stencil_for_mesh,
1313 &mesh_blend_target(wgpu::BlendState {
1314 color: wgpu::BlendComponent {
1315 src_factor: wgpu::BlendFactor::One,
1316 dst_factor: wgpu::BlendFactor::One,
1317 operation: wgpu::BlendOperation::Max,
1318 },
1319 alpha: premult_alpha,
1320 }),
1321 );
1322 let projective_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1327 label: Some("projective_layer.wgsl"),
1328 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1329 "shaders/projective_layer.wgsl"
1330 ))),
1331 });
1332 let projective_pipeline_layout =
1333 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1334 label: Some("projective layer pipeline layout"),
1335 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
1336 immediate_size: 0,
1337 });
1338 let projective_vertex_layout = wgpu::VertexBufferLayout {
1339 array_stride: std::mem::size_of::<ProjectiveInstance>() as u64,
1340 step_mode: wgpu::VertexStepMode::Instance,
1341 attributes: &[
1342 wgpu::VertexAttribute {
1343 shader_location: 0,
1344 offset: 0,
1345 format: wgpu::VertexFormat::Float32x2,
1346 },
1347 wgpu::VertexAttribute {
1348 shader_location: 1,
1349 offset: 8,
1350 format: wgpu::VertexFormat::Float32x2,
1351 },
1352 wgpu::VertexAttribute {
1353 shader_location: 2,
1354 offset: 16,
1355 format: wgpu::VertexFormat::Float32x2,
1356 },
1357 wgpu::VertexAttribute {
1358 shader_location: 3,
1359 offset: 24,
1360 format: wgpu::VertexFormat::Float32x2,
1361 },
1362 wgpu::VertexAttribute {
1363 shader_location: 4,
1364 offset: 32,
1365 format: wgpu::VertexFormat::Float32x4,
1366 },
1367 wgpu::VertexAttribute {
1368 shader_location: 5,
1369 offset: 48,
1370 format: wgpu::VertexFormat::Float32x4,
1371 },
1372 wgpu::VertexAttribute {
1373 shader_location: 6,
1374 offset: 64,
1375 format: wgpu::VertexFormat::Float32,
1376 },
1377 ],
1378 };
1379 let projective_layer = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1380 label: Some("projective layer composite pipeline"),
1381 layout: Some(&projective_pipeline_layout),
1382 vertex: wgpu::VertexState {
1383 module: &projective_shader,
1384 entry_point: Some("vs_main"),
1385 buffers: &[Some(projective_vertex_layout)],
1386 compilation_options: wgpu::PipelineCompilationOptions::default(),
1387 },
1388 fragment: Some(wgpu::FragmentState {
1389 module: &projective_shader,
1390 entry_point: Some("fs_main"),
1391 targets: &[Some(wgpu::ColorTargetState {
1392 format,
1393 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1394 write_mask: wgpu::ColorWrites::ALL,
1395 })],
1396 compilation_options: wgpu::PipelineCompilationOptions::default(),
1397 }),
1398 primitive: wgpu::PrimitiveState::default(),
1399 depth_stencil: Some(stencil_for_content.clone()),
1400 multisample: msaa_state,
1401 multiview_mask: None,
1402 cache: None,
1403 });
1404
1405 let blend_layer_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1406 label: Some("blend_layer.wgsl"),
1407 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1408 "shaders/blend_layer.wgsl"
1409 ))),
1410 });
1411 let blend_layer_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1412 label: Some("blend layer pipeline layout"),
1413 bind_group_layouts: &[
1414 Some(globals_layout),
1415 Some(text_bind_layout),
1416 Some(text_bind_layout),
1417 ],
1418 immediate_size: 0,
1419 });
1420 let blend_vertex_layout = wgpu::VertexBufferLayout {
1421 array_stride: std::mem::size_of::<BlendInstance>() as u64,
1422 step_mode: wgpu::VertexStepMode::Instance,
1423 attributes: &[
1424 wgpu::VertexAttribute {
1425 shader_location: 0,
1426 offset: 0,
1427 format: wgpu::VertexFormat::Float32x4,
1428 },
1429 wgpu::VertexAttribute {
1430 shader_location: 1,
1431 offset: 16,
1432 format: wgpu::VertexFormat::Float32x4,
1433 },
1434 wgpu::VertexAttribute {
1435 shader_location: 2,
1436 offset: 32,
1437 format: wgpu::VertexFormat::Float32x4,
1438 },
1439 wgpu::VertexAttribute {
1440 shader_location: 3,
1441 offset: 48,
1442 format: wgpu::VertexFormat::Float32x4,
1443 },
1444 wgpu::VertexAttribute {
1445 shader_location: 4,
1446 offset: 64,
1447 format: wgpu::VertexFormat::Uint32,
1448 },
1449 ],
1450 };
1451 let blend_layer = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1452 label: Some("blend layer pipeline"),
1453 layout: Some(&blend_layer_layout),
1454 vertex: wgpu::VertexState {
1455 module: &blend_layer_shader,
1456 entry_point: Some("vs_main"),
1457 buffers: &[Some(blend_vertex_layout)],
1458 compilation_options: wgpu::PipelineCompilationOptions::default(),
1459 },
1460 fragment: Some(wgpu::FragmentState {
1461 module: &blend_layer_shader,
1462 entry_point: Some("fs_main"),
1463 targets: &[Some(wgpu::ColorTargetState {
1464 format,
1465 blend: None,
1466 write_mask: wgpu::ColorWrites::ALL,
1467 })],
1468 compilation_options: wgpu::PipelineCompilationOptions::default(),
1469 }),
1470 primitive: wgpu::PrimitiveState::default(),
1471 depth_stencil: Some(stencil_for_content.clone()),
1472 multisample: msaa_state,
1473 multiview_mask: None,
1474 cache: None,
1475 });
1476
1477 Self {
1478 rects,
1479 borders,
1480 ellipses,
1481 ellipse_borders,
1482 arcs,
1483 text_mask,
1484 text_color,
1485 image_rgba,
1486 image_nv12,
1487 coverage,
1488 blur,
1489 blur_content,
1490 clip_bin,
1491 clip_dec,
1492 slug,
1493 mesh,
1494 mesh_add,
1495 mesh_multiply,
1496 mesh_screen,
1497 mesh_darken,
1498 mesh_lighten,
1499 blend_layer,
1500 mesh_overlay,
1501 mesh_clip_inc,
1502 mesh_clip_dec,
1503 projective_layer,
1504 }
1505 }
1506}
1507
1508struct Pass {
1510 target: PassTarget,
1511 initial_scissor: (u32, u32, u32, u32),
1513 clear_color: Option<[f32; 4]>,
1516 cmds: Vec<Cmd>,
1517}
1518
1519struct FlattenRecord {
1523 stack_len: usize,
1526 layer_id: u32,
1527 map: [f32; 9],
1530 layer_rect: repose_core::Rect,
1532 saved_scissor: Vec<repose_core::Rect>,
1533 saved_root: repose_core::Rect,
1534 saved_size: (f32, f32),
1535}
1536
1537const FLATTEN_ID_BASE: u32 = 0xF000_0000;
1541
1542#[allow(non_snake_case)]
1543enum Cmd {
1544 ClipPush {
1545 off: u64,
1546 cnt: u32,
1547 scissor: (u32, u32, u32, u32),
1548 difference: bool,
1549 rounded: bool,
1550 },
1551 ClipPop {
1552 off: u64,
1553 cnt: u32,
1554 scissor: (u32, u32, u32, u32),
1555 difference: bool,
1556 },
1557 Rect {
1558 off: u64,
1559 cnt: u32,
1560 },
1561 Border {
1562 off: u64,
1563 cnt: u32,
1564 },
1565 Ellipse {
1566 off: u64,
1567 cnt: u32,
1568 },
1569 EllipseBorder {
1570 off: u64,
1571 cnt: u32,
1572 },
1573 Arc {
1574 off: u64,
1575 cnt: u32,
1576 },
1577 GlyphsMask {
1578 off: u64,
1579 cnt: u32,
1580 },
1581 GlyphsColor {
1582 off: u64,
1583 cnt: u32,
1584 },
1585 GlyphsVector {
1586 off: u64,
1587 cnt: u32,
1588 },
1589 ImageRgba {
1590 off: u64,
1591 cnt: u32,
1592 handle: u64,
1593 },
1594 Coverage {
1598 off: u64,
1599 cnt: u32,
1600 handle: u64,
1601 },
1602 ImageNv12 {
1603 off: u64,
1604 cnt: u32,
1605 handle: u64,
1606 },
1607 CompositeLayer {
1611 off: u64,
1612 cnt: u32,
1613 layer_id: u32,
1614 },
1615 CompositeShadow {
1619 off: u64,
1620 cnt: u32,
1621 layer_id: u32,
1622 },
1623 CompositeBlur {
1626 off: u64,
1627 cnt: u32,
1628 layer_id: u32,
1629 },
1630 CompositeProjective {
1635 off: u64,
1636 cnt: u32,
1637 layer_id: u32,
1638 },
1639 VectorMesh {
1641 voff: u64,
1642 vcnt: u32,
1643 ioff: u64,
1644 icnt: u32,
1645 uoff: u64,
1646 blend: repose_core::BlendMode,
1647 },
1648 BlendLayer {
1655 off: u64,
1656 cnt: u32,
1657 src_layer: u32,
1658 dst_layer: Option<u32>,
1659 parent: PassTarget,
1660 },
1661 VectorOverlay {
1663 voff: u64,
1664 vcnt: u32,
1665 ioff: u64,
1666 icnt: u32,
1667 uoff: u64,
1668 },
1669 VectorClipPush {
1674 voff: u64,
1675 vcnt: u32,
1676 ioff: u64,
1677 icnt: u32,
1678 uoff: u64,
1679 scissor: (u32, u32, u32, u32),
1680 difference: bool,
1681 },
1682 VectorClipPop {
1684 voff: u64,
1685 vcnt: u32,
1686 ioff: u64,
1687 icnt: u32,
1688 uoff: u64,
1689 scissor: (u32, u32, u32, u32),
1690 difference: bool,
1691 },
1692 Callback {
1693 rect: repose_core::Rect,
1694 payload: repose_core::PaintCallbackPayload,
1695 },
1696}
1697
1698struct CoverageTex {
1703 #[allow(dead_code)]
1705 tex: wgpu::Texture,
1706 bind: wgpu::BindGroup,
1707 w: u32,
1708 h: u32,
1709 last_used_frame: u64,
1710 bytes: u64,
1711}
1712
1713enum ImageTex {
1714 Rgba {
1715 tex: wgpu::Texture,
1716 bind: wgpu::BindGroup,
1717 w: u32,
1718 h: u32,
1719 format: wgpu::TextureFormat,
1720 last_used_frame: u64,
1721 bytes: u64,
1722 },
1723 User {
1725 bind: wgpu::BindGroup,
1726 w: u32,
1727 h: u32,
1728 last_used_frame: u64,
1729 bytes: u64,
1730 },
1731 Nv12 {
1732 tex_y: wgpu::Texture,
1733 tex_uv: wgpu::Texture,
1734 bind: wgpu::BindGroup,
1735 yuv_buf: wgpu::Buffer,
1736 w: u32,
1737 h: u32,
1738 color_info: ColorInfo,
1739 last_used_frame: u64,
1740 bytes: u64,
1741 },
1742}
1743
1744#[derive(Clone)]
1745struct RetainedImage {
1746 w: u32,
1747 h: u32,
1748 format: wgpu::TextureFormat,
1749 rgba: Vec<u8>,
1750}
1751
1752struct AtlasA8 {
1753 tex: wgpu::Texture,
1754 view: wgpu::TextureView,
1755 sampler: wgpu::Sampler,
1756 size: u32,
1757 next_x: u32,
1758 next_y: u32,
1759 row_h: u32,
1760 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
1761}
1762
1763struct AtlasRGBA {
1764 tex: wgpu::Texture,
1765 view: wgpu::TextureView,
1766 sampler: wgpu::Sampler,
1767 size: u32,
1768 next_x: u32,
1769 next_y: u32,
1770 row_h: u32,
1771 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
1772}
1773
1774#[derive(Clone, Copy)]
1775struct GlyphInfo {
1776 u0: f32,
1777 v0: f32,
1778 u1: f32,
1779 v1: f32,
1780 w: f32,
1781 h: f32,
1782}
1783
1784#[repr(C)]
1785#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1786struct RectInstance {
1787 xywh: [f32; 4],
1788 radii: [f32; 4],
1789 brush_type: u32,
1790 grad_kind: u32,
1791 _pad: [f32; 2],
1792 color0: [f32; 4],
1793 color1: [f32; 4],
1794 grad_p0: [f32; 2],
1795 grad_p1: [f32; 2],
1796 tile_mode: u32,
1797 _pad2: [f32; 3],
1798 fwd_mat: [f32; 4],
1799}
1800
1801#[repr(C)]
1802#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1803struct BorderInstance {
1804 xywh: [f32; 4],
1805 radii: [f32; 4],
1806 stroke: f32,
1807 brush_type: u32,
1808 _pad: [f32; 2],
1809 grad_kind: u32,
1810 color0: [f32; 4],
1811 color1: [f32; 4],
1812 grad_p0: [f32; 2],
1813 grad_p1: [f32; 2],
1814 tile_mode: u32,
1815 _pad2: [f32; 3],
1816 fwd_mat: [f32; 4],
1817}
1818
1819#[repr(C)]
1820#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1821struct EllipseInstance {
1822 xywh: [f32; 4],
1823 brush_type: u32,
1824 grad_kind: u32,
1825 _pad: [f32; 2],
1826 color0: [f32; 4],
1827 color1: [f32; 4],
1828 grad_p0: [f32; 2],
1829 grad_p1: [f32; 2],
1830 tile_mode: u32,
1831 _pad2: [f32; 3],
1832 fwd_mat: [f32; 4],
1833}
1834
1835#[repr(C)]
1836#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1837struct EllipseBorderInstance {
1838 xywh: [f32; 4],
1839 stroke: f32,
1840 pad: f32,
1841 brush_type: u32,
1842 grad_kind: u32,
1843 color0: [f32; 4],
1844 color1: [f32; 4],
1845 grad_p0: [f32; 2],
1846 grad_p1: [f32; 2],
1847 tile_mode: u32,
1848 _pad2: [f32; 3],
1849 fwd_mat: [f32; 4],
1850}
1851
1852#[repr(C)]
1853#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1854struct ArcInstance {
1855 xywh: [f32; 4],
1856 start_angle: f32,
1857 sweep_angle: f32,
1858 stroke: f32,
1859 pad: f32,
1860 brush_type: u32,
1861 grad_kind: u32,
1862 _pad0: [f32; 2],
1863 color0: [f32; 4],
1864 color1: [f32; 4],
1865 grad_p0: [f32; 2],
1866 grad_p1: [f32; 2],
1867 tile_mode: u32,
1868 cap: f32, _pad1: [f32; 2],
1870 fwd_mat: [f32; 4],
1871}
1872
1873#[repr(C)]
1874#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1875struct GlyphInstance {
1876 xywh: [f32; 4],
1877 uv: [f32; 4],
1878 color: [f32; 4],
1879 fwd_mat: [f32; 4],
1880}
1881
1882#[repr(C)]
1883#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1884struct BlurInstance {
1885 xywh: [f32; 4],
1886 uv: [f32; 4],
1887 color: [f32; 4],
1888 blur_uv: [f32; 2],
1889 fwd_mat: [f32; 4],
1890}
1891
1892#[repr(C)]
1898#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1899struct ProjectiveInstance {
1900 c0: [f32; 2],
1901 c1: [f32; 2],
1902 c2: [f32; 2],
1903 c3: [f32; 2],
1904 uv: [f32; 4],
1905 w: [f32; 4],
1906 alpha: f32,
1907 _pad: [f32; 3],
1908}
1909
1910#[repr(C)]
1913#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1914struct YuvTransformRaw {
1915 row0: [f32; 4],
1916 row1: [f32; 4],
1917 row2: [f32; 4],
1918 b: [f32; 4],
1919}
1920
1921#[repr(C)]
1922#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1923struct Nv12Instance {
1924 xywh: [f32; 4],
1925 uv: [f32; 4],
1926 color: [f32; 4], uv_x_offset: f32,
1928 fwd_mat: [f32; 4],
1929 _pad: [f32; 1],
1930}
1931
1932#[repr(C)]
1933#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1934struct ClipInstance {
1935 xywh: [f32; 4],
1936 radii: [f32; 4],
1937 fwd_mat: [f32; 4],
1938}
1939
1940#[repr(C)]
1943#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1944struct BlendInstance {
1945 xywh: [f32; 4],
1946 uv: [f32; 4],
1947 color: [f32; 4],
1948 fwd_mat: [f32; 4],
1949 mode: u32,
1950 _pad: [f32; 3],
1951}
1952
1953#[repr(C)]
1954#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1955struct MeshVertex {
1956 pos: [f32; 2],
1957 color: [f32; 4],
1958 uv: [f32; 2],
1959}
1960
1961#[repr(C)]
1962#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1963struct MeshUniform {
1964 m0: [f32; 4],
1965 m1: [f32; 4],
1966 paint: [u32; 4],
1967 color0: [f32; 4],
1968 color1: [f32; 4],
1969 grad_start: [f32; 2],
1970 _p3: [f32; 2],
1971 grad_end: [f32; 2],
1972 _p4: [f32; 2],
1973}
1974
1975const MESH_UNIFORM_SLOT: u64 = 256;
1977const MESH_UNIFORM_CAP: u64 = 4 * 1024 * 1024;
1978
1979impl MeshUniform {
1980 fn identity() -> Self {
1981 Self {
1982 m0: [1.0, 0.0, 0.0, 0.0],
1983 m1: [0.0, 1.0, 0.0, 0.0],
1984 paint: [0; 4],
1985 color0: [0.0; 4],
1986 color1: [0.0; 4],
1987 grad_start: [0.0; 2],
1988 _p3: [0.0; 2],
1989 grad_end: [0.0; 2],
1990 _p4: [0.0; 2],
1991 }
1992 }
1993}
1994
1995fn mesh_uniform_from_paint(affine: [f32; 6], paint: &repose_core::PaintDesc) -> MeshUniform {
1996 let (paint_type, paint_kind, color0, color1, grad_start, grad_end) = match paint {
1997 repose_core::PaintDesc::Solid => (0u32, 0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
1998 repose_core::PaintDesc::Linear {
1999 start,
2000 end,
2001 start_color,
2002 end_color,
2003 } => (
2004 1u32,
2005 0u32,
2006 start_color.to_linear(),
2007 end_color.to_linear(),
2008 [start.x, start.y],
2009 [end.x, end.y],
2010 ),
2011 repose_core::PaintDesc::Radial {
2012 center,
2013 radius,
2014 start_color,
2015 end_color,
2016 } => (
2017 1u32,
2018 1u32,
2019 start_color.to_linear(),
2020 end_color.to_linear(),
2021 [center.x, center.y],
2022 [radius.max(0.0), 0.0],
2023 ),
2024 repose_core::PaintDesc::Sweep {
2025 center,
2026 start_color,
2027 end_color,
2028 } => (
2029 1u32,
2030 2u32,
2031 start_color.to_linear(),
2032 end_color.to_linear(),
2033 [center.x, center.y],
2034 [0.0, 0.0],
2035 ),
2036 _ => (0u32, 0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
2037 };
2038 MeshUniform {
2039 m0: [affine[0], affine[1], affine[2], 0.0],
2040 m1: [affine[3], affine[4], affine[5], 0.0],
2041 paint: [paint_type, paint_kind, 0, 0],
2042 color0,
2043 color1,
2044 grad_start,
2045 _p3: [0.0; 2],
2046 grad_end,
2047 _p4: [0.0; 2],
2048 }
2049}
2050
2051fn combine_mesh_affine(current: &Transform, mesh: [f32; 6]) -> [f32; 6] {
2052 let cm = current.linear();
2053 let (cm00, cm01, cm10, cm11) = (cm[0], cm[1], cm[2], cm[3]);
2054 let mm00 = mesh[0];
2055 let mm01 = mesh[1];
2056 let mm10 = mesh[2];
2057 let mm11 = mesh[3];
2058 let mtx = mesh[4];
2059 let mty = mesh[5];
2060 let r00 = cm00 * mm00 + cm01 * mm10;
2061 let r01 = cm00 * mm01 + cm01 * mm11;
2062 let r10 = cm10 * mm00 + cm11 * mm10;
2063 let r11 = cm10 * mm01 + cm11 * mm11;
2064 let tx = cm00 * mtx + cm01 * mty + current.translate_x;
2065 let ty = cm10 * mtx + cm11 * mty + current.translate_y;
2066 [r00, r01, tx, r10, r11, ty]
2069}
2070
2071fn mesh_aabb(mesh: &repose_core::VectorMeshData, affine: [f32; 6]) -> repose_core::Rect {
2072 let mut min_x = f32::MAX;
2073 let mut min_y = f32::MAX;
2074 let mut max_x = f32::MIN;
2075 let mut max_y = f32::MIN;
2076 for v in mesh.vertices.iter() {
2077 let x = affine[0] * v.pos[0] + affine[1] * v.pos[1] + affine[2];
2078 let y = affine[3] * v.pos[0] + affine[4] * v.pos[1] + affine[5];
2079 min_x = min_x.min(x);
2080 min_y = min_y.min(y);
2081 max_x = max_x.max(x);
2082 max_y = max_y.max(y);
2083 }
2084 let w = (max_x - min_x).max(0.0);
2085 let h = (max_y - min_y).max(0.0);
2086 if !min_x.is_finite() || !min_y.is_finite() {
2087 return repose_core::Rect {
2088 x: 0.0,
2089 y: 0.0,
2090 w: 0.0,
2091 h: 0.0,
2092 };
2093 }
2094 repose_core::Rect {
2095 x: min_x,
2096 y: min_y,
2097 w,
2098 h,
2099 }
2100}
2101
2102fn swash_to_a8_coverage(content: repose_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
2103 match content {
2104 repose_text::SwashContent::Mask => Some(data.to_vec()),
2105 repose_text::SwashContent::SubpixelMask => {
2106 let mut out = Vec::with_capacity(data.len() / 4);
2107 for px in data.as_chunks::<4>().0 {
2108 let r = px[0];
2109 let g = px[1];
2110 let b = px[2];
2111 out.push(r.max(g).max(b));
2112 }
2113 Some(out)
2114 }
2115 repose_text::SwashContent::Color => None,
2116 }
2117}
2118
2119impl WgpuSceneRenderer {
2120 pub fn from_device(
2121 device: wgpu::Device,
2122 queue: wgpu::Queue,
2123 output_format: wgpu::TextureFormat,
2124 msaa_samples: u32,
2125 ) -> Self {
2126 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2127 label: Some("globals layout"),
2128 entries: &[wgpu::BindGroupLayoutEntry {
2129 binding: 0,
2130 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
2131 ty: wgpu::BindingType::Buffer {
2132 ty: wgpu::BufferBindingType::Uniform,
2133 has_dynamic_offset: false,
2134 min_binding_size: None,
2135 },
2136 count: None,
2137 }],
2138 });
2139
2140 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
2141 label: Some("globals buf"),
2142 size: std::mem::size_of::<Globals>() as u64,
2143 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2144 mapped_at_creation: false,
2145 });
2146
2147 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
2148 label: Some("globals bind"),
2149 layout: &globals_layout,
2150 entries: &[wgpu::BindGroupEntry {
2151 binding: 0,
2152 resource: globals_buf.as_entire_binding(),
2153 }],
2154 });
2155
2156 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
2157
2158 let stencil_for_content = wgpu::DepthStencilState {
2159 format: ds_format,
2160 depth_write_enabled: Some(false),
2161 depth_compare: Some(wgpu::CompareFunction::Always),
2162 stencil: wgpu::StencilState {
2163 front: wgpu::StencilFaceState {
2164 compare: wgpu::CompareFunction::Equal,
2171 fail_op: wgpu::StencilOperation::Keep,
2172 depth_fail_op: wgpu::StencilOperation::Keep,
2173 pass_op: wgpu::StencilOperation::Keep,
2174 },
2175 back: wgpu::StencilFaceState {
2176 compare: wgpu::CompareFunction::Equal,
2177 fail_op: wgpu::StencilOperation::Keep,
2178 depth_fail_op: wgpu::StencilOperation::Keep,
2179 pass_op: wgpu::StencilOperation::Keep,
2180 },
2181 read_mask: 0xFF,
2182 write_mask: 0x00,
2183 },
2184 bias: wgpu::DepthBiasState::default(),
2185 };
2186
2187 let stencil_for_clip_inc = wgpu::DepthStencilState {
2188 format: ds_format,
2189 depth_write_enabled: Some(false),
2190 depth_compare: Some(wgpu::CompareFunction::Always),
2191 stencil: wgpu::StencilState {
2192 front: wgpu::StencilFaceState {
2193 compare: wgpu::CompareFunction::Equal,
2194 fail_op: wgpu::StencilOperation::Keep,
2195 depth_fail_op: wgpu::StencilOperation::Keep,
2196 pass_op: wgpu::StencilOperation::IncrementClamp,
2197 },
2198 back: wgpu::StencilFaceState {
2199 compare: wgpu::CompareFunction::Equal,
2200 fail_op: wgpu::StencilOperation::Keep,
2201 depth_fail_op: wgpu::StencilOperation::Keep,
2202 pass_op: wgpu::StencilOperation::IncrementClamp,
2203 },
2204 read_mask: 0xFF,
2205 write_mask: 0xFF,
2206 },
2207 bias: wgpu::DepthBiasState::default(),
2208 };
2209
2210 let stencil_for_clip_dec = wgpu::DepthStencilState {
2211 format: ds_format,
2212 depth_write_enabled: Some(false),
2213 depth_compare: Some(wgpu::CompareFunction::Always),
2214 stencil: wgpu::StencilState {
2215 front: wgpu::StencilFaceState {
2216 compare: wgpu::CompareFunction::Equal,
2217 fail_op: wgpu::StencilOperation::Keep,
2218 depth_fail_op: wgpu::StencilOperation::Keep,
2219 pass_op: wgpu::StencilOperation::DecrementClamp,
2220 },
2221 back: wgpu::StencilFaceState {
2222 compare: wgpu::CompareFunction::Equal,
2223 fail_op: wgpu::StencilOperation::Keep,
2224 depth_fail_op: wgpu::StencilOperation::Keep,
2225 pass_op: wgpu::StencilOperation::DecrementClamp,
2226 },
2227 read_mask: 0xFF,
2228 write_mask: 0xFF,
2229 },
2230 bias: wgpu::DepthBiasState::default(),
2231 };
2232
2233 let _multisample_state = wgpu::MultisampleState {
2234 count: msaa_samples,
2235 mask: !0,
2236 alpha_to_coverage_enabled: false,
2237 };
2238
2239 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
2243 label: Some("image/text sampler"),
2244 address_mode_u: wgpu::AddressMode::ClampToEdge,
2245 address_mode_v: wgpu::AddressMode::ClampToEdge,
2246 mag_filter: wgpu::FilterMode::Linear,
2247 min_filter: wgpu::FilterMode::Linear,
2248 mipmap_filter: wgpu::MipmapFilterMode::Linear,
2249 ..Default::default()
2250 });
2251
2252 let layer_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
2254 label: Some("layer nearest sampler"),
2255 address_mode_u: wgpu::AddressMode::ClampToEdge,
2256 address_mode_v: wgpu::AddressMode::ClampToEdge,
2257 mag_filter: wgpu::FilterMode::Nearest,
2258 min_filter: wgpu::FilterMode::Nearest,
2259 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
2260 ..Default::default()
2261 });
2262
2263 let layer_sampler_linear = device.create_sampler(&wgpu::SamplerDescriptor {
2266 label: Some("layer linear sampler"),
2267 address_mode_u: wgpu::AddressMode::ClampToEdge,
2268 address_mode_v: wgpu::AddressMode::ClampToEdge,
2269 mag_filter: wgpu::FilterMode::Linear,
2270 min_filter: wgpu::FilterMode::Linear,
2271 mipmap_filter: wgpu::MipmapFilterMode::Linear,
2272 ..Default::default()
2273 });
2274
2275 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2277 label: Some("text/rgba bind layout"),
2278 entries: &[
2279 wgpu::BindGroupLayoutEntry {
2280 binding: 0,
2281 visibility: wgpu::ShaderStages::FRAGMENT,
2282 ty: wgpu::BindingType::Texture {
2283 multisampled: false,
2284 view_dimension: wgpu::TextureViewDimension::D2,
2285 sample_type: wgpu::TextureSampleType::Float { filterable: true },
2286 },
2287 count: None,
2288 },
2289 wgpu::BindGroupLayoutEntry {
2290 binding: 1,
2291 visibility: wgpu::ShaderStages::FRAGMENT,
2292 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
2293 count: None,
2294 },
2295 ],
2296 });
2297 let image_bind_layout_rgba = text_bind_layout.clone();
2299
2300 let image_bind_layout_nv12 =
2302 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2303 label: Some("image bind layout nv12"),
2304 entries: &[
2305 wgpu::BindGroupLayoutEntry {
2307 binding: 0,
2308 visibility: wgpu::ShaderStages::FRAGMENT,
2309 ty: wgpu::BindingType::Texture {
2310 multisampled: false,
2311 view_dimension: wgpu::TextureViewDimension::D2,
2312 sample_type: wgpu::TextureSampleType::Float { filterable: true },
2313 },
2314 count: None,
2315 },
2316 wgpu::BindGroupLayoutEntry {
2318 binding: 1,
2319 visibility: wgpu::ShaderStages::FRAGMENT,
2320 ty: wgpu::BindingType::Texture {
2321 multisampled: false,
2322 view_dimension: wgpu::TextureViewDimension::D2,
2323 sample_type: wgpu::TextureSampleType::Float { filterable: true },
2324 },
2325 count: None,
2326 },
2327 wgpu::BindGroupLayoutEntry {
2329 binding: 2,
2330 visibility: wgpu::ShaderStages::FRAGMENT,
2331 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
2332 count: None,
2333 },
2334 wgpu::BindGroupLayoutEntry {
2336 binding: 3,
2337 visibility: wgpu::ShaderStages::FRAGMENT,
2338 ty: wgpu::BindingType::Buffer {
2339 ty: wgpu::BufferBindingType::Uniform,
2340 has_dynamic_offset: false,
2341 min_binding_size: None,
2342 },
2343 count: None,
2344 },
2345 ],
2346 });
2347
2348 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2350 label: Some("clip pipeline layout"),
2351 bind_group_layouts: &[Some(&globals_layout)],
2352 immediate_size: 0,
2353 });
2354 let clip_vertex_layout = wgpu::VertexBufferLayout {
2355 array_stride: std::mem::size_of::<ClipInstance>() as u64,
2356 step_mode: wgpu::VertexStepMode::Instance,
2357 attributes: &[
2358 wgpu::VertexAttribute {
2359 shader_location: 0,
2360 offset: 0,
2361 format: wgpu::VertexFormat::Float32x4,
2362 },
2363 wgpu::VertexAttribute {
2364 shader_location: 1,
2365 offset: 16,
2366 format: wgpu::VertexFormat::Float32x4,
2367 },
2368 wgpu::VertexAttribute {
2369 shader_location: 2,
2370 offset: 32,
2371 format: wgpu::VertexFormat::Float32x4,
2372 },
2373 ],
2374 };
2375 let clip_color_target = wgpu::ColorTargetState {
2376 format: output_format,
2377 blend: None,
2378 write_mask: wgpu::ColorWrites::empty(),
2379 };
2380
2381 let mesh_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2383 label: Some("mesh uniform layout"),
2384 entries: &[wgpu::BindGroupLayoutEntry {
2385 binding: 0,
2386 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
2387 ty: wgpu::BindingType::Buffer {
2388 ty: wgpu::BufferBindingType::Uniform,
2389 has_dynamic_offset: true,
2390 min_binding_size: NonZero::new(MESH_UNIFORM_SLOT),
2391 },
2392 count: None,
2393 }],
2394 });
2395 let mesh_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
2396 label: Some("mesh uniform buffer"),
2397 size: MESH_UNIFORM_CAP,
2398 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2399 mapped_at_creation: false,
2400 });
2401 let mesh_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
2402 label: Some("mesh uniform bind"),
2403 layout: &mesh_bind_layout,
2404 entries: &[wgpu::BindGroupEntry {
2405 binding: 0,
2406 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2407 buffer: &mesh_uniform_buf,
2408 offset: 0,
2409 size: NonZero::new(MESH_UNIFORM_SLOT),
2410 }),
2411 }],
2412 });
2413
2414 let surface_pipes = Pipelines::create(
2417 &device,
2418 output_format,
2419 msaa_samples,
2420 &globals_layout,
2421 &text_bind_layout,
2422 &image_bind_layout_nv12,
2423 &clip_pipeline_layout,
2424 &stencil_for_content,
2425 &stencil_for_clip_inc,
2426 &stencil_for_clip_dec,
2427 &clip_color_target,
2428 &clip_vertex_layout,
2429 &mesh_bind_layout,
2430 );
2431 let layer_pipes = Pipelines::create(
2432 &device,
2433 output_format,
2434 1,
2435 &globals_layout,
2436 &text_bind_layout,
2437 &image_bind_layout_nv12,
2438 &clip_pipeline_layout,
2439 &stencil_for_content,
2440 &stencil_for_clip_inc,
2441 &stencil_for_clip_dec,
2442 &clip_color_target,
2443 &clip_vertex_layout,
2444 &mesh_bind_layout,
2445 );
2446
2447 let slug_enabled = true;
2449
2450 let blur_ring = UploadRing::new(
2452 &device,
2453 "blur ring",
2454 1024 * 1024,
2455 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2456 );
2457
2458 let atlas_mask = init_atlas_mask(&device);
2460 let atlas_color = init_atlas_color(&device);
2461
2462 let ring_rect = UploadRing::new(
2464 &device,
2465 "ring rect",
2466 1 << 20,
2467 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2468 );
2469 let ring_border = UploadRing::new(
2470 &device,
2471 "ring border",
2472 1 << 20,
2473 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2474 );
2475 let ring_ellipse = UploadRing::new(
2476 &device,
2477 "ring ellipse",
2478 1 << 20,
2479 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2480 );
2481 let ring_ellipse_border = UploadRing::new(
2482 &device,
2483 "ring ellipse border",
2484 1 << 20,
2485 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2486 );
2487 let ring_arc = UploadRing::new(
2488 &device,
2489 "ring arc",
2490 1 << 20,
2491 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2492 );
2493 let ring_glyph_mask = UploadRing::new(
2494 &device,
2495 "ring glyph mask",
2496 1 << 20,
2497 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2498 );
2499 let ring_glyph_color = UploadRing::new(
2500 &device,
2501 "ring glyph color",
2502 1 << 20,
2503 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2504 );
2505 let ring_slug = UploadRing::new(
2506 &device,
2507 "ring slug",
2508 1 << 22,
2509 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2510 );
2511 let ring_clip = UploadRing::new(
2512 &device,
2513 "ring clip",
2514 1 << 16,
2515 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2516 );
2517 let blend_ring = UploadRing::new(
2518 &device,
2519 "ring blend",
2520 1 << 16,
2521 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2522 );
2523 let ring_projective = UploadRing::new(
2524 &device,
2525 "ring projective",
2526 1 << 16,
2527 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2528 );
2529 let ring_nv12 = UploadRing::new(
2530 &device,
2531 "ring nv12",
2532 1 << 20,
2533 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2534 );
2535 let ring_mesh_verts = UploadRing::new(
2536 &device,
2537 "ring mesh verts",
2538 1 << 22,
2539 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2540 );
2541 let ring_mesh_indices = UploadRing::new(
2542 &device,
2543 "ring mesh indices",
2544 1 << 22,
2545 wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
2546 );
2547
2548 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
2550 label: Some("temp ds"),
2551 size: wgpu::Extent3d {
2552 width: 1,
2553 height: 1,
2554 depth_or_array_layers: 1,
2555 },
2556 mip_level_count: 1,
2557 sample_count: 1,
2558 dimension: wgpu::TextureDimension::D2,
2559 format: wgpu::TextureFormat::Depth24PlusStencil8,
2560 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
2561 view_formats: &[],
2562 });
2563 let depth_stencil_view =
2564 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
2565
2566 let mut renderer = WgpuSceneRenderer {
2567 device,
2568 queue,
2569 output_format,
2570 output_width: 0,
2571 output_height: 0,
2572 pixels_per_point: 1.0,
2573
2574 surface_pipes,
2575 layer_pipes,
2576
2577 rects: InstancedPipe::new(ring_rect),
2578 borders: InstancedPipe::new(ring_border),
2579 ellipses: InstancedPipe::new(ring_ellipse),
2580 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
2581 arcs: InstancedPipe::new(ring_arc),
2582 glyph_mask: InstancedPipe::new(ring_glyph_mask),
2583 glyph_color: InstancedPipe::new(ring_glyph_color),
2584
2585 text_bind_layout,
2586
2587 image_bind_layout_rgba,
2588 image_bind_layout_nv12,
2589 image_sampler,
2590 layer_sampler,
2591 layer_sampler_linear,
2592
2593 blur_ring,
2594
2595 slug_enabled,
2596 slug_ring: ring_slug,
2597 slug_cache: slug::GlyphSlugCache::new(),
2598
2599 clip_ring: ring_clip,
2600
2601 nv12: InstancedPipe::new(ring_nv12),
2602
2603 mesh_verts: ring_mesh_verts,
2604 mesh_indices: ring_mesh_indices,
2605 mesh_uniform_buf,
2606 mesh_bind_layout,
2607 mesh_bind,
2608 mesh_uniform_head: 0,
2609 mesh_clip_stack: Vec::new(),
2610
2611 projective_ring: ring_projective,
2612 blend_ring,
2613 flatten_layer_ids: Vec::new(),
2614 blend_snapshots: std::collections::HashMap::new(),
2615 blend_copies: Vec::new(),
2616
2617 msaa_samples,
2618 depth_stencil_tex,
2619 depth_stencil_view,
2620 msaa_tex: None,
2621 msaa_view: None,
2622 globals_bind,
2623 globals_buf,
2624
2625 atlas_mask,
2626 atlas_color,
2627
2628 next_image_handle: 1,
2629 images: HashMap::new(),
2630 retained: HashMap::new(),
2631
2632 next_coverage_handle: 1,
2633 coverages: HashMap::new(),
2634
2635 frame_index: 0,
2636 image_bytes_total: 0,
2637 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
2640
2641 working_space: false,
2642 ws_tex: None,
2643 ws_view: None,
2644 ws_bind: None,
2645 display_pipeline: None,
2646 display_layout: None,
2647
2648 callback_resources: CallbackResources::default(),
2649 };
2650
2651 renderer.recreate_msaa_and_depth_stencil();
2652 renderer
2653 }
2654}
2655
2656impl WgpuSurfaceBackend {
2657 #[cfg(feature = "winit-surface")]
2658 pub async fn new_async(
2659 window: Arc<winit::window::Window>,
2660 ) -> anyhow::Result<WgpuSurfaceBackend> {
2661 Self::new_async_with_options(window, 4, PresentModePref::Auto).await
2662 }
2663
2664 #[cfg(feature = "winit-surface")]
2667 pub async fn new_async_with_msaa(
2668 window: Arc<winit::window::Window>,
2669 msaa_samples: u32,
2670 ) -> anyhow::Result<WgpuSurfaceBackend> {
2671 Self::new_async_with_options(window, msaa_samples, PresentModePref::Auto).await
2672 }
2673
2674 #[cfg(feature = "winit-surface")]
2677 pub async fn new_async_with_options(
2678 window: Arc<winit::window::Window>,
2679 msaa_samples: u32,
2680 present_mode: PresentModePref,
2681 ) -> anyhow::Result<WgpuSurfaceBackend> {
2682 let instance: Instance = if cfg!(target_arch = "wasm32") {
2683 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
2684 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
2685 wgpu::util::new_instance_with_webgpu_detection(desc).await
2686 } else {
2687 wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle())
2688 };
2689
2690 let surface = instance.create_surface(window.clone())?;
2691
2692 let adapter = instance
2693 .request_adapter(&wgpu::RequestAdapterOptions {
2694 power_preference: wgpu::PowerPreference::HighPerformance,
2695 compatible_surface: Some(&surface),
2696 force_fallback_adapter: false,
2697 apply_limit_buckets: false,
2698 })
2699 .await
2700 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
2701
2702 let limits = adapter.limits();
2703
2704 #[cfg(target_os = "linux")]
2705 let features = {
2706 let af = adapter.features();
2707 let mut f = wgpu::Features::empty();
2708 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD) {
2709 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD;
2710 }
2711 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF) {
2712 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF;
2713 }
2714 f
2715 };
2716 #[cfg(not(target_os = "linux"))]
2717 let features = wgpu::Features::empty();
2718
2719 let (device, queue) = adapter
2720 .request_device(&wgpu::DeviceDescriptor {
2721 label: Some("repose-rs device"),
2722 required_features: features,
2723 required_limits: limits,
2724 experimental_features: wgpu::ExperimentalFeatures::disabled(),
2725 memory_hints: wgpu::MemoryHints::default(),
2726 trace: wgpu::Trace::Off,
2727 })
2728 .await
2729 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
2730
2731 let size = window.inner_size();
2732
2733 let caps = surface.get_capabilities(&adapter);
2734
2735 let (format, view_format) = if cfg!(target_arch = "wasm32")
2736 && adapter
2737 .get_downlevel_capabilities()
2738 .flags
2739 .contains(wgpu::DownlevelFlags::SURFACE_VIEW_FORMATS)
2740 {
2741 let non_srgb = caps
2742 .formats
2743 .iter()
2744 .copied()
2745 .find(|f| !f.is_srgb())
2746 .unwrap_or(caps.formats[0]);
2747 (non_srgb, Some(non_srgb.add_srgb_suffix()))
2748 } else if cfg!(target_arch = "wasm32") {
2749 let fmt = caps
2750 .formats
2751 .iter()
2752 .copied()
2753 .find(|f| f.is_srgb())
2754 .unwrap_or(caps.formats[0]);
2755 (fmt, None)
2756 } else {
2757 let fmt = caps
2758 .formats
2759 .iter()
2760 .copied()
2761 .find(|f| f.is_srgb())
2762 .unwrap_or(caps.formats[0]);
2763 (fmt, None)
2764 };
2765
2766 let present_mode = pick_present_mode(&caps, present_mode);
2767 let alpha_mode = caps.alpha_modes[0];
2768
2769 let render_format = view_format.unwrap_or(format);
2770 let msaa_samples = pick_surface_msaa(&adapter, format, msaa_samples);
2771 let renderer = WgpuSceneRenderer::from_device(device, queue, render_format, msaa_samples);
2772
2773 let view_formats = view_format.into_iter().collect::<Vec<_>>();
2774
2775 let config = wgpu::SurfaceConfiguration {
2776 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
2777 format,
2778 width: size.width.max(1),
2779 height: size.height.max(1),
2780 present_mode,
2781 alpha_mode,
2782 color_space: wgpu::SurfaceColorSpace::Auto,
2783 view_formats,
2784 desired_maximum_frame_latency: 1,
2785 };
2786 surface.configure(&renderer.device, &config);
2787
2788 Ok(WgpuSurfaceBackend {
2789 surface: Some(surface),
2790 surface_config: Some(config),
2791 renderer,
2792 })
2793 }
2794
2795 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2796 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2797 pollster::block_on(Self::new_async(window))
2798 }
2799
2800 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2801 pub fn new_with_msaa(
2802 window: Arc<winit::window::Window>,
2803 msaa_samples: u32,
2804 ) -> anyhow::Result<WgpuSurfaceBackend> {
2805 pollster::block_on(Self::new_async_with_msaa(window, msaa_samples))
2806 }
2807
2808 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2809 pub fn new_with_options(
2810 window: Arc<winit::window::Window>,
2811 msaa_samples: u32,
2812 present_mode: PresentModePref,
2813 ) -> anyhow::Result<WgpuSurfaceBackend> {
2814 pollster::block_on(Self::new_async_with_options(
2815 window,
2816 msaa_samples,
2817 present_mode,
2818 ))
2819 }
2820
2821 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2822 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2823 anyhow::bail!("Use WgpuSurfaceBackend::new_async(window).await on wasm32")
2824 }
2825
2826 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2827 pub fn new_with_msaa(
2828 _window: Arc<winit::window::Window>,
2829 _msaa_samples: u32,
2830 ) -> anyhow::Result<WgpuSurfaceBackend> {
2831 anyhow::bail!("Use WgpuSurfaceBackend::new_async_with_msaa(window, msaa).await on wasm32")
2832 }
2833
2834 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2835 pub fn new_with_options(
2836 _window: Arc<winit::window::Window>,
2837 _msaa_samples: u32,
2838 _present_mode: PresentModePref,
2839 ) -> anyhow::Result<WgpuSurfaceBackend> {
2840 anyhow::bail!(
2841 "Use WgpuSurfaceBackend::new_async_with_options(window, msaa, mode).await on wasm32"
2842 )
2843 }
2844}
2845
2846fn pick_present_mode(caps: &wgpu::SurfaceCapabilities, pref: PresentModePref) -> wgpu::PresentMode {
2849 let auto = || {
2850 caps.present_modes
2851 .iter()
2852 .copied()
2853 .find(|m| *m == wgpu::PresentMode::Fifo)
2854 .or_else(|| {
2855 caps.present_modes
2856 .iter()
2857 .copied()
2858 .find(|m| *m == wgpu::PresentMode::Mailbox)
2859 })
2860 .unwrap_or(wgpu::PresentMode::Immediate)
2861 };
2862 match pref {
2863 PresentModePref::Auto => auto(),
2864 PresentModePref::Fifo if caps.present_modes.contains(&wgpu::PresentMode::Fifo) => {
2865 wgpu::PresentMode::Fifo
2866 }
2867 PresentModePref::Mailbox if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) => {
2868 wgpu::PresentMode::Mailbox
2869 }
2870 PresentModePref::Immediate
2871 if caps.present_modes.contains(&wgpu::PresentMode::Immediate) =>
2872 {
2873 wgpu::PresentMode::Immediate
2874 }
2875 _ => auto(),
2876 }
2877}
2878
2879pub fn pick_surface_msaa(
2882 adapter: &wgpu::Adapter,
2883 format: wgpu::TextureFormat,
2884 requested: u32,
2885) -> u32 {
2886 let requested = requested.max(1);
2887 let color_feat = adapter.get_texture_format_features(format);
2888 let depth_feat = adapter.get_texture_format_features(wgpu::TextureFormat::Depth24PlusStencil8);
2889 let supported = |n: u32| {
2890 color_feat.flags.sample_count_supported(n)
2891 && color_feat
2892 .flags
2893 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
2894 && depth_feat.flags.sample_count_supported(n)
2895 };
2896 let mut candidates = vec![requested];
2897 for n in [8, 4, 2, 1] {
2898 if n < requested {
2899 candidates.push(n);
2900 }
2901 }
2902 let chosen = candidates.into_iter().find(|&n| supported(n)).unwrap_or(1);
2903 if chosen != requested {
2904 log::info!("requested MSAA x{requested}, using x{chosen}");
2905 }
2906 chosen
2907}
2908
2909impl WgpuSceneRenderer {
2910 pub fn set_image_from_bytes(
2913 &mut self,
2914 handle: u64,
2915 data: &[u8],
2916 srgb: bool,
2917 ) -> anyhow::Result<()> {
2918 let img = image::load_from_memory(data)?;
2919 let rgba = img.to_rgba8();
2920 let (w, h) = rgba.dimensions();
2921 self.set_image_rgba8(handle, w, h, &rgba, srgb)
2922 }
2923
2924 pub fn set_image_rgba8(
2925 &mut self,
2926 handle: u64,
2927 w: u32,
2928 h: u32,
2929 rgba: &[u8],
2930 srgb: bool,
2931 ) -> anyhow::Result<()> {
2932 let expected = (w as usize) * (h as usize) * 4;
2933 if rgba.len() < expected {
2934 return Err(anyhow::anyhow!(
2935 "RGBA buffer too small: {} < {}",
2936 rgba.len(),
2937 expected
2938 ));
2939 }
2940
2941 let format = if srgb {
2942 wgpu::TextureFormat::Rgba8UnormSrgb
2943 } else {
2944 wgpu::TextureFormat::Rgba8Unorm
2945 };
2946
2947 let needs_recreate = match self.images.get(&handle) {
2948 Some(ImageTex::Rgba {
2949 w: cw,
2950 h: ch,
2951 format: cf,
2952 ..
2953 }) => *cw != w || *ch != h || *cf != format,
2954 _ => true,
2955 };
2956
2957 if needs_recreate {
2958 self.remove_image(handle);
2959
2960 let (tex, bind) = self.create_rgba_tex(w, h, format);
2961 let bytes = (w as u64) * (h as u64) * 4;
2962 self.image_bytes_total += bytes;
2963
2964 self.images.insert(
2965 handle,
2966 ImageTex::Rgba {
2967 tex,
2968 bind,
2969 w,
2970 h,
2971 format,
2972 last_used_frame: self.frame_index,
2973 bytes,
2974 },
2975 );
2976 }
2977
2978 self.retained.insert(
2979 handle,
2980 RetainedImage {
2981 w,
2982 h,
2983 format,
2984 rgba: rgba[..expected].to_vec(),
2985 },
2986 );
2987
2988 let tex = match self.images.get(&handle) {
2989 Some(ImageTex::Rgba { tex, .. }) => tex,
2990 _ => unreachable!(),
2991 };
2992
2993 self.queue.write_texture(
2994 wgpu::TexelCopyTextureInfo {
2995 texture: tex,
2996 mip_level: 0,
2997 origin: wgpu::Origin3d::ZERO,
2998 aspect: wgpu::TextureAspect::All,
2999 },
3000 &rgba[..expected],
3001 wgpu::TexelCopyBufferLayout {
3002 offset: 0,
3003 bytes_per_row: Some(4 * w),
3004 rows_per_image: Some(h),
3005 },
3006 wgpu::Extent3d {
3007 width: w,
3008 height: h,
3009 depth_or_array_layers: 1,
3010 },
3011 );
3012
3013 self.evict_budget_excess();
3015
3016 Ok(())
3017 }
3018
3019 fn create_rgba_tex(
3022 &self,
3023 w: u32,
3024 h: u32,
3025 format: wgpu::TextureFormat,
3026 ) -> (wgpu::Texture, wgpu::BindGroup) {
3027 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3028 label: Some("user image rgba"),
3029 size: wgpu::Extent3d {
3030 width: w,
3031 height: h,
3032 depth_or_array_layers: 1,
3033 },
3034 mip_level_count: 1,
3035 sample_count: 1,
3036 dimension: wgpu::TextureDimension::D2,
3037 format,
3038 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3039 view_formats: &[],
3040 });
3041 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3042
3043 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3044 label: Some("image bind rgba"),
3045 layout: &self.image_bind_layout_rgba,
3046 entries: &[
3047 wgpu::BindGroupEntry {
3048 binding: 0,
3049 resource: wgpu::BindingResource::TextureView(&view),
3050 },
3051 wgpu::BindGroupEntry {
3052 binding: 1,
3053 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3054 },
3055 ],
3056 });
3057
3058 (tex, bind)
3059 }
3060
3061 pub fn register_native_texture(
3063 &mut self,
3064 view: &wgpu::TextureView,
3065 width: u32,
3066 height: u32,
3067 ) -> u64 {
3068 let handle = self.next_image_handle;
3069 self.next_image_handle += 1;
3070 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3071 label: Some("user native image"),
3072 layout: &self.image_bind_layout_rgba,
3073 entries: &[
3074 wgpu::BindGroupEntry {
3075 binding: 0,
3076 resource: wgpu::BindingResource::TextureView(view),
3077 },
3078 wgpu::BindGroupEntry {
3079 binding: 1,
3080 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3081 },
3082 ],
3083 });
3084 self.images.insert(
3085 handle,
3086 ImageTex::User {
3087 bind,
3088 w: width,
3089 h: height,
3090 last_used_frame: self.frame_index,
3091 bytes: 0,
3092 },
3093 );
3094 handle
3095 }
3096
3097 pub fn register_native_texture_with_sampler(
3099 &mut self,
3100 view: &wgpu::TextureView,
3101 sampler_desc: wgpu::SamplerDescriptor<'_>,
3102 width: u32,
3103 height: u32,
3104 ) -> u64 {
3105 let handle = self.next_image_handle;
3106 self.next_image_handle += 1;
3107 let sampler = self.device.create_sampler(&sampler_desc);
3108 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3109 label: Some("user native image sampleropts"),
3110 layout: &self.image_bind_layout_rgba,
3111 entries: &[
3112 wgpu::BindGroupEntry {
3113 binding: 0,
3114 resource: wgpu::BindingResource::TextureView(view),
3115 },
3116 wgpu::BindGroupEntry {
3117 binding: 1,
3118 resource: wgpu::BindingResource::Sampler(&sampler),
3119 },
3120 ],
3121 });
3122 self.images.insert(
3123 handle,
3124 ImageTex::User {
3125 bind,
3126 w: width,
3127 h: height,
3128 last_used_frame: self.frame_index,
3129 bytes: 0,
3130 },
3131 );
3132 handle
3133 }
3134
3135 pub fn update_native_texture(&mut self, handle: u64, view: &wgpu::TextureView) {
3137 let Some(entry) = self.images.get_mut(&handle) else {
3138 log::warn!("update_native_texture: handle {handle} not found");
3139 return;
3140 };
3141 let w = match entry {
3142 ImageTex::User { w, .. } => *w,
3143 ImageTex::Rgba { w, .. } => *w,
3144 _ => {
3145 log::warn!("update_native_texture: handle {handle} is not rgba/user");
3146 return;
3147 }
3148 };
3149 let h = match entry {
3150 ImageTex::User { h, .. } => *h,
3151 ImageTex::Rgba { h, .. } => *h,
3152 _ => 0,
3153 };
3154 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3155 label: Some("user native image update"),
3156 layout: &self.image_bind_layout_rgba,
3157 entries: &[
3158 wgpu::BindGroupEntry {
3159 binding: 0,
3160 resource: wgpu::BindingResource::TextureView(view),
3161 },
3162 wgpu::BindGroupEntry {
3163 binding: 1,
3164 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3165 },
3166 ],
3167 });
3168 *entry = ImageTex::User {
3169 bind,
3170 w,
3171 h,
3172 last_used_frame: self.frame_index,
3173 bytes: 0,
3174 };
3175 }
3176
3177 pub fn set_image_nv12(
3178 &mut self,
3179 handle: u64,
3180 w: u32,
3181 h: u32,
3182 y: &[u8],
3183 uv: &[u8],
3184 color_info: ColorInfo,
3185 ) -> anyhow::Result<()> {
3186 let y_expected = (w as usize) * (h as usize);
3187 let uv_w = w.div_ceil(2);
3188 let uv_h = h.div_ceil(2);
3189 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
3190
3191 if y.len() < y_expected {
3192 return Err(anyhow::anyhow!("Y plane too small"));
3193 }
3194 if uv.len() < uv_expected {
3195 return Err(anyhow::anyhow!("UV plane too small"));
3196 }
3197
3198 let needs_recreate = match self.images.get(&handle) {
3199 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
3200 _ => true,
3201 };
3202
3203 let yuv = color_info.to_yuv_transform();
3205 let yuv_raw = YuvTransformRaw {
3206 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
3207 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
3208 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
3209 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
3210 };
3211
3212 if needs_recreate {
3213 self.remove_image(handle);
3214
3215 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
3216 label: Some("nv12 Y"),
3217 size: wgpu::Extent3d {
3218 width: w,
3219 height: h,
3220 depth_or_array_layers: 1,
3221 },
3222 mip_level_count: 1,
3223 sample_count: 1,
3224 dimension: wgpu::TextureDimension::D2,
3225 format: wgpu::TextureFormat::R8Unorm,
3226 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3227 view_formats: &[],
3228 });
3229 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
3230
3231 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
3232 label: Some("nv12 UV"),
3233 size: wgpu::Extent3d {
3234 width: uv_w,
3235 height: uv_h,
3236 depth_or_array_layers: 1,
3237 },
3238 mip_level_count: 1,
3239 sample_count: 1,
3240 dimension: wgpu::TextureDimension::D2,
3241 format: wgpu::TextureFormat::Rg8Unorm,
3242 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3243 view_formats: &[],
3244 });
3245 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
3246
3247 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3249 label: Some("nv12 yuv transform"),
3250 size: std::mem::size_of::<YuvTransformRaw>() as u64,
3251 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3252 mapped_at_creation: false,
3253 });
3254
3255 self.queue
3257 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3258
3259 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3260 label: Some("nv12 bind"),
3261 layout: &self.image_bind_layout_nv12,
3262 entries: &[
3263 wgpu::BindGroupEntry {
3264 binding: 0,
3265 resource: wgpu::BindingResource::TextureView(&view_y),
3266 },
3267 wgpu::BindGroupEntry {
3268 binding: 1,
3269 resource: wgpu::BindingResource::TextureView(&view_uv),
3270 },
3271 wgpu::BindGroupEntry {
3272 binding: 2,
3273 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3274 },
3275 wgpu::BindGroupEntry {
3276 binding: 3,
3277 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3278 buffer: &yuv_buf,
3279 offset: 0,
3280 size: None,
3281 }),
3282 },
3283 ],
3284 });
3285
3286 let bytes = (w as u64) * (h as u64)
3287 + (uv_w as u64) * (uv_h as u64) * 2
3288 + std::mem::size_of::<YuvTransformRaw>() as u64;
3289 self.image_bytes_total += bytes;
3290
3291 self.images.insert(
3292 handle,
3293 ImageTex::Nv12 {
3294 tex_y,
3295 tex_uv,
3296 bind,
3297 yuv_buf,
3298 w,
3299 h,
3300 color_info,
3301 last_used_frame: self.frame_index,
3302 bytes,
3303 },
3304 );
3305 } else {
3306 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
3308 self.queue
3309 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3310 }
3311 }
3312
3313 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
3314 Some(ImageTex::Nv12 {
3315 tex_y,
3316 tex_uv,
3317 bind,
3318 ..
3319 }) => (tex_y, tex_uv, bind),
3320 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
3321 };
3322
3323 self.queue.write_texture(
3324 wgpu::TexelCopyTextureInfo {
3325 texture: tex_y,
3326 mip_level: 0,
3327 origin: wgpu::Origin3d::ZERO,
3328 aspect: wgpu::TextureAspect::All,
3329 },
3330 &y[..y_expected],
3331 wgpu::TexelCopyBufferLayout {
3332 offset: 0,
3333 bytes_per_row: Some(w),
3334 rows_per_image: Some(h),
3335 },
3336 wgpu::Extent3d {
3337 width: w,
3338 height: h,
3339 depth_or_array_layers: 1,
3340 },
3341 );
3342
3343 self.queue.write_texture(
3344 wgpu::TexelCopyTextureInfo {
3345 texture: tex_uv,
3346 mip_level: 0,
3347 origin: wgpu::Origin3d::ZERO,
3348 aspect: wgpu::TextureAspect::All,
3349 },
3350 &uv[..uv_expected],
3351 wgpu::TexelCopyBufferLayout {
3352 offset: 0,
3353 bytes_per_row: Some(2 * uv_w),
3354 rows_per_image: Some(uv_h),
3355 },
3356 wgpu::Extent3d {
3357 width: uv_w,
3358 height: uv_h,
3359 depth_or_array_layers: 1,
3360 },
3361 );
3362
3363 self.evict_budget_excess();
3364 Ok(())
3365 }
3366
3367 pub fn set_image_planes(
3368 &mut self,
3369 handle: u64,
3370 w: u32,
3371 h: u32,
3372 pixel_format: PixelFormat,
3373 planes: &[&[u8]],
3374 color_info: ColorInfo,
3375 ) -> anyhow::Result<()> {
3376 match pixel_format {
3377 PixelFormat::Nv12 => {
3378 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
3379 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
3380 self.set_image_nv12(handle, w, h, y, uv, color_info)
3381 }
3382 PixelFormat::P010 => {
3383 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
3384 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
3385 self.set_image_p010(handle, w, h, y, uv, color_info)
3386 }
3387 PixelFormat::I420 | PixelFormat::I444 => Err(anyhow::anyhow!(
3388 "I420/I444 not implemented and unlikely -> cheap to convert to NV12 (better for the GPU too)"
3389 )),
3390 PixelFormat::Rgba => {
3391 let rgba = planes
3392 .first()
3393 .ok_or(anyhow::anyhow!("missing RGBA plane"))?;
3394 self.set_image_rgba8(handle, w, h, rgba, false)
3395 }
3396 }
3397 }
3398
3399 fn set_image_p010(
3400 &mut self,
3401 handle: u64,
3402 w: u32,
3403 h: u32,
3404 y: &[u8],
3405 uv: &[u8],
3406 color_info: ColorInfo,
3407 ) -> anyhow::Result<()> {
3408 let uv_w = w.div_ceil(2);
3409 let uv_h = h.div_ceil(2);
3410
3411 let y_expected = (w as usize) * (h as usize) * 2;
3412 let uv_expected = (uv_w as usize) * (uv_h as usize) * 4;
3413
3414 if y.len() < y_expected {
3415 return Err(anyhow::anyhow!("P010 Y plane too small"));
3416 }
3417 if uv.len() < uv_expected {
3418 return Err(anyhow::anyhow!("P010 UV plane too small"));
3419 }
3420
3421 let needs_recreate = match self.images.get(&handle) {
3425 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
3426 _ => true,
3427 };
3428
3429 let yuv = color_info.to_yuv_transform();
3430 let yuv_raw = YuvTransformRaw {
3431 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
3432 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
3433 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
3434 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
3435 };
3436
3437 if needs_recreate {
3438 self.remove_image(handle);
3439
3440 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
3441 label: Some("p010 Y"),
3442 size: wgpu::Extent3d {
3443 width: w,
3444 height: h,
3445 depth_or_array_layers: 1,
3446 },
3447 mip_level_count: 1,
3448 sample_count: 1,
3449 dimension: wgpu::TextureDimension::D2,
3450 format: wgpu::TextureFormat::R16Unorm,
3451 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3452 view_formats: &[],
3453 });
3454 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
3455
3456 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
3457 label: Some("p010 UV"),
3458 size: wgpu::Extent3d {
3459 width: uv_w,
3460 height: uv_h,
3461 depth_or_array_layers: 1,
3462 },
3463 mip_level_count: 1,
3464 sample_count: 1,
3465 dimension: wgpu::TextureDimension::D2,
3466 format: wgpu::TextureFormat::Rg16Unorm,
3467 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3468 view_formats: &[],
3469 });
3470 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
3471
3472 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3473 label: Some("p010 yuv transform"),
3474 size: std::mem::size_of::<YuvTransformRaw>() as u64,
3475 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3476 mapped_at_creation: false,
3477 });
3478 self.queue
3479 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3480
3481 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3482 label: Some("p010 bind"),
3483 layout: &self.image_bind_layout_nv12,
3484 entries: &[
3485 wgpu::BindGroupEntry {
3486 binding: 0,
3487 resource: wgpu::BindingResource::TextureView(&view_y),
3488 },
3489 wgpu::BindGroupEntry {
3490 binding: 1,
3491 resource: wgpu::BindingResource::TextureView(&view_uv),
3492 },
3493 wgpu::BindGroupEntry {
3494 binding: 2,
3495 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3496 },
3497 wgpu::BindGroupEntry {
3498 binding: 3,
3499 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3500 buffer: &yuv_buf,
3501 offset: 0,
3502 size: None,
3503 }),
3504 },
3505 ],
3506 });
3507
3508 let bytes = (w as u64) * (h as u64) * 2
3509 + (uv_w as u64) * (uv_h as u64) * 4
3510 + std::mem::size_of::<YuvTransformRaw>() as u64;
3511 self.image_bytes_total += bytes;
3512
3513 self.images.insert(
3514 handle,
3515 ImageTex::Nv12 {
3516 tex_y,
3517 tex_uv,
3518 bind,
3519 yuv_buf,
3520 w,
3521 h,
3522 color_info,
3523 last_used_frame: self.frame_index,
3524 bytes,
3525 },
3526 );
3527 } else {
3528 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
3529 self.queue
3530 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3531 }
3532 }
3533
3534 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
3535 Some(ImageTex::Nv12 {
3536 tex_y,
3537 tex_uv,
3538 bind,
3539 ..
3540 }) => (tex_y, tex_uv, bind),
3541 _ => return Err(anyhow::anyhow!("Handle is not P010/NV12")),
3542 };
3543
3544 self.queue.write_texture(
3545 wgpu::TexelCopyTextureInfo {
3546 texture: tex_y,
3547 mip_level: 0,
3548 origin: wgpu::Origin3d::ZERO,
3549 aspect: wgpu::TextureAspect::All,
3550 },
3551 &y[..y_expected],
3552 wgpu::TexelCopyBufferLayout {
3553 offset: 0,
3554 bytes_per_row: Some(w * 2),
3555 rows_per_image: Some(h),
3556 },
3557 wgpu::Extent3d {
3558 width: w,
3559 height: h,
3560 depth_or_array_layers: 1,
3561 },
3562 );
3563 self.queue.write_texture(
3564 wgpu::TexelCopyTextureInfo {
3565 texture: tex_uv,
3566 mip_level: 0,
3567 origin: wgpu::Origin3d::ZERO,
3568 aspect: wgpu::TextureAspect::All,
3569 },
3570 &uv[..uv_expected],
3571 wgpu::TexelCopyBufferLayout {
3572 offset: 0,
3573 bytes_per_row: Some(uv_w * 4),
3574 rows_per_image: Some(uv_h),
3575 },
3576 wgpu::Extent3d {
3577 width: uv_w,
3578 height: uv_h,
3579 depth_or_array_layers: 1,
3580 },
3581 );
3582
3583 self.evict_budget_excess();
3584 Ok(())
3585 }
3586
3587 #[cfg(target_os = "linux")]
3588 pub fn set_image_dmabuf(
3589 &mut self,
3590 handle: u64,
3591 w: u32,
3592 h: u32,
3593 fds: Vec<std::os::unix::io::OwnedFd>,
3594 modifier: u64,
3595 strides: Vec<u32>,
3596 offsets: Vec<u64>,
3597 color_info: ColorInfo,
3598 ) -> anyhow::Result<()> {
3599 log::info!(
3600 "set_image_dmabuf handle={handle} {}x{} fds={} modifier=0x{modifier:x}",
3601 w,
3602 h,
3603 fds.len()
3604 );
3605
3606 self.remove_image(handle);
3607
3608 let yuv = color_info.to_yuv_transform();
3609 let yuv_raw = YuvTransformRaw {
3610 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
3611 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
3612 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
3613 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
3614 };
3615
3616 if fds.len() != 2 {
3617 return Err(anyhow::anyhow!(
3618 "unsupported fd count {} - need exactly 2 for separate Y/UV planes",
3619 fds.len()
3620 ));
3621 }
3622
3623 let uv_w = w.div_ceil(2);
3624 let uv_h = h.div_ceil(2);
3625
3626 let hal_y_desc = wgpu::hal::TextureDescriptor {
3627 label: Some("dmabuf y"),
3628 size: wgpu::Extent3d {
3629 width: w,
3630 height: h,
3631 depth_or_array_layers: 1,
3632 },
3633 mip_level_count: 1,
3634 sample_count: 1,
3635 dimension: wgpu::TextureDimension::D2,
3636 format: wgpu::TextureFormat::R8Unorm,
3637 usage: wgpu::wgt::TextureUses::RESOURCE,
3638 memory_flags: wgpu::hal::MemoryFlags::empty(),
3639 view_formats: vec![],
3640 };
3641 let hal_uv_desc = wgpu::hal::TextureDescriptor {
3642 label: Some("dmabuf uv"),
3643 size: wgpu::Extent3d {
3644 width: uv_w,
3645 height: uv_h,
3646 depth_or_array_layers: 1,
3647 },
3648 mip_level_count: 1,
3649 sample_count: 1,
3650 dimension: wgpu::TextureDimension::D2,
3651 format: wgpu::TextureFormat::Rg8Unorm,
3652 usage: wgpu::wgt::TextureUses::RESOURCE,
3653 memory_flags: wgpu::hal::MemoryFlags::empty(),
3654 view_formats: vec![],
3655 };
3656
3657 let wgpu_y_desc = wgpu::TextureDescriptor {
3658 label: Some("dmabuf y"),
3659 size: wgpu::Extent3d {
3660 width: w,
3661 height: h,
3662 depth_or_array_layers: 1,
3663 },
3664 mip_level_count: 1,
3665 sample_count: 1,
3666 dimension: wgpu::TextureDimension::D2,
3667 format: wgpu::TextureFormat::R8Unorm,
3668 usage: wgpu::TextureUsages::TEXTURE_BINDING,
3669 view_formats: &[],
3670 };
3671 let wgpu_uv_desc = wgpu::TextureDescriptor {
3672 label: Some("dmabuf uv"),
3673 size: wgpu::Extent3d {
3674 width: uv_w,
3675 height: uv_h,
3676 depth_or_array_layers: 1,
3677 },
3678 mip_level_count: 1,
3679 sample_count: 1,
3680 dimension: wgpu::TextureDimension::D2,
3681 format: wgpu::TextureFormat::Rg8Unorm,
3682 usage: wgpu::TextureUsages::TEXTURE_BINDING,
3683 view_formats: &[],
3684 };
3685
3686 let (tex_y, view_y, tex_uv, view_uv) = unsafe {
3687 let hal_guard = self
3688 .device
3689 .as_hal::<wgpu::hal::vulkan::Api>()
3690 .ok_or_else(|| {
3691 log::warn!("as_hal::<vulkan::Api> returned None");
3692 anyhow::anyhow!("Device is not Vulkan")
3693 })?;
3694
3695 let mut fds = fds;
3696 let uv_fd = fds.remove(1);
3697 let y_fd = fds.remove(0);
3698
3699 let yt = hal_guard
3700 .texture_from_dmabuf_fd(y_fd, &hal_y_desc, modifier, strides[0] as u64, offsets[0])
3701 .map_err(|e| anyhow::anyhow!("import Y dmabuf: {e:?}"))?;
3702 log::info!("imported Y dmabuf OK");
3703
3704 let uvt = hal_guard
3705 .texture_from_dmabuf_fd(
3706 uv_fd,
3707 &hal_uv_desc,
3708 modifier,
3709 strides[1] as u64,
3710 offsets[1],
3711 )
3712 .map_err(|e| anyhow::anyhow!("import UV dmabuf: {e:?}"))?;
3713 log::info!("imported UV dmabuf OK");
3714
3715 drop(hal_guard);
3716
3717 let tex_y = self
3718 .device
3719 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
3720 yt,
3721 &wgpu_y_desc,
3722 wgpu::wgt::TextureUses::UNINITIALIZED,
3723 );
3724 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
3725
3726 let tex_uv = self
3727 .device
3728 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
3729 uvt,
3730 &wgpu_uv_desc,
3731 wgpu::wgt::TextureUses::UNINITIALIZED,
3732 );
3733 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
3734
3735 (tex_y, view_y, tex_uv, view_uv)
3736 };
3737
3738 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3739 label: Some("dmabuf yuv transform"),
3740 size: std::mem::size_of::<YuvTransformRaw>() as u64,
3741 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3742 mapped_at_creation: false,
3743 });
3744 self.queue
3745 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3746
3747 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3748 label: Some("dmabuf nv12 bind"),
3749 layout: &self.image_bind_layout_nv12,
3750 entries: &[
3751 wgpu::BindGroupEntry {
3752 binding: 0,
3753 resource: wgpu::BindingResource::TextureView(&view_y),
3754 },
3755 wgpu::BindGroupEntry {
3756 binding: 1,
3757 resource: wgpu::BindingResource::TextureView(&view_uv),
3758 },
3759 wgpu::BindGroupEntry {
3760 binding: 2,
3761 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3762 },
3763 wgpu::BindGroupEntry {
3764 binding: 3,
3765 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3766 buffer: &yuv_buf,
3767 offset: 0,
3768 size: None,
3769 }),
3770 },
3771 ],
3772 });
3773
3774 let bytes = (w as u64) * (h as u64)
3775 + (uv_w as u64) * (uv_h as u64) * 2
3776 + std::mem::size_of::<YuvTransformRaw>() as u64;
3777
3778 self.images.insert(
3779 handle,
3780 ImageTex::Nv12 {
3781 tex_y,
3782 tex_uv,
3783 bind,
3784 yuv_buf,
3785 w,
3786 h,
3787 color_info,
3788 last_used_frame: self.frame_index,
3789 bytes,
3790 },
3791 );
3792
3793 self.evict_budget_excess();
3794 Ok(())
3795 }
3796
3797 pub fn remove_image(&mut self, handle: u64) {
3798 if let Some(img) = self.images.remove(&handle) {
3799 let b = match &img {
3800 ImageTex::Rgba { bytes, .. } => *bytes,
3801 ImageTex::Nv12 { bytes, .. } => *bytes,
3802 ImageTex::User { bytes, .. } => *bytes,
3803 };
3804 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
3805 }
3806 self.retained.remove(&handle);
3807 }
3808
3809 fn evict_image_gpu(&mut self, handle: u64) -> u64 {
3810 let Some(img) = self.images.remove(&handle) else {
3811 return 0;
3812 };
3813 let b = match &img {
3814 ImageTex::Rgba { bytes, .. } => *bytes,
3815 ImageTex::Nv12 { bytes, .. } => *bytes,
3816 ImageTex::User { bytes, .. } => *bytes,
3817 };
3818 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
3819 b
3820 }
3821
3822 fn revive_retained_image(&mut self, handle: u64) -> bool {
3823 if self.images.contains_key(&handle) {
3824 return true;
3825 }
3826 let Some(r) = self.retained.get(&handle).cloned() else {
3827 return false;
3828 };
3829 let (tex, bind) = self.create_rgba_tex(r.w, r.h, r.format);
3830
3831 self.queue.write_texture(
3832 wgpu::TexelCopyTextureInfo {
3833 texture: &tex,
3834 mip_level: 0,
3835 origin: wgpu::Origin3d::ZERO,
3836 aspect: wgpu::TextureAspect::All,
3837 },
3838 &r.rgba,
3839 wgpu::TexelCopyBufferLayout {
3840 offset: 0,
3841 bytes_per_row: Some(4 * r.w),
3842 rows_per_image: Some(r.h),
3843 },
3844 wgpu::Extent3d {
3845 width: r.w,
3846 height: r.h,
3847 depth_or_array_layers: 1,
3848 },
3849 );
3850
3851 let bytes = (r.w as u64) * (r.h as u64) * 4;
3852 self.image_bytes_total += bytes;
3853 self.images.insert(
3854 handle,
3855 ImageTex::Rgba {
3856 tex,
3857 bind,
3858 w: r.w,
3859 h: r.h,
3860 format: r.format,
3861 last_used_frame: self.frame_index,
3862 bytes,
3863 },
3864 );
3865 true
3866 }
3867
3868 fn resolve_image_for_draw(&mut self, handle: u64) -> Option<(u32, u32, bool)> {
3869 if let Some(t) = self.images.get_mut(&handle) {
3870 return match t {
3871 ImageTex::Rgba {
3872 w,
3873 h,
3874 last_used_frame,
3875 ..
3876 } => {
3877 *last_used_frame = self.frame_index;
3878 Some((*w, *h, false))
3879 }
3880 ImageTex::User {
3881 w,
3882 h,
3883 last_used_frame,
3884 ..
3885 } => {
3886 *last_used_frame = self.frame_index;
3887 Some((*w, *h, false))
3888 }
3889 ImageTex::Nv12 {
3890 w,
3891 h,
3892 last_used_frame,
3893 ..
3894 } => {
3895 *last_used_frame = self.frame_index;
3896 Some((*w, *h, true))
3897 }
3898 };
3899 }
3900 if self.revive_retained_image(handle)
3901 && let Some(ImageTex::Rgba {
3902 w,
3903 h,
3904 last_used_frame,
3905 ..
3906 }) = self.images.get_mut(&handle)
3907 {
3908 *last_used_frame = self.frame_index;
3909 return Some((*w, *h, false));
3910 }
3911 None
3912 }
3913
3914 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
3916 let handle = self.next_image_handle;
3917 self.next_image_handle += 1;
3918 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
3919 log::error!("Failed to register image: {e}");
3920 }
3921 handle
3922 }
3923
3924 pub fn register_image_rgba8(&mut self, w: u32, h: u32, rgba: &[u8], srgb: bool) -> u64 {
3929 let handle = self.next_image_handle;
3930 self.next_image_handle += 1;
3931 if let Err(e) = self.set_image_rgba8(handle, w, h, rgba, srgb) {
3932 log::error!("Failed to register image: {e}");
3933 }
3934 handle
3935 }
3936
3937 pub fn register_coverage_a8(&mut self, w: u32, h: u32, coverage: &[u8]) -> u64 {
3943 let expected = (w as usize) * (h as usize);
3944 if coverage.len() < expected || w == 0 || h == 0 {
3945 log::error!("Coverage buffer too small: {} < {expected}", coverage.len());
3946 return 0;
3947 }
3948 let handle = self.next_coverage_handle;
3949 self.next_coverage_handle += 1;
3950
3951 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3952 label: Some("coverage tile a8"),
3953 size: wgpu::Extent3d {
3954 width: w,
3955 height: h,
3956 depth_or_array_layers: 1,
3957 },
3958 mip_level_count: 1,
3959 sample_count: 1,
3960 dimension: wgpu::TextureDimension::D2,
3961 format: wgpu::TextureFormat::R8Unorm,
3962 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3963 view_formats: &[],
3964 });
3965 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3966 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3967 label: Some("coverage bind a8"),
3968 layout: &self.image_bind_layout_rgba,
3969 entries: &[
3970 wgpu::BindGroupEntry {
3971 binding: 0,
3972 resource: wgpu::BindingResource::TextureView(&view),
3973 },
3974 wgpu::BindGroupEntry {
3975 binding: 1,
3976 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3977 },
3978 ],
3979 });
3980 self.queue.write_texture(
3981 wgpu::TexelCopyTextureInfo {
3982 texture: &tex,
3983 mip_level: 0,
3984 origin: wgpu::Origin3d::ZERO,
3985 aspect: wgpu::TextureAspect::All,
3986 },
3987 &coverage[..expected],
3988 wgpu::TexelCopyBufferLayout {
3989 offset: 0,
3990 bytes_per_row: Some(w),
3991 rows_per_image: Some(h),
3992 },
3993 wgpu::Extent3d {
3994 width: w,
3995 height: h,
3996 depth_or_array_layers: 1,
3997 },
3998 );
3999 let bytes = (w as u64) * (h as u64);
4000 self.image_bytes_total += bytes;
4001 self.coverages.insert(
4002 handle,
4003 CoverageTex {
4004 tex,
4005 bind,
4006 w,
4007 h,
4008 last_used_frame: self.frame_index,
4009 bytes,
4010 },
4011 );
4012 self.evict_budget_excess();
4013 handle
4014 }
4015
4016 pub fn remove_coverage(&mut self, handle: u64) {
4018 if let Some(tile) = self.coverages.remove(&handle) {
4019 self.image_bytes_total = self.image_bytes_total.saturating_sub(tile.bytes);
4020 }
4021 }
4022
4023 pub fn coverage_dimensions(&mut self, handle: u64) -> Option<(u32, u32)> {
4026 if let Some(tile) = self.coverages.get_mut(&handle) {
4027 tile.last_used_frame = self.frame_index;
4028 return Some((tile.w, tile.h));
4029 }
4030 None
4031 }
4032
4033 fn evict_unused_images(&mut self) {
4034 let now = self.frame_index;
4035 let evict_after = self.image_evict_after_frames;
4036
4037 let mut to_evict = Vec::new();
4040 for (h, t) in self.images.iter() {
4041 let last = match t {
4042 ImageTex::Rgba {
4043 last_used_frame, ..
4044 } => *last_used_frame,
4045 ImageTex::User {
4046 last_used_frame, ..
4047 } => *last_used_frame,
4048 ImageTex::Nv12 {
4049 last_used_frame, ..
4050 } => *last_used_frame,
4051 };
4052 if now.saturating_sub(last) > evict_after {
4053 to_evict.push(*h);
4054 }
4055 }
4056 for h in to_evict {
4057 if self.retained.contains_key(&h) {
4058 self.evict_image_gpu(h);
4059 } else {
4060 self.remove_image(h);
4061 }
4062 }
4063
4064 let mut stale = Vec::new();
4066 for (h, t) in self.coverages.iter() {
4067 if now.saturating_sub(t.last_used_frame) > evict_after {
4068 stale.push(*h);
4069 }
4070 }
4071 for h in stale {
4072 self.remove_coverage(h);
4073 }
4074
4075 self.evict_budget_excess();
4076 }
4077
4078 fn evict_budget_excess(&mut self) {
4079 if self.image_bytes_total <= self.image_budget_bytes {
4080 return;
4081 }
4082 let mut candidates: Vec<(u64, u64, u64)> = self
4084 .images
4085 .iter()
4086 .map(|(h, t)| {
4087 let (last, bytes) = match t {
4088 ImageTex::Rgba {
4089 last_used_frame,
4090 bytes,
4091 ..
4092 } => (*last_used_frame, *bytes),
4093 ImageTex::User {
4094 last_used_frame,
4095 bytes,
4096 ..
4097 } => (*last_used_frame, *bytes),
4098 ImageTex::Nv12 {
4099 last_used_frame,
4100 bytes,
4101 ..
4102 } => (*last_used_frame, *bytes),
4103 };
4104 (*h, last, bytes)
4105 })
4106 .collect();
4107
4108 candidates.sort_by_key(|k| k.1);
4110
4111 let now = self.frame_index;
4112 for (h, last, _bytes) in candidates {
4113 if self.image_bytes_total <= self.image_budget_bytes {
4114 break;
4115 }
4116 if last == now {
4118 continue;
4119 }
4120 if self.retained.contains_key(&h) {
4121 self.evict_image_gpu(h);
4122 } else {
4123 self.remove_image(h);
4124 }
4125 }
4126 }
4127
4128 pub fn set_pixels_per_point(&mut self, ppp: f32) {
4130 self.pixels_per_point = ppp.clamp(0.5, 8.0);
4131 }
4132
4133 pub fn set_working_space(&mut self, enabled: bool) {
4137 if enabled == self.working_space {
4138 return;
4139 }
4140 self.working_space = enabled;
4141 if enabled {
4142 self.ensure_display_pipeline();
4143 self.recreate_working_space_texture();
4144 } else {
4145 self.ws_tex = None;
4146 self.ws_view = None;
4147 self.ws_bind = None;
4148 }
4149 }
4150
4151 fn ensure_display_pipeline(&mut self) {
4152 if self.display_pipeline.is_some() {
4153 return;
4154 }
4155
4156 let layout = self
4157 .device
4158 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
4159 label: Some("display transform layout"),
4160 entries: &[
4161 wgpu::BindGroupLayoutEntry {
4162 binding: 0,
4163 visibility: wgpu::ShaderStages::FRAGMENT,
4164 ty: wgpu::BindingType::Texture {
4165 multisampled: false,
4166 view_dimension: wgpu::TextureViewDimension::D2,
4167 sample_type: wgpu::TextureSampleType::Float { filterable: true },
4168 },
4169 count: None,
4170 },
4171 wgpu::BindGroupLayoutEntry {
4172 binding: 1,
4173 visibility: wgpu::ShaderStages::FRAGMENT,
4174 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
4175 count: None,
4176 },
4177 ],
4178 });
4179 self.display_layout = Some(layout);
4180
4181 let shader = self
4182 .device
4183 .create_shader_module(wgpu::ShaderModuleDescriptor {
4184 label: Some("display_transform.wgsl"),
4185 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
4186 "shaders/display_transform.wgsl"
4187 ))),
4188 });
4189
4190 let pipeline_layout = self
4191 .device
4192 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
4193 label: Some("display transform pipeline layout"),
4194 bind_group_layouts: &[None, self.display_layout.as_ref()],
4195 immediate_size: 0,
4196 });
4197
4198 let pipeline = self
4199 .device
4200 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
4201 label: Some("display transform pipeline"),
4202 layout: Some(&pipeline_layout),
4203 vertex: wgpu::VertexState {
4204 module: &shader,
4205 entry_point: Some("vs_main"),
4206 buffers: &[],
4207 compilation_options: wgpu::PipelineCompilationOptions::default(),
4208 },
4209 fragment: Some(wgpu::FragmentState {
4210 module: &shader,
4211 entry_point: Some("fs_main"),
4212 targets: &[Some(wgpu::ColorTargetState {
4213 format: self.output_format,
4214 blend: None,
4215 write_mask: wgpu::ColorWrites::ALL,
4216 })],
4217 compilation_options: wgpu::PipelineCompilationOptions::default(),
4218 }),
4219 primitive: wgpu::PrimitiveState::default(),
4220 depth_stencil: None,
4221 multisample: wgpu::MultisampleState::default(),
4222 multiview_mask: None,
4223 cache: None,
4224 });
4225 self.display_pipeline = Some(pipeline);
4226 }
4227
4228 pub fn resize(&mut self, width: u32, height: u32) {
4233 self.output_width = width;
4234 self.output_height = height;
4235 self.recreate_msaa_and_depth_stencil();
4236 self.recreate_working_space_texture();
4237 }
4238
4239 fn recreate_working_space_texture(&mut self) {
4240 if !self.working_space {
4241 return;
4242 }
4243 let w = self.output_width.max(1);
4244 let h = self.output_height.max(1);
4245
4246 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
4247 label: Some("working space"),
4248 size: wgpu::Extent3d {
4249 width: w,
4250 height: h,
4251 depth_or_array_layers: 1,
4252 },
4253 mip_level_count: 1,
4254 sample_count: 1,
4255 dimension: wgpu::TextureDimension::D2,
4256 format: wgpu::TextureFormat::Rgba16Float,
4257 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
4258 view_formats: &[],
4259 });
4260 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4261
4262 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4263 label: Some("working space bind"),
4264 layout: self.display_layout.as_ref().unwrap(),
4265 entries: &[
4266 wgpu::BindGroupEntry {
4267 binding: 0,
4268 resource: wgpu::BindingResource::TextureView(&view),
4269 },
4270 wgpu::BindGroupEntry {
4271 binding: 1,
4272 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
4273 },
4274 ],
4275 });
4276
4277 self.ws_tex = Some(tex);
4278 self.ws_view = Some(view);
4279 self.ws_bind = Some(bind);
4280 }
4281
4282 fn recreate_msaa_and_depth_stencil(&mut self) {
4283 if self.msaa_samples > 1 {
4284 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
4285 label: Some("msaa color"),
4286 size: wgpu::Extent3d {
4287 width: self.output_width.max(1),
4288 height: self.output_height.max(1),
4289 depth_or_array_layers: 1,
4290 },
4291 mip_level_count: 1,
4292 sample_count: self.msaa_samples,
4293 dimension: wgpu::TextureDimension::D2,
4294 format: self.output_format,
4295 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
4296 view_formats: &[],
4297 });
4298 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4299 self.msaa_tex = Some(tex);
4300 self.msaa_view = Some(view);
4301 } else {
4302 self.msaa_tex = None;
4303 self.msaa_view = None;
4304 }
4305
4306 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
4307 label: Some("depth-stencil (stencil clips)"),
4308 size: wgpu::Extent3d {
4309 width: self.output_width.max(1),
4310 height: self.output_height.max(1),
4311 depth_or_array_layers: 1,
4312 },
4313 mip_level_count: 1,
4314 sample_count: self.msaa_samples,
4315 dimension: wgpu::TextureDimension::D2,
4316 format: wgpu::TextureFormat::Depth24PlusStencil8,
4317 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
4318 view_formats: &[],
4319 });
4320 self.depth_stencil_view = self
4321 .depth_stencil_tex
4322 .create_view(&wgpu::TextureViewDescriptor::default());
4323 }
4324
4325 fn get_or_create_layer(
4326 &mut self,
4327 layer_id: u32,
4328 width: u32,
4329 height: u32,
4330 rect: repose_core::Rect,
4331 ) {
4332 let needs_alloc = match self.layer_pool.get(&layer_id) {
4333 Some(lt) => lt.width != width || lt.height != height,
4334 None => true,
4335 };
4336 if !needs_alloc {
4337 if let Some(lt) = self.layer_pool.get_mut(&layer_id) {
4338 lt.rect_px = (rect.x, rect.y, rect.w, rect.h);
4339 }
4340 return;
4341 }
4342 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
4343 label: Some("graphics layer"),
4344 size: wgpu::Extent3d {
4345 width: width.max(1),
4346 height: height.max(1),
4347 depth_or_array_layers: 1,
4348 },
4349 mip_level_count: 1,
4350 sample_count: 1,
4351 dimension: wgpu::TextureDimension::D2,
4352 format: self.output_format,
4353 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
4354 | wgpu::TextureUsages::TEXTURE_BINDING
4355 | wgpu::TextureUsages::COPY_SRC,
4356 view_formats: &[],
4357 });
4358 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4359 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4360 label: Some("layer bind"),
4361 layout: &self.image_bind_layout_rgba,
4362 entries: &[
4363 wgpu::BindGroupEntry {
4364 binding: 0,
4365 resource: wgpu::BindingResource::TextureView(&view),
4366 },
4367 wgpu::BindGroupEntry {
4368 binding: 1,
4369 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
4370 },
4371 ],
4372 });
4373 let bind_linear = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4374 label: Some("layer bind linear"),
4375 layout: &self.image_bind_layout_rgba,
4376 entries: &[
4377 wgpu::BindGroupEntry {
4378 binding: 0,
4379 resource: wgpu::BindingResource::TextureView(&view),
4380 },
4381 wgpu::BindGroupEntry {
4382 binding: 1,
4383 resource: wgpu::BindingResource::Sampler(&self.layer_sampler_linear),
4384 },
4385 ],
4386 });
4387 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
4388 label: Some("graphics layer depth-stencil"),
4389 size: wgpu::Extent3d {
4390 width: width.max(1),
4391 height: height.max(1),
4392 depth_or_array_layers: 1,
4393 },
4394 mip_level_count: 1,
4395 sample_count: 1,
4396 dimension: wgpu::TextureDimension::D2,
4397 format: wgpu::TextureFormat::Depth24PlusStencil8,
4398 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
4399 view_formats: &[],
4400 });
4401 let depth_stencil_view =
4402 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
4403 self.layer_pool.insert(
4404 layer_id,
4405 LayerTarget {
4406 texture: tex,
4407 view,
4408 bind,
4409 bind_linear,
4410 depth_stencil_view,
4411 width,
4412 height,
4413 rect_px: (rect.x, rect.y, rect.w, rect.h),
4414 },
4415 );
4416 }
4417
4418 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
4419 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4420 label: Some("atlas bind"),
4421 layout: &self.text_bind_layout,
4422 entries: &[
4423 wgpu::BindGroupEntry {
4424 binding: 0,
4425 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
4426 },
4427 wgpu::BindGroupEntry {
4428 binding: 1,
4429 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
4430 },
4431 ],
4432 })
4433 }
4434
4435 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
4436 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4437 label: Some("atlas bind color"),
4438 layout: &self.text_bind_layout,
4439 entries: &[
4440 wgpu::BindGroupEntry {
4441 binding: 0,
4442 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
4443 },
4444 wgpu::BindGroupEntry {
4445 binding: 1,
4446 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
4447 },
4448 ],
4449 })
4450 }
4451
4452 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
4453 let keyp = (key, px.to_bits());
4454 if let Some(info) = self.atlas_mask.map.get(&keyp) {
4455 return Some(*info);
4456 }
4457
4458 let gb = repose_text::rasterize(key, px)?;
4459 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
4460 return None;
4461 }
4462
4463 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
4464
4465 let w = gb.w.max(1);
4466 let h = gb.h.max(1);
4467
4468 if !self.alloc_space_mask(w, h) {
4469 self.grow_mask_and_rebuild();
4470 }
4471 if !self.alloc_space_mask(w, h) {
4472 return None;
4473 }
4474 let x = self.atlas_mask.next_x;
4475 let y = self.atlas_mask.next_y;
4476 self.atlas_mask.next_x += w + 1;
4477 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
4478
4479 let layout = wgpu::TexelCopyBufferLayout {
4480 offset: 0,
4481 bytes_per_row: Some(w),
4482 rows_per_image: Some(h),
4483 };
4484 let size = wgpu::Extent3d {
4485 width: w,
4486 height: h,
4487 depth_or_array_layers: 1,
4488 };
4489 self.queue.write_texture(
4490 wgpu::TexelCopyTextureInfoBase {
4491 texture: &self.atlas_mask.tex,
4492 mip_level: 0,
4493 origin: wgpu::Origin3d { x, y, z: 0 },
4494 aspect: wgpu::TextureAspect::All,
4495 },
4496 &coverage,
4497 layout,
4498 size,
4499 );
4500
4501 let info = GlyphInfo {
4502 u0: x as f32 / self.atlas_mask.size as f32,
4503 v0: y as f32 / self.atlas_mask.size as f32,
4504 u1: (x + w) as f32 / self.atlas_mask.size as f32,
4505 v1: (y + h) as f32 / self.atlas_mask.size as f32,
4506 w: w as f32,
4507 h: h as f32,
4508 };
4509 self.atlas_mask.map.insert(keyp, info);
4510 Some(info)
4511 }
4512
4513 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
4514 let keyp = (key, px.to_bits());
4515 if let Some(info) = self.atlas_color.map.get(&keyp) {
4516 return Some(*info);
4517 }
4518 let gb = repose_text::rasterize(key, px)?;
4519 if !matches!(gb.content, repose_text::SwashContent::Color) {
4520 return None;
4521 }
4522 let w = gb.w.max(1);
4523 let h = gb.h.max(1);
4524 if !self.alloc_space_color(w, h) {
4525 self.grow_color_and_rebuild();
4526 }
4527 if !self.alloc_space_color(w, h) {
4528 return None;
4529 }
4530 let x = self.atlas_color.next_x;
4531 let y = self.atlas_color.next_y;
4532 self.atlas_color.next_x += w + 1;
4533 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
4534
4535 let layout = wgpu::TexelCopyBufferLayout {
4536 offset: 0,
4537 bytes_per_row: Some(w * 4),
4538 rows_per_image: Some(h),
4539 };
4540 let size = wgpu::Extent3d {
4541 width: w,
4542 height: h,
4543 depth_or_array_layers: 1,
4544 };
4545 self.queue.write_texture(
4546 wgpu::TexelCopyTextureInfoBase {
4547 texture: &self.atlas_color.tex,
4548 mip_level: 0,
4549 origin: wgpu::Origin3d { x, y, z: 0 },
4550 aspect: wgpu::TextureAspect::All,
4551 },
4552 &gb.data,
4553 layout,
4554 size,
4555 );
4556 let info = GlyphInfo {
4557 u0: x as f32 / self.atlas_color.size as f32,
4558 v0: y as f32 / self.atlas_color.size as f32,
4559 u1: (x + w) as f32 / self.atlas_color.size as f32,
4560 v1: (y + h) as f32 / self.atlas_color.size as f32,
4561 w: w as f32,
4562 h: h as f32,
4563 };
4564 self.atlas_color.map.insert(keyp, info);
4565 Some(info)
4566 }
4567
4568 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
4569 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
4570 self.atlas_mask.next_x = 1;
4571 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
4572 self.atlas_mask.row_h = 0;
4573 }
4574 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
4575 return false;
4576 }
4577 true
4578 }
4579
4580 fn grow_mask_and_rebuild(&mut self) {
4581 let new_size = (self.atlas_mask.size * 2).min(4096);
4582 if new_size == self.atlas_mask.size {
4583 return;
4584 }
4585 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
4586 label: Some("glyph atlas A8 (grown)"),
4587 size: wgpu::Extent3d {
4588 width: new_size,
4589 height: new_size,
4590 depth_or_array_layers: 1,
4591 },
4592 mip_level_count: 1,
4593 sample_count: 1,
4594 dimension: wgpu::TextureDimension::D2,
4595 format: wgpu::TextureFormat::R8Unorm,
4596 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4597 view_formats: &[],
4598 });
4599 self.atlas_mask.tex = tex;
4600 self.atlas_mask.view = self
4601 .atlas_mask
4602 .tex
4603 .create_view(&wgpu::TextureViewDescriptor::default());
4604 self.atlas_mask.size = new_size;
4605 self.atlas_mask.next_x = 1;
4606 self.atlas_mask.next_y = 1;
4607 self.atlas_mask.row_h = 0;
4608 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
4609 self.atlas_mask.map.clear();
4610 for (k, px_bits) in keys {
4611 let _ = self.upload_glyph_mask(k, f32::from_bits(px_bits));
4612 }
4613 }
4614
4615 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
4616 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
4617 self.atlas_color.next_x = 1;
4618 self.atlas_color.next_y += self.atlas_color.row_h + 1;
4619 self.atlas_color.row_h = 0;
4620 }
4621 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
4622 return false;
4623 }
4624 true
4625 }
4626
4627 fn grow_color_and_rebuild(&mut self) {
4628 let new_size = (self.atlas_color.size * 2).min(4096);
4629 if new_size == self.atlas_color.size {
4630 return;
4631 }
4632 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
4633 label: Some("glyph atlas RGBA (grown)"),
4634 size: wgpu::Extent3d {
4635 width: new_size,
4636 height: new_size,
4637 depth_or_array_layers: 1,
4638 },
4639 mip_level_count: 1,
4640 sample_count: 1,
4641 dimension: wgpu::TextureDimension::D2,
4642 format: wgpu::TextureFormat::Rgba8UnormSrgb,
4643 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4644 view_formats: &[],
4645 });
4646 self.atlas_color.tex = tex;
4647 self.atlas_color.view = self
4648 .atlas_color
4649 .tex
4650 .create_view(&wgpu::TextureViewDescriptor::default());
4651 self.atlas_color.size = new_size;
4652 self.atlas_color.next_x = 1;
4653 self.atlas_color.next_y = 1;
4654 self.atlas_color.row_h = 0;
4655 let keys: Vec<(repose_text::GlyphKey, u32)> =
4656 self.atlas_color.map.keys().copied().collect();
4657 self.atlas_color.map.clear();
4658 for (k, px_bits) in keys {
4659 let _ = self.upload_glyph_color(k, f32::from_bits(px_bits));
4660 }
4661 }
4662}
4663
4664fn brush_to_shape_fields(
4678 brush: &Brush,
4679 _rect: &repose_core::Rect,
4680 transform: &Transform,
4681) -> (u32, u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2], u32) {
4682 let to_local = |p: Vec2| {
4683 let m = transform.linear();
4684 let det = m[0] * m[3] - m[1] * m[2];
4685 if det.abs() < 1e-12 {
4686 return [p.x, p.y];
4687 }
4688 [
4689 (m[3] * p.x - m[1] * p.y) / det,
4690 (-m[2] * p.x + m[0] * p.y) / det,
4691 ]
4692 };
4693 match brush {
4694 Brush::Solid(c) => (
4695 0u32,
4696 0u32,
4697 c.to_linear(),
4698 [0.0; 4],
4699 [0.0; 2],
4700 [0.0; 2],
4701 0u32,
4702 ),
4703 Brush::Linear {
4704 start,
4705 end,
4706 start_color,
4707 end_color,
4708 } => (
4709 1u32,
4710 0u32,
4711 start_color.to_linear(),
4712 end_color.to_linear(),
4713 to_local(*start),
4714 to_local(*end),
4715 0u32,
4716 ),
4717 Brush::Radial {
4718 center,
4719 radius,
4720 start_color,
4721 end_color,
4722 } => (
4723 1u32,
4724 1u32,
4725 start_color.to_linear(),
4726 end_color.to_linear(),
4727 to_local(*center),
4728 [radius.max(0.0), 0.0],
4729 0u32,
4730 ),
4731 Brush::Sweep {
4732 center,
4733 start_color,
4734 end_color,
4735 } => (
4736 1u32,
4737 2u32,
4738 start_color.to_linear(),
4739 end_color.to_linear(),
4740 to_local(*center),
4741 [0.0, 0.0],
4742 0u32,
4743 ),
4744 _ => (0u32, 0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2], 0u32),
4745 }
4746}
4747
4748fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
4749 match brush {
4750 Brush::Solid(c) => (
4751 0u32,
4752 c.to_linear(),
4753 [0.0, 0.0, 0.0, 0.0],
4754 [0.0, 0.0],
4755 [0.0, 1.0],
4756 ),
4757 Brush::Linear {
4758 start,
4759 end,
4760 start_color,
4761 end_color,
4762 } => (
4763 1u32,
4764 start_color.to_linear(),
4765 end_color.to_linear(),
4766 [start.x, start.y],
4767 [end.x, end.y],
4768 ),
4769 Brush::Radial { start_color, .. } => (
4770 0u32,
4771 start_color.to_linear(),
4772 [0.0, 0.0, 0.0, 0.0],
4773 [0.0, 0.0],
4774 [0.0, 1.0],
4775 ),
4776 Brush::Sweep { start_color, .. } => (
4777 0u32,
4778 start_color.to_linear(),
4779 [0.0, 0.0, 0.0, 0.0],
4780 [0.0, 0.0],
4781 [0.0, 1.0],
4782 ),
4783 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
4784 }
4785}
4786
4787#[allow(dead_code)]
4790fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
4791 match brush {
4792 Brush::Solid(c) => c.to_linear(),
4793 Brush::Linear { start_color, .. } => start_color.to_linear(),
4794 Brush::Radial { start_color, .. } => start_color.to_linear(),
4795 Brush::Sweep { start_color, .. } => start_color.to_linear(),
4796 _ => [0.0; 4],
4797 }
4798}
4799
4800fn init_atlas_mask(device: &wgpu::Device) -> AtlasA8 {
4801 let size = 1024u32;
4802 let tex = device.create_texture(&wgpu::TextureDescriptor {
4803 label: Some("glyph atlas A8"),
4804 size: wgpu::Extent3d {
4805 width: size,
4806 height: size,
4807 depth_or_array_layers: 1,
4808 },
4809 mip_level_count: 1,
4810 sample_count: 1,
4811 dimension: wgpu::TextureDimension::D2,
4812 format: wgpu::TextureFormat::R8Unorm,
4813 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4814 view_formats: &[],
4815 });
4816 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4817 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4818 label: Some("glyph atlas sampler A8"),
4819 address_mode_u: wgpu::AddressMode::ClampToEdge,
4820 address_mode_v: wgpu::AddressMode::ClampToEdge,
4821 address_mode_w: wgpu::AddressMode::ClampToEdge,
4822 mag_filter: wgpu::FilterMode::Linear,
4823 min_filter: wgpu::FilterMode::Linear,
4824 mipmap_filter: wgpu::MipmapFilterMode::Linear,
4825 ..Default::default()
4826 });
4827
4828 AtlasA8 {
4829 tex,
4830 view,
4831 sampler,
4832 size,
4833 next_x: 1,
4834 next_y: 1,
4835 row_h: 0,
4836 map: HashMap::new(),
4837 }
4838}
4839
4840fn init_atlas_color(device: &wgpu::Device) -> AtlasRGBA {
4841 let size = 1024u32;
4842 let tex = device.create_texture(&wgpu::TextureDescriptor {
4843 label: Some("glyph atlas RGBA"),
4844 size: wgpu::Extent3d {
4845 width: size,
4846 height: size,
4847 depth_or_array_layers: 1,
4848 },
4849 mip_level_count: 1,
4850 sample_count: 1,
4851 dimension: wgpu::TextureDimension::D2,
4852 format: wgpu::TextureFormat::Rgba8UnormSrgb,
4853 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4854 view_formats: &[],
4855 });
4856 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4857 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4858 label: Some("glyph atlas sampler RGBA"),
4859 address_mode_u: wgpu::AddressMode::ClampToEdge,
4860 address_mode_v: wgpu::AddressMode::ClampToEdge,
4861 address_mode_w: wgpu::AddressMode::ClampToEdge,
4862 mag_filter: wgpu::FilterMode::Linear,
4863 min_filter: wgpu::FilterMode::Linear,
4864 mipmap_filter: wgpu::MipmapFilterMode::Linear,
4865 ..Default::default()
4866 });
4867 AtlasRGBA {
4868 tex,
4869 view,
4870 sampler,
4871 size,
4872 next_x: 1,
4873 next_y: 1,
4874 row_h: 0,
4875 map: HashMap::new(),
4876 }
4877}
4878
4879#[cfg(feature = "winit-surface")]
4880impl RenderBackend for WgpuSurfaceBackend {
4881 fn configure_surface(&mut self, width: u32, height: u32) {
4882 if width == 0 || height == 0 {
4883 return;
4884 }
4885 self.renderer.output_width = width;
4886 self.renderer.output_height = height;
4887 if let Some(ref mut config) = self.surface_config {
4888 config.width = width;
4889 config.height = height;
4890 }
4891 if let (Some(surface), Some(config)) = (self.surface.as_ref(), self.surface_config.as_ref())
4892 {
4893 surface.configure(&self.renderer.device, config);
4894 }
4895 self.renderer.recreate_msaa_and_depth_stencil();
4896 self.renderer.recreate_working_space_texture();
4897 }
4898
4899 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
4900 let surface = self.surface.as_ref().expect("WgpuSurfaceBackend::frame() requires a surface (use from_device + render_to_view instead)");
4901 let surface_config = self
4902 .surface_config
4903 .as_ref()
4904 .expect("surface_config required for frame()");
4905
4906 self.renderer.frame_index = self.renderer.frame_index.wrapping_add(1);
4907 self.renderer.slug_cache.next_frame();
4908
4909 if self.renderer.output_width == 0 || self.renderer.output_height == 0 {
4910 return;
4911 }
4912
4913 let mut retries = 0u32;
4914 const MAX_RETRIES: u32 = 4;
4915 let frame = loop {
4916 match surface.get_current_texture() {
4917 wgpu::CurrentSurfaceTexture::Success(f) => break f,
4918 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
4919 log::warn!("suboptimal surface; reconfiguring");
4920 surface.configure(&self.renderer.device, surface_config);
4921 break f;
4922 }
4923 wgpu::CurrentSurfaceTexture::Outdated => {
4924 retries += 1;
4925 if retries >= MAX_RETRIES {
4926 log::warn!(
4927 "surface outdated persisted after {MAX_RETRIES} retries; skipping frame"
4928 );
4929 return;
4930 }
4931 log::warn!("surface outdated; reconfiguring");
4932 surface.configure(&self.renderer.device, surface_config);
4933 }
4934 wgpu::CurrentSurfaceTexture::Lost => {
4935 retries += 1;
4936 if retries >= MAX_RETRIES {
4937 log::warn!(
4938 "surface lost persisted after {MAX_RETRIES} retries; skipping frame"
4939 );
4940 return;
4941 }
4942 log::warn!("surface lost; reconfiguring");
4943 surface.configure(&self.renderer.device, surface_config);
4944 }
4945 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
4946 request_frame();
4947 return;
4948 }
4949 wgpu::CurrentSurfaceTexture::Validation => {
4950 retries += 1;
4951 if retries >= MAX_RETRIES {
4952 log::warn!(
4953 "surface validation persisted after {MAX_RETRIES} retries; skipping frame"
4954 );
4955 return;
4956 }
4957 surface.configure(&self.renderer.device, surface_config);
4958 }
4959 }
4960 };
4961
4962 let swap_view = if let Some(view_format) = self
4963 .surface_config
4964 .as_ref()
4965 .and_then(|c| c.view_formats.iter().find(|f| f.is_srgb()).copied())
4966 {
4967 frame.texture.create_view(&wgpu::TextureViewDescriptor {
4968 format: Some(view_format),
4969 ..Default::default()
4970 })
4971 } else {
4972 frame
4973 .texture
4974 .create_view(&wgpu::TextureViewDescriptor::default())
4975 };
4976 let mut encoder =
4977 self.renderer
4978 .device
4979 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4980 label: Some("frame encoder"),
4981 });
4982
4983 let clear_color = Some([
4984 scene.clear_color.0 as f64 / 255.0,
4985 scene.clear_color.1 as f64 / 255.0,
4986 scene.clear_color.2 as f64 / 255.0,
4987 scene.clear_color.3 as f64 / 255.0,
4988 ]);
4989
4990 self.renderer
4991 .render_scene_to_encoder(scene, &mut encoder, &swap_view, clear_color);
4992
4993 {
4996 let _reset = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
4997 label: Some("webgl color_mask reset before present"),
4998 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
4999 view: &swap_view,
5000 resolve_target: None,
5001 ops: wgpu::Operations {
5002 load: wgpu::LoadOp::Load,
5003 store: wgpu::StoreOp::Store,
5004 },
5005 depth_slice: None,
5006 })],
5007 depth_stencil_attachment: None,
5008 timestamp_writes: None,
5009 occlusion_query_set: None,
5010 multiview_mask: None,
5011 });
5012 }
5013
5014 self.renderer
5015 .queue
5016 .submit(std::iter::once(encoder.finish()));
5017 if let Err(e) = catch_unwind(AssertUnwindSafe(|| self.renderer.queue.present(frame))) {
5018 log::warn!("queue.present panicked: {:?}", e);
5019 }
5020 }
5021}
5022
5023impl WgpuSceneRenderer {
5024 #[allow(clippy::too_many_arguments)]
5036 fn push_perspective_layer(
5037 &mut self,
5038 node: Transform,
5039 top: Transform,
5040 transform_stack: &mut Vec<Transform>,
5041 scissor_stack: &mut Vec<repose_core::Rect>,
5042 root_clip_rect: &mut repose_core::Rect,
5043 current_target_size: &mut (f32, f32),
5044 current_pass: &mut Pass,
5045 passes: &mut Vec<Pass>,
5046 target_stack: &mut Vec<PassTarget>,
5047 flatten_stack: &mut Vec<FlattenRecord>,
5048 id_head: &mut u32,
5049 ids_used: &mut Vec<u32>,
5050 ) {
5051 let map =
5055 Transform::compose_projective(&top.projective_matrix(), &node.projective_matrix());
5056 let scr = scissor_stack.last().copied().unwrap_or(*root_clip_rect);
5057 let w = scr.w.ceil().max(1.0);
5058 let h = scr.h.ceil().max(1.0);
5059 let layer_rect = repose_core::Rect {
5060 x: scr.x,
5061 y: scr.y,
5062 w,
5063 h,
5064 };
5065 let layer_id = *id_head;
5068 *id_head = id_head.wrapping_add(1);
5069 ids_used.push(layer_id);
5070
5071 let stack_len = transform_stack.len();
5072 transform_stack.push(top);
5076 transform_stack.push(Transform::translate(-layer_rect.x, -layer_rect.y));
5077
5078 let saved_scissor = std::mem::replace(
5079 scissor_stack,
5080 vec![repose_core::Rect {
5081 x: 0.0,
5082 y: 0.0,
5083 w,
5084 h,
5085 }],
5086 );
5087 let saved_root = std::mem::replace(
5088 root_clip_rect,
5089 repose_core::Rect {
5090 x: 0.0,
5091 y: 0.0,
5092 w,
5093 h,
5094 },
5095 );
5096 let saved_size = std::mem::replace(current_target_size, (w, h));
5097 let prev_target = current_pass.target;
5098 let saved = std::mem::replace(
5099 current_pass,
5100 Pass {
5101 target: PassTarget::Layer(layer_id),
5102 initial_scissor: (0, 0, w as u32, h as u32),
5103 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
5104 cmds: Vec::new(),
5105 },
5106 );
5107 passes.push(saved);
5108 target_stack.push(prev_target);
5109 self.get_or_create_layer(layer_id, w as u32, h as u32, layer_rect);
5110 *current_target_size = (w, h);
5111 flatten_stack.push(FlattenRecord {
5112 stack_len,
5113 layer_id,
5114 map,
5115 layer_rect,
5116 saved_scissor,
5117 saved_root,
5118 saved_size,
5119 });
5120 }
5121
5122 #[allow(clippy::too_many_arguments)]
5125 fn pop_perspective_layer(
5126 &mut self,
5127 rec: FlattenRecord,
5128 scissor_stack: &mut Vec<repose_core::Rect>,
5129 root_clip_rect: &mut repose_core::Rect,
5130 current_target_size: &mut (f32, f32),
5131 current_pass: &mut Pass,
5132 passes: &mut Vec<Pass>,
5133 target_stack: &mut Vec<PassTarget>,
5134 ) {
5135 *scissor_stack = rec.saved_scissor;
5136 *root_clip_rect = rec.saved_root;
5137 *current_target_size = rec.saved_size;
5138 let saved = std::mem::replace(
5139 current_pass,
5140 Pass {
5141 target: target_stack.pop().unwrap_or(PassTarget::Surface),
5142 initial_scissor: (0, 0, self.output_width, self.output_height),
5143 clear_color: None,
5144 cmds: Vec::new(),
5145 },
5146 );
5147 passes.push(saved);
5148
5149 let (tw, th) = rec.saved_size;
5153 let r = rec.layer_rect;
5154 let corners = [
5155 (r.x, r.y),
5156 (r.x + r.w, r.y),
5157 (r.x + r.w, r.y + r.h),
5158 (r.x, r.y + r.h),
5159 ];
5160 let mut ndc = [[0.0f32; 2]; 4];
5161 let mut ws = [1.0f32; 4];
5162 let mut all_behind = true;
5163 for (i, (x, y)) in corners.iter().enumerate() {
5164 let w_raw = rec.map[6] * x + rec.map[7] * y + rec.map[8];
5165 let w = if w_raw.abs() < 1e-6 {
5166 if w_raw < 0.0 { -1e-6 } else { 1e-6 }
5167 } else {
5168 w_raw
5169 };
5170 if w > 0.0 {
5171 all_behind = false;
5172 }
5173 let px = (rec.map[0] * x + rec.map[1] * y + rec.map[2]) / w;
5174 let py = (rec.map[3] * x + rec.map[4] * y + rec.map[5]) / w;
5175 ndc[i] = [px / tw * 2.0 - 1.0, 1.0 - py / th * 2.0];
5176 ws[i] = w;
5177 }
5178 if all_behind {
5179 return;
5182 }
5183 let layer = self.layer_pool.get(&rec.layer_id).expect("flatten layer");
5184 let uv_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
5185 let uv_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
5186 let inst = ProjectiveInstance {
5187 c0: ndc[0],
5188 c1: ndc[1],
5189 c2: ndc[2],
5190 c3: ndc[3],
5191 uv: [0.0, 0.0, uv_u1, uv_v1],
5192 w: ws,
5193 alpha: 1.0,
5194 _pad: [0.0; 3],
5195 };
5196 self.projective_ring.grow_to_fit(
5197 &self.device,
5198 std::mem::size_of::<ProjectiveInstance>() as u64,
5199 );
5200 let bytes = bytemuck::bytes_of(&inst);
5201 let (off, _) = self.projective_ring.alloc_write(&self.queue, bytes);
5202 current_pass.cmds.push(Cmd::CompositeProjective {
5203 off,
5204 cnt: 1,
5205 layer_id: rec.layer_id,
5206 });
5207 }
5208
5209 fn upload_mesh_geometry(&mut self, mesh: &repose_core::VectorMeshData) -> (u64, u32, u64, u32) {
5210 let verts: Vec<MeshVertex> = mesh
5211 .vertices
5212 .iter()
5213 .map(|v| MeshVertex {
5214 pos: v.pos,
5215 color: v.color,
5216 uv: v.uv,
5217 })
5218 .collect();
5219 let vbytes = bytemuck::cast_slice(&verts);
5220 self.mesh_verts
5221 .grow_to_fit(&self.device, vbytes.len() as u64);
5222 let (voff, _) = self.mesh_verts.alloc_write(&self.queue, vbytes);
5223 let ibytes = bytemuck::cast_slice(&mesh.indices);
5224 self.mesh_indices
5225 .grow_to_fit(&self.device, ibytes.len() as u64);
5226 let (ioff, _) = self.mesh_indices.alloc_write(&self.queue, ibytes);
5227 (voff, verts.len() as u32, ioff, mesh.indices.len() as u32)
5228 }
5229
5230 fn alloc_mesh_uniform(&mut self, u: MeshUniform) -> u64 {
5231 if self.mesh_uniform_head + MESH_UNIFORM_SLOT > MESH_UNIFORM_CAP {
5232 log::warn!("mesh uniform buffer overflow; regenerating");
5233 self.recreate_mesh_uniform_buffer();
5234 }
5235 let slot = self.mesh_uniform_head;
5236 self.queue
5237 .write_buffer(&self.mesh_uniform_buf, slot, bytemuck::bytes_of(&u));
5238 self.mesh_uniform_head = slot + MESH_UNIFORM_SLOT;
5239 slot
5240 }
5241
5242 fn recreate_mesh_uniform_buffer(&mut self) {
5243 let new_cap = self.mesh_uniform_head + MESH_UNIFORM_SLOT;
5244 self.mesh_uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
5245 label: Some("mesh uniform buffer"),
5246 size: new_cap,
5247 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
5248 mapped_at_creation: false,
5249 });
5250 self.mesh_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
5251 label: Some("mesh uniform bind"),
5252 layout: &self.mesh_bind_layout,
5253 entries: &[wgpu::BindGroupEntry {
5254 binding: 0,
5255 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
5256 buffer: &self.mesh_uniform_buf,
5257 offset: 0,
5258 size: NonZero::new(MESH_UNIFORM_SLOT),
5259 }),
5260 }],
5261 });
5262 self.mesh_uniform_head = 0;
5263 }
5264
5265 #[allow(clippy::too_many_arguments)]
5277 fn emit_isolated_blend(
5278 &mut self,
5279 mesh: std::sync::Arc<repose_core::VectorMeshData>,
5280 transform: [f32; 6],
5281 paint: repose_core::PaintDesc,
5282 blend: repose_core::BlendMode,
5283 current_transform: &repose_core::Transform,
5284 current_pass: &mut Pass,
5285 passes: &mut Vec<Pass>,
5286 target_stack: &mut Vec<PassTarget>,
5287 id_head: &mut u32,
5288 ids_used: &mut Vec<u32>,
5289 current_target_size: &mut (f32, f32),
5290 fb_w: f32,
5291 fb_h: f32,
5292 ) {
5293 let parent_target = target_stack.last().copied().unwrap_or(PassTarget::Surface);
5294 let parent_origin = match parent_target {
5298 PassTarget::Surface => (0.0, 0.0),
5299 PassTarget::Layer(id) => match self.layer_pool.get(&id) {
5300 Some(lt) => (lt.rect_px.0, lt.rect_px.1),
5301 None => (0.0, 0.0),
5302 },
5303 };
5304 let affine = combine_mesh_affine(current_transform, transform);
5305 let aabb = mesh_aabb(&mesh, affine);
5306 if aabb.w <= 0.0 || aabb.h <= 0.0 {
5307 return;
5308 }
5309 let local_rect = repose_core::Rect {
5311 x: aabb.x - parent_origin.0,
5312 y: aabb.y - parent_origin.1,
5313 w: aabb.w,
5314 h: aabb.h,
5315 };
5316 let w = local_rect.w.ceil().max(1.0);
5317 let h = local_rect.h.ceil().max(1.0);
5318 let layer_rect = repose_core::Rect {
5319 x: local_rect.x,
5320 y: local_rect.y,
5321 w,
5322 h,
5323 };
5324 let layer_id = *id_head;
5325 *id_head = id_head.wrapping_add(1);
5326 ids_used.push(layer_id);
5327
5328 self.alloc_blend_snapshot(layer_id, w as u32, h as u32);
5332 self.blend_copies
5333 .push((layer_id, parent_target, layer_rect));
5334
5335 let mut layer_cmds = Vec::new();
5339 self.get_or_create_layer(layer_id, w as u32, h as u32, layer_rect);
5340 let shift = repose_core::Transform::translate(-local_rect.x, -local_rect.y);
5341 let local = current_transform.combine(&shift);
5342 self.emit_vector_mesh(
5343 &local,
5344 &mesh,
5345 transform,
5346 &paint,
5347 repose_core::BlendMode::Alpha,
5348 &mut layer_cmds,
5349 );
5350 let saved = std::mem::replace(
5351 current_pass,
5352 Pass {
5353 target: parent_target,
5354 initial_scissor: (0, 0, self.output_width, self.output_height),
5355 clear_color: None,
5356 cmds: Vec::new(),
5357 },
5358 );
5359 passes.push(Pass {
5360 target: PassTarget::Layer(layer_id),
5361 initial_scissor: (0, 0, w as u32, h as u32),
5362 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
5363 cmds: layer_cmds,
5364 });
5365 passes.push(saved);
5366 *current_target_size = (fb_w, fb_h);
5367
5368 let (parent_w, parent_h) = match parent_target {
5372 PassTarget::Surface => (fb_w, fb_h),
5373 PassTarget::Layer(id) => match self.layer_pool.get(&id) {
5374 Some(lt) => (lt.width as f32, lt.height as f32),
5375 None => (fb_w, fb_h),
5376 },
5377 };
5378 let ndc = {
5379 let cx = local_rect.x + local_rect.w * 0.5;
5380 let cy = local_rect.y + local_rect.h * 0.5;
5381 let ndc_cx = (cx / parent_w) * 2.0 - 1.0;
5382 let ndc_cy = 1.0 - (cy / parent_h) * 2.0;
5383 let ndc_w = (local_rect.w / parent_w) * 2.0;
5384 let ndc_h = (local_rect.h / parent_h) * 2.0;
5385 [ndc_cx, ndc_cy, ndc_w, ndc_h]
5386 };
5387 let inst = BlendInstance {
5388 xywh: ndc,
5389 uv: [0.0, 0.0, 1.0, 1.0],
5390 color: [1.0, 1.0, 1.0, 1.0],
5391 fwd_mat: [1.0, 0.0, 0.0, 1.0],
5392 mode: blend.shader_mode(),
5393 _pad: [0.0; 3],
5394 };
5395 self.blend_ring.grow_to_fit(
5396 &self.device,
5397 std::mem::size_of::<BlendInstance>() as u64,
5398 );
5399 let bytes = bytemuck::bytes_of(&inst);
5400 let (off, _) = self.blend_ring.alloc_write(&self.queue, bytes);
5401 current_pass.cmds.push(Cmd::BlendLayer {
5402 off,
5403 cnt: 1,
5404 src_layer: layer_id,
5405 dst_layer: Some(layer_id),
5406 parent: parent_target,
5407 });
5408 }
5409
5410 fn alloc_blend_snapshot(&mut self, layer_id: u32, w: u32, h: u32) {
5413 let reuse = self
5414 .blend_snapshots
5415 .get(&layer_id)
5416 .is_some_and(|s| s.width == w && s.height == h);
5417 if reuse {
5418 return;
5419 }
5420 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
5421 label: Some("blend backdrop snapshot"),
5422 size: wgpu::Extent3d {
5423 width: w.max(1),
5424 height: h.max(1),
5425 depth_or_array_layers: 1,
5426 },
5427 mip_level_count: 1,
5428 sample_count: 1,
5429 dimension: wgpu::TextureDimension::D2,
5430 format: self.output_format,
5431 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
5432 view_formats: &[],
5433 });
5434 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
5435 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
5436 label: Some("blend snapshot bind"),
5437 layout: &self.image_bind_layout_rgba,
5438 entries: &[
5439 wgpu::BindGroupEntry {
5440 binding: 0,
5441 resource: wgpu::BindingResource::TextureView(&view),
5442 },
5443 wgpu::BindGroupEntry {
5444 binding: 1,
5445 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
5446 },
5447 ],
5448 });
5449 let _ = view;
5450 self.blend_snapshots.insert(
5451 layer_id,
5452 BlendSnapshot {
5453 texture: tex,
5454 bind,
5455 width: w,
5456 height: h,
5457 },
5458 );
5459 }
5460
5461 fn blend_snapshot_texture(&self, layer_id: u32) -> Option<wgpu::Texture> {
5462 self.blend_snapshots.get(&layer_id).map(|s| s.texture.clone())
5463 }
5464
5465 #[allow(clippy::too_many_arguments)]
5466 fn emit_vector_mesh(
5467 &mut self,
5468 current_transform: &Transform,
5469 mesh: &repose_core::VectorMeshData,
5470 transform: [f32; 6],
5471 paint: &repose_core::PaintDesc,
5472 blend: repose_core::BlendMode,
5473 cmds: &mut Vec<Cmd>,
5474 ) {
5475 let affine = combine_mesh_affine(current_transform, transform);
5476 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
5477 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(affine, paint));
5478 cmds.push(Cmd::VectorMesh {
5479 voff,
5480 vcnt,
5481 ioff,
5482 icnt,
5483 uoff,
5484 blend,
5485 });
5486 }
5487
5488 pub fn render_scene_to_encoder(
5489 &mut self,
5490 scene: &Scene,
5491 encoder: &mut wgpu::CommandEncoder,
5492 target_view: &wgpu::TextureView,
5493 clear_color_override: Option<[f64; 4]>,
5494 ) {
5495 self.render_scene_to_encoder_with_texture(
5496 scene,
5497 encoder,
5498 target_view,
5499 None,
5500 clear_color_override,
5501 )
5502 }
5503
5504 pub fn render_scene_to_encoder_with_texture(
5510 &mut self,
5511 scene: &Scene,
5512 encoder: &mut wgpu::CommandEncoder,
5513 target_view: &wgpu::TextureView,
5514 target_texture: Option<&wgpu::Texture>,
5515 clear_color_override: Option<[f64; 4]>,
5516 ) {
5517 fn affine_aabb(transform: &Transform, rect: &repose_core::Rect) -> repose_core::Rect {
5520 let m = transform.linear();
5521 let (tx, ty) = (transform.translate_x, transform.translate_y);
5522 let corners = [
5523 (rect.x, rect.y),
5524 (rect.x + rect.w, rect.y),
5525 (rect.x, rect.y + rect.h),
5526 (rect.x + rect.w, rect.y + rect.h),
5527 ];
5528 let mut min_x = f32::MAX;
5529 let mut min_y = f32::MAX;
5530 let mut max_x = f32::MIN;
5531 let mut max_y = f32::MIN;
5532 for (x, y) in corners {
5533 let wx = m[0] * x + m[1] * y + tx;
5534 let wy = m[2] * x + m[3] * y + ty;
5535 min_x = min_x.min(wx);
5536 min_y = min_y.min(wy);
5537 max_x = max_x.max(wx);
5538 max_y = max_y.max(wy);
5539 }
5540 repose_core::Rect {
5541 x: min_x,
5542 y: min_y,
5543 w: (max_x - min_x).max(0.0),
5544 h: (max_y - min_y).max(0.0),
5545 }
5546 }
5547
5548 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
5549 let x0 = (x / fb_w) * 2.0 - 1.0;
5550 let y0 = 1.0 - (y / fb_h) * 2.0;
5551 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
5552 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
5553 let min_x = x0.min(x1);
5554 let min_y = y0.min(y1);
5555 let w_ndc = (x1 - x0).abs();
5556 let h_ndc = (y1 - y0).abs();
5557 [min_x, min_y, w_ndc, h_ndc]
5558 }
5559
5560 fn rect_to_instance_ndc(
5565 rect: repose_core::Rect,
5566 transform: &Transform,
5567 fb_w: f32,
5568 fb_h: f32,
5569 ) -> ([f32; 4], [f32; 4]) {
5570 let cx = rect.x + rect.w * 0.5;
5571 let cy = rect.y + rect.h * 0.5;
5572
5573 let m = transform.linear();
5574 let tx = m[0] * cx + m[1] * cy + transform.translate_x;
5575 let ty = m[2] * cx + m[3] * cy + transform.translate_y;
5576
5577 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
5578 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
5579 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
5581 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
5582
5583 ([ndc_cx, ndc_cy, ndc_w, ndc_h], forward_rs_mat(transform))
5584 }
5585
5586 fn forward_rs_mat(transform: &Transform) -> [f32; 4] {
5590 let c = transform.rotate.cos();
5591 let s = transform.rotate.sin();
5592 let (hx, hy) = (transform.shear_x, transform.shear_y);
5593 let m = [c - s * hy, c * hx - s, s + c * hy, s * hx + c];
5594 if (m[0] * m[3] - m[1] * m[2]).abs() < 1e-6 {
5595 return [1.0, 0.0, 0.0, 1.0];
5596 }
5597 m
5598 }
5599
5600 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
5601 let mut x = r.x.floor() as i64;
5602 let mut y = r.y.floor() as i64;
5603 let fb_wi = fb_w as i64;
5604 let fb_hi = fb_h as i64;
5605 x = x.clamp(0, fb_wi.saturating_sub(1));
5606 y = y.clamp(0, fb_hi.saturating_sub(1));
5607 let w_req = r.w.ceil().max(1.0) as i64;
5608 let h_req = r.h.ceil().max(1.0) as i64;
5609 let w = (w_req).min(fb_wi - x).max(1);
5610 let h = (h_req).min(fb_hi - y).max(1);
5611 (x as u32, y as u32, w as u32, h as u32)
5612 }
5613
5614 let fb_w = self.output_width as f32;
5615 let fb_h = self.output_height as f32;
5616
5617 let mut passes: Vec<Pass> = Vec::with_capacity(1);
5618 let clear_color = clear_color_override.unwrap_or_else(|| {
5619 let lin = scene.clear_color.to_linear();
5623 [lin[0] as f64, lin[1] as f64, lin[2] as f64, lin[3] as f64]
5624 });
5625 let mut current_pass: Pass = Pass {
5626 target: PassTarget::Surface,
5627 initial_scissor: (0, 0, self.output_width, self.output_height),
5628 clear_color: Some([
5629 clear_color[0] as f32,
5630 clear_color[1] as f32,
5631 clear_color[2] as f32,
5632 clear_color[3] as f32,
5633 ]),
5634 cmds: Vec::with_capacity(scene.nodes.len()),
5635 };
5636 let mut target_stack: Vec<PassTarget> = Vec::new();
5637 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
5638 let mut layer_blurs: Vec<(u32, f32, f32)> = Vec::new();
5639 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
5640
5641 struct Batch {
5642 rects: Vec<RectInstance>,
5643 borders: Vec<BorderInstance>,
5644 ellipses: Vec<EllipseInstance>,
5645 e_borders: Vec<EllipseBorderInstance>,
5646 arcs: Vec<ArcInstance>,
5647 masks: Vec<GlyphInstance>,
5648 colors: Vec<GlyphInstance>,
5649 nv12s: Vec<Nv12Instance>,
5650 }
5651
5652 impl Batch {
5653 fn new() -> Self {
5654 Self {
5655 rects: vec![],
5656 borders: vec![],
5657 ellipses: vec![],
5658 e_borders: vec![],
5659 arcs: vec![],
5660 masks: vec![],
5661 colors: vec![],
5662 nv12s: vec![],
5663 }
5664 }
5665
5666 fn is_empty(&self) -> bool {
5667 self.rects.is_empty()
5668 && self.borders.is_empty()
5669 && self.ellipses.is_empty()
5670 && self.e_borders.is_empty()
5671 && self.arcs.is_empty()
5672 && self.masks.is_empty()
5673 && self.colors.is_empty()
5674 && self.nv12s.is_empty()
5675 }
5676
5677 fn flush(
5678 &mut self,
5679 pipes: (
5680 &mut InstancedPipe<RectInstance>,
5681 &mut InstancedPipe<BorderInstance>,
5682 &mut InstancedPipe<EllipseInstance>,
5683 &mut InstancedPipe<EllipseBorderInstance>,
5684 &mut InstancedPipe<ArcInstance>,
5685 ),
5686 glyph_pipes: (
5687 &mut InstancedPipe<GlyphInstance>,
5688 &mut InstancedPipe<GlyphInstance>,
5689 ),
5690 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
5691 device: &wgpu::Device,
5692 queue: &wgpu::Queue,
5693 cmds: &mut Vec<Cmd>,
5694 ) {
5695 let (rects, borders, ellipses, e_borders, arcs) = pipes;
5696 let (masks, colors) = glyph_pipes;
5697
5698 macro_rules! flush_one {
5699 ($buf:ident, $pipe:expr, $variant:ident) => {
5700 if !self.$buf.is_empty() {
5701 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
5702 cmds.push(Cmd::$variant { off, cnt });
5703 }
5704 self.$buf.clear();
5705 }
5706 };
5707 }
5708
5709 flush_one!(rects, rects, Rect);
5710 flush_one!(borders, borders, Border);
5711 flush_one!(ellipses, ellipses, Ellipse);
5712 flush_one!(e_borders, e_borders, EllipseBorder);
5713 flush_one!(arcs, arcs, Arc);
5714 flush_one!(masks, masks, GlyphsMask);
5715 flush_one!(colors, colors, GlyphsColor);
5716
5717 if !self.nv12s.is_empty() {
5718 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
5719 let _ = (off, cnt);
5720 }
5721 self.nv12s.clear();
5722 }
5723 }
5724 }
5725
5726 self.rects.reset();
5727 self.borders.reset();
5728 self.ellipses.reset();
5729 self.ellipse_borders.reset();
5730 self.arcs.reset();
5731 self.glyph_mask.reset();
5732 self.glyph_color.reset();
5733 self.clip_ring.reset();
5734 self.blur_ring.reset();
5735 self.nv12.reset();
5736
5737 self.slug_ring.reset();
5738 self.mesh_verts.reset();
5739 self.mesh_indices.reset();
5740 self.mesh_uniform_head = 0;
5741 self.mesh_clip_stack.clear();
5742 self.projective_ring.reset();
5743 self.blend_ring.reset();
5744 for id in self.flatten_layer_ids.drain(..) {
5748 self.layer_pool.remove(&id);
5749 }
5750 self.blend_snapshots.clear();
5751 self.blend_copies.clear();
5752 let mut batch = Batch::new();
5753 let mut slug_verts_local: Vec<slug::TessVertex> = Vec::new();
5754 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
5755 let mut flatten_stack: Vec<FlattenRecord> = Vec::new();
5756 let mut flatten_id_head: u32 = FLATTEN_ID_BASE;
5757 let mut flatten_ids_used: Vec<u32> = Vec::new();
5758 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
5759 let mut clip_cmd_stack: Vec<(u64, u32, bool)> = Vec::with_capacity(8);
5763 let mut root_clip_rect = repose_core::Rect {
5764 x: 0.0,
5765 y: 0.0,
5766 w: fb_w,
5767 h: fb_h,
5768 };
5769 let mut saved_scissor_stack: Vec<repose_core::Rect> = Vec::new();
5770 let mut saved_root_clip_rect = root_clip_rect;
5771
5772 let mut current_prim: Option<&'static str> = None;
5773
5774 macro_rules! flush_if_prim_changed {
5775 ($prim:literal, $pipe:expr) => {
5776 if current_prim != Some($prim) {
5777 flush_batch!();
5778 current_prim = Some($prim);
5779 }
5780 };
5781 }
5782
5783 macro_rules! flush_batch {
5784 () => {
5785 if !batch.is_empty() {
5786 batch.flush(
5787 (
5788 &mut self.rects,
5789 &mut self.borders,
5790 &mut self.ellipses,
5791 &mut self.ellipse_borders,
5792 &mut self.arcs,
5793 ),
5794 (&mut self.glyph_mask, &mut self.glyph_color),
5795 &mut self.nv12,
5796 &self.device,
5797 &self.queue,
5798 &mut current_pass.cmds,
5799 )
5800 }
5801 };
5802 }
5803 for node in &scene.nodes {
5804 let t_identity = Transform::identity();
5805 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5806
5807 match node {
5808 SceneNode::Rect {
5809 rect,
5810 brush,
5811 radius,
5812 } => {
5813 flush_if_prim_changed!("rect", &self.rects);
5814 let (ndc, fwd_mat) = rect_to_instance_ndc(
5815 *rect,
5816 current_transform,
5817 current_target_size.0,
5818 current_target_size.1,
5819 );
5820 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
5821 brush_to_shape_fields(brush, rect, current_transform);
5822 batch.rects.push(RectInstance {
5823 xywh: ndc,
5824 radii: radius.map(|r| r.0),
5825 brush_type,
5826 grad_kind,
5827 _pad: [0.0; 2],
5828 color0,
5829 color1,
5830 grad_p0,
5831 grad_p1,
5832 tile_mode,
5833 _pad2: [0.0; 3],
5834 fwd_mat,
5835 });
5836 }
5837 SceneNode::Border {
5838 rect,
5839 brush,
5840 width,
5841 radius,
5842 } => {
5843 flush_if_prim_changed!("border", &self.borders);
5844 let (ndc, fwd_mat) = rect_to_instance_ndc(
5845 *rect,
5846 current_transform,
5847 current_target_size.0,
5848 current_target_size.1,
5849 );
5850 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
5851 brush_to_shape_fields(brush, rect, current_transform);
5852 batch.borders.push(BorderInstance {
5853 xywh: ndc,
5854 radii: radius.map(|r| r.0),
5855 stroke: width.0,
5856 brush_type,
5857 _pad: [0.0; 2],
5858 grad_kind,
5859 color0,
5860 color1,
5861 grad_p0,
5862 grad_p1,
5863 tile_mode,
5864 _pad2: [0.0; 3],
5865 fwd_mat,
5866 });
5867 }
5868 SceneNode::Ellipse { rect, brush } => {
5869 flush_if_prim_changed!("ellipse", &self.ellipses);
5870 let (ndc, fwd_mat) = rect_to_instance_ndc(
5871 *rect,
5872 current_transform,
5873 current_target_size.0,
5874 current_target_size.1,
5875 );
5876 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
5877 brush_to_shape_fields(brush, rect, current_transform);
5878 batch.ellipses.push(EllipseInstance {
5879 xywh: ndc,
5880 brush_type,
5881 grad_kind,
5882 _pad: [0.0; 2],
5883 color0,
5884 color1,
5885 grad_p0,
5886 grad_p1,
5887 tile_mode,
5888 _pad2: [0.0; 3],
5889 fwd_mat,
5890 });
5891 }
5892 SceneNode::EllipseBorder { rect, brush, width } => {
5893 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
5894 let (ndc, fwd_mat) = rect_to_instance_ndc(
5895 *rect,
5896 current_transform,
5897 current_target_size.0,
5898 current_target_size.1,
5899 );
5900 let pad_px = width.0 * 0.5 + 2.0;
5901 let pad = (pad_px / current_target_size.0) * 2.0;
5902 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
5903 brush_to_shape_fields(brush, rect, current_transform);
5904 batch.e_borders.push(EllipseBorderInstance {
5905 xywh: ndc,
5906 stroke: width.0,
5907 pad,
5908 brush_type,
5909 grad_kind,
5910 color0,
5911 color1,
5912 grad_p0,
5913 grad_p1,
5914 tile_mode,
5915 _pad2: [0.0; 3],
5916 fwd_mat,
5917 });
5918 }
5919 SceneNode::Arc {
5920 rect,
5921 start_angle,
5922 sweep_angle,
5923 stroke_width,
5924 brush,
5925 cap,
5926 } => {
5927 flush_if_prim_changed!("arc", &self.arcs);
5928 let (ndc, fwd_mat) = rect_to_instance_ndc(
5929 *rect,
5930 current_transform,
5931 current_target_size.0,
5932 current_target_size.1,
5933 );
5934 let pad_px = stroke_width.0 * 0.5 + 2.0;
5935 let pad = (pad_px / current_target_size.0) * 2.0;
5936 let cap_val = match cap {
5937 StrokeCap::Butt => 0.0,
5938 StrokeCap::Round => 1.0,
5939 StrokeCap::Square => 2.0,
5940 };
5941 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
5942 brush_to_shape_fields(brush, rect, current_transform);
5943 batch.arcs.push(ArcInstance {
5944 xywh: ndc,
5945 start_angle: *start_angle,
5946 sweep_angle: *sweep_angle,
5947 stroke: stroke_width.0,
5948 pad,
5949 brush_type,
5950 grad_kind,
5951 _pad0: [0.0; 2],
5952 color0,
5953 color1,
5954 grad_p0,
5955 grad_p1,
5956 tile_mode,
5957 cap: cap_val,
5958 _pad1: [0.0; 2],
5959 fwd_mat,
5960 });
5961 }
5962 SceneNode::Text {
5963 rect,
5964 text,
5965 color,
5966 size,
5967 font_family,
5968 text_align: _,
5969 font_weight,
5970 font_style,
5971 text_decoration,
5972 letter_spacing,
5973 line_height: _,
5974 extra_style,
5975 url: _,
5976 font_variation_settings,
5977 } => {
5978 flush_batch!(); let px = size.0;
5981 let lh_ratio = rect.h / px;
5982 let fw = font_weight.0;
5983 let fs = if *font_style == FontStyle::Italic {
5984 1
5985 } else {
5986 0
5987 };
5988 let shaped = repose_text::shape_line(
5989 text.as_ref(),
5990 px,
5991 lh_ratio,
5992 *font_family,
5993 fw,
5994 fs,
5995 letter_spacing.0,
5996 font_variation_settings.as_deref(),
5997 );
5998 let baseline_y = shaped.first().map(|g| rect.y + g.y);
5999
6000 let fwd = forward_rs_mat(current_transform);
6001 let has_linear = fwd != [1.0, 0.0, 0.0, 1.0];
6002
6003 let lin = current_transform.linear();
6004 let tr_x = current_transform.translate_x;
6005 let tr_y = current_transform.translate_y;
6006
6007 let make_glyph_instance =
6008 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 4]) {
6009 if has_linear {
6010 let gc_x = gx + gw * 0.5;
6011 let gc_y = gy + gh * 0.5;
6012 let wc_x = lin[0] * gc_x + lin[1] * gc_y + tr_x;
6013 let wc_y = lin[2] * gc_x + lin[3] * gc_y + tr_y;
6014 let ww = gw * current_transform.scale_x;
6015 let wh = gh * current_transform.scale_y;
6016 let ex = fwd[0].abs() * ww * 0.5 + fwd[1].abs() * wh * 0.5;
6017 let ey = fwd[2].abs() * ww * 0.5 + fwd[3].abs() * wh * 0.5;
6018 let ndc_tl = to_ndc(
6019 wc_x - ex,
6020 wc_y - ey,
6021 ex * 2.0,
6022 ey * 2.0,
6023 current_target_size.0,
6024 current_target_size.1,
6025 );
6026 let ndc = [
6027 ndc_tl[0] + ndc_tl[2] * 0.5,
6028 ndc_tl[1] + ndc_tl[3] * 0.5,
6029 ndc_tl[2],
6030 ndc_tl[3],
6031 ];
6032 (ndc, fwd)
6033 } else {
6034 let (sx, sy) = if current_transform.scale_x == 1.0
6035 && current_transform.scale_y == 1.0
6036 {
6037 (gx.round(), gy.round())
6038 } else {
6039 (gx, gy)
6040 };
6041 rect_to_instance_ndc(
6042 repose_core::Rect {
6043 x: sx,
6044 y: sy,
6045 w: gw,
6046 h: gh,
6047 },
6048 current_transform,
6049 current_target_size.0,
6050 current_target_size.1,
6051 )
6052 }
6053 };
6054
6055 let baseline_shift_y: f32 = px * extra_style.baseline_shift.0;
6056
6057 let (
6058 draws_fill,
6059 is_stroke,
6060 stroke_width,
6061 stroke_cap,
6062 stroke_join,
6063 stroke_miter,
6064 stroke_path_effect,
6065 ) = match &extra_style.draw_style {
6066 repose_core::DrawStyle::Stroke {
6067 width,
6068 cap,
6069 join,
6070 miter,
6071 path_effect,
6072 } => (
6073 false,
6074 true,
6075 *width,
6076 *cap,
6077 *join,
6078 *miter,
6079 path_effect.clone(),
6080 ),
6081 repose_core::DrawStyle::FillAndStroke {
6082 width,
6083 cap,
6084 join,
6085 miter,
6086 path_effect,
6087 } => (true, true, *width, *cap, *join, *miter, path_effect.clone()),
6088 _ => (
6089 true,
6090 false,
6091 0.0,
6092 repose_core::StrokeCap::Butt,
6093 repose_core::StrokeJoin::Miter,
6094 4.0,
6095 None,
6096 ),
6097 };
6098 let stroke_tess_key = if is_stroke {
6099 Some(slug::StrokeTessKey::new(
6100 stroke_width,
6101 stroke_cap,
6102 stroke_join,
6103 stroke_miter,
6104 &stroke_path_effect,
6105 ))
6106 } else {
6107 None
6108 };
6109
6110 for sg in shaped {
6111 let gx = rect.x + sg.x + sg.bearing_x;
6112 let gy = rect.y + sg.y - sg.bearing_y + baseline_shift_y;
6113
6114 if self.slug_enabled {
6116 let ck = repose_text::lookup_cache_key(sg.key, sg.px);
6117 if let Some(ref ck) = ck {
6118 let need_tessellate = self.slug_cache.get(ck).is_none_or(|g| {
6120 (draws_fill && g.fill_vertices.is_none())
6121 || (is_stroke
6122 && !g
6123 .stroke_variants
6124 .contains_key(stroke_tess_key.as_ref().unwrap()))
6125 });
6126 if need_tessellate {
6127 if let Some((ck2, commands)) =
6128 repose_text::lookup_and_extract_outline(sg.key, sg.px)
6129 {
6130 let font_size_px = f32::from_bits(ck2.font_size_bits);
6131 if draws_fill {
6132 self.slug_cache.get_or_insert(
6133 ck2,
6134 font_size_px,
6135 &commands,
6136 );
6137 }
6138 if is_stroke {
6139 self.slug_cache.get_or_insert_stroke(
6140 ck2,
6141 font_size_px,
6142 &commands,
6143 stroke_width,
6144 stroke_cap,
6145 stroke_join,
6146 stroke_miter,
6147 &stroke_path_effect,
6148 );
6149 }
6150 }
6151 } else {
6152 self.slug_cache.touch(ck);
6153 }
6154 }
6155 if let Some(entry) = ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
6156 {
6157 let ox = rect.x + sg.x;
6158 let oy = rect.y + sg.y + baseline_shift_y;
6159 let scx = current_transform.scale_x;
6160 let scy = current_transform.scale_y;
6161 let ttx = current_transform.translate_x;
6162 let tty = current_transform.translate_y;
6163
6164 let tf = |x: f32, y: f32| -> (f32, f32) {
6165 if has_linear {
6166 (
6167 lin[0] * x + lin[1] * y + ttx,
6168 lin[2] * x + lin[3] * y + tty,
6169 )
6170 } else {
6171 (x * scx + ttx, y * scy + tty)
6172 }
6173 };
6174
6175 let tw = current_target_size.0;
6176 let th = current_target_size.1;
6177
6178 let mut emit = |verts: &[[f32; 2]]| {
6179 for &v in verts {
6180 let (sx, sy) = tf(ox + v[0] * px, oy - v[1] * px);
6181 let ndc_x = sx / tw * 2.0 - 1.0;
6182 let ndc_y = -(sy / th) * 2.0 + 1.0;
6183 slug_verts_local.push(slug::TessVertex {
6184 ndc_pos: [ndc_x, ndc_y],
6185 color: color.to_linear(),
6186 });
6187 }
6188 };
6189 if draws_fill {
6190 emit(entry.fill_vertices.as_deref().unwrap_or(&[]));
6191 }
6192 if is_stroke {
6193 let key = stroke_tess_key.as_ref().unwrap();
6194 emit(
6195 entry
6196 .stroke_variants
6197 .get(key)
6198 .map(|v| v.as_slice())
6199 .unwrap_or(&[]),
6200 );
6201 }
6202
6203 if !draws_fill {
6204 continue;
6206 }
6207 continue;
6208 }
6209 }
6210
6211 if !draws_fill {
6212 continue;
6214 }
6215
6216 if let Some(info) = self.upload_glyph_color(sg.key, sg.px) {
6217 let (ndc, fwd_mat) = make_glyph_instance(gx, gy, info.w, info.h);
6218 batch.colors.push(GlyphInstance {
6219 xywh: ndc,
6220 uv: [info.u0, info.v1, info.u1, info.v0],
6221 color: color.to_linear(),
6222 fwd_mat,
6223 });
6224 } else if let Some(info) = self.upload_glyph_mask(sg.key, sg.px) {
6225 let (ndc, fwd_mat) = make_glyph_instance(gx, gy, info.w, info.h);
6226 batch.masks.push(GlyphInstance {
6227 xywh: ndc,
6228 uv: [info.u0, info.v1, info.u1, info.v0],
6229 color: color.to_linear(),
6230 fwd_mat,
6231 });
6232 }
6233 }
6234
6235 if !slug_verts_local.is_empty() {
6237 let bytes = bytemuck::cast_slice(&slug_verts_local);
6238 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
6239 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
6240 current_pass.cmds.push(Cmd::GlyphsVector {
6241 off,
6242 cnt: slug_verts_local.len() as u32,
6243 });
6244 slug_verts_local.clear();
6245 }
6246
6247 if (text_decoration.underline || text_decoration.strikethrough)
6249 && let Some(baseline_y) = baseline_y
6250 {
6251 flush_batch!();
6252 current_prim = Some("rect");
6253 let deco_color = text_decoration.color.unwrap_or(*color);
6254 let thickness = (px * 0.07).max(1.0);
6255
6256 if text_decoration.underline {
6257 let dy = baseline_y + px * 0.1;
6258 let (ndc, fwd_mat) = rect_to_instance_ndc(
6259 repose_core::Rect {
6260 x: rect.x,
6261 y: dy,
6262 w: rect.w,
6263 h: thickness,
6264 },
6265 current_transform,
6266 current_target_size.0,
6267 current_target_size.1,
6268 );
6269 batch.rects.push(RectInstance {
6270 xywh: ndc,
6271 radii: [0.0; 4],
6272 brush_type: 0,
6273 grad_kind: 0,
6274 _pad: [0.0; 2],
6275 color0: deco_color.to_linear(),
6276 color1: [0.0; 4],
6277 grad_p0: [0.0; 2],
6278 grad_p1: [0.0; 2],
6279 tile_mode: 0,
6280 _pad2: [0.0; 3],
6281 fwd_mat,
6282 });
6283 }
6284 if text_decoration.strikethrough {
6285 let sy = baseline_y - px * 0.3;
6286 let (ndc, fwd_mat) = rect_to_instance_ndc(
6287 repose_core::Rect {
6288 x: rect.x,
6289 y: sy,
6290 w: rect.w,
6291 h: thickness,
6292 },
6293 current_transform,
6294 current_target_size.0,
6295 current_target_size.1,
6296 );
6297 batch.rects.push(RectInstance {
6298 xywh: ndc,
6299 radii: [0.0; 4],
6300 brush_type: 0,
6301 grad_kind: 0,
6302 _pad: [0.0; 2],
6303 color0: deco_color.to_linear(),
6304 color1: [0.0; 4],
6305 grad_p0: [0.0; 2],
6306 grad_p1: [0.0; 2],
6307 tile_mode: 0,
6308 _pad2: [0.0; 3],
6309 fwd_mat,
6310 });
6311 }
6312 }
6313 }
6314 SceneNode::Image {
6315 rect,
6316 handle,
6317 tint,
6318 fit,
6319 } => {
6320 flush_batch!();
6321
6322 let (img_w, img_h, is_nv12) = match self.resolve_image_for_draw(*handle) {
6325 Some(wh) => wh,
6326 None => {
6327 log::warn!("Image handle {} not found", handle);
6328 continue;
6329 }
6330 };
6331
6332 let src_w = img_w as f32;
6333 let src_h = img_h as f32;
6334
6335 let dst_w = rect.w.max(0.0);
6336 let dst_h = rect.h.max(0.0);
6337 if dst_w <= 0.0 || dst_h <= 0.0 {
6338 continue;
6339 }
6340
6341 let (draw_rect, uv_rect) = match fit {
6342 repose_core::view::ImageFit::Contain => {
6343 let scale = (dst_w / src_w).min(dst_h / src_h);
6344 let w = src_w * scale;
6345 let h = src_h * scale;
6346 (
6347 repose_core::Rect {
6348 x: rect.x + (dst_w - w) * 0.5,
6349 y: rect.y + (dst_h - h) * 0.5,
6350 w,
6351 h,
6352 },
6353 [0.0, 1.0, 1.0, 0.0],
6354 )
6355 }
6356 repose_core::view::ImageFit::Cover => {
6357 let scale = (dst_w / src_w).max(dst_h / src_h);
6358 let content_w = src_w * scale;
6359 let content_h = src_h * scale;
6360 let overflow_x = (content_w - dst_w) * 0.5;
6361 let overflow_y = (content_h - dst_h) * 0.5;
6362 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
6363 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
6364 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
6365 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
6366 (*rect, [u0, 1.0 - v0, u1, 1.0 - v1])
6367 }
6368 repose_core::view::ImageFit::FitWidth => {
6369 let scale = dst_w / src_w;
6370 (
6371 repose_core::Rect {
6372 x: rect.x,
6373 y: rect.y + (dst_h - src_h * scale) * 0.5,
6374 w: dst_w,
6375 h: src_h * scale,
6376 },
6377 [0.0, 1.0, 1.0, 0.0],
6378 )
6379 }
6380 repose_core::view::ImageFit::FitHeight => {
6381 let scale = dst_h / src_h;
6382 (
6383 repose_core::Rect {
6384 x: rect.x + (dst_w - src_w * scale) * 0.5,
6385 y: rect.y,
6386 w: src_w * scale,
6387 h: dst_h,
6388 },
6389 [0.0, 1.0, 1.0, 0.0],
6390 )
6391 }
6392 repose_core::view::ImageFit::FillBounds => (*rect, [0.0, 1.0, 1.0, 0.0]),
6393 repose_core::view::ImageFit::Inside => {
6394 let scale = (dst_w / src_w).min(dst_h / src_h).min(1.0);
6395 let w = src_w * scale;
6396 let h = src_h * scale;
6397 (
6398 repose_core::Rect {
6399 x: rect.x + (dst_w - w) * 0.5,
6400 y: rect.y + (dst_h - h) * 0.5,
6401 w,
6402 h,
6403 },
6404 [0.0, 1.0, 1.0, 0.0],
6405 )
6406 }
6407 repose_core::view::ImageFit::None => {
6408 (
6409 repose_core::Rect {
6410 x: rect.x,
6411 y: rect.y,
6412 w: src_w.min(dst_w),
6413 h: src_h.min(dst_h),
6414 },
6415 [
6417 0.0,
6418 1.0,
6419 (dst_w / src_w).min(1.0),
6420 1.0 - (dst_h / src_h).min(1.0),
6421 ],
6422 )
6423 }
6424 _ => continue,
6425 };
6426
6427 let (ndc_center, fwd_mat) = rect_to_instance_ndc(
6428 draw_rect,
6429 current_transform,
6430 current_target_size.0,
6431 current_target_size.1,
6432 );
6433
6434 if is_nv12 {
6435 let uv_x_offset = if let Some(ImageTex::Nv12 { w, color_info, .. }) =
6436 self.images.get(handle)
6437 {
6438 match color_info.chroma_siting {
6439 ChromaSiting::Center | ChromaSiting::TopLeft => 0.0,
6440 ChromaSiting::Left => -1.0 / *w as f32,
6441 }
6442 } else {
6443 0.0
6444 };
6445
6446 let inst = Nv12Instance {
6447 xywh: ndc_center,
6448 uv: uv_rect,
6449 color: tint.to_linear(),
6450 uv_x_offset,
6451 fwd_mat,
6452 _pad: [0.0],
6453 };
6454 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
6455 {
6456 current_pass.cmds.push(Cmd::ImageNv12 {
6457 off,
6458 cnt: 1,
6459 handle: *handle,
6460 });
6461 }
6462 } else {
6463 let inst = GlyphInstance {
6465 xywh: ndc_center,
6466 uv: uv_rect,
6467 color: tint.to_linear(),
6468 fwd_mat,
6469 };
6470 if let Some((off, _)) =
6471 self.glyph_color.upload(&self.device, &self.queue, &[inst])
6472 {
6473 current_pass.cmds.push(Cmd::ImageRgba {
6474 off,
6475 cnt: 1,
6476 handle: *handle,
6477 });
6478 }
6479 }
6480 }
6481 SceneNode::Coverage {
6482 rect,
6483 handle,
6484 color,
6485 } => {
6486 flush_batch!();
6487 let Some((tile_w, tile_h)) = self.coverage_dimensions(*handle) else {
6490 log::warn!("Coverage handle {handle} not found");
6491 continue;
6492 };
6493 let draw_rect = repose_core::Rect {
6496 x: rect.x,
6497 y: rect.y,
6498 w: tile_w as f32,
6499 h: tile_h as f32,
6500 };
6501 let (ndc_center, fwd_mat) = rect_to_instance_ndc(
6502 draw_rect,
6503 current_transform,
6504 current_target_size.0,
6505 current_target_size.1,
6506 );
6507 let inst = GlyphInstance {
6508 xywh: ndc_center,
6509 uv: [0.0, 1.0, 1.0, 0.0],
6510 color: color.to_linear(),
6511 fwd_mat,
6512 };
6513 if let Some((off, _)) =
6514 self.glyph_color.upload(&self.device, &self.queue, &[inst])
6515 {
6516 current_pass.cmds.push(Cmd::Coverage {
6517 off,
6518 cnt: 1,
6519 handle: *handle,
6520 });
6521 }
6522 }
6523 SceneNode::PushClip { rect, radius, op } => {
6524 flush_batch!(); let is_diff = matches!(op, repose_core::ClipOp::Difference);
6527
6528 let t_identity = Transform::identity();
6529 let current_transform = transform_stack.last().unwrap_or(&t_identity);
6530 let transformed = affine_aabb(current_transform, rect);
6531
6532 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
6533 let next_scissor = if is_diff {
6534 top
6535 } else {
6536 intersect(top, transformed)
6537 };
6538 scissor_stack.push(next_scissor);
6539 let scissor = to_scissor(
6540 &next_scissor,
6541 current_target_size.0 as u32,
6542 current_target_size.1 as u32,
6543 );
6544
6545 let clip_ndc_tl = to_ndc(
6546 transformed.x,
6547 transformed.y,
6548 transformed.w,
6549 transformed.h,
6550 current_target_size.0,
6551 current_target_size.1,
6552 );
6553 let inst = ClipInstance {
6554 xywh: [
6555 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
6556 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
6557 clip_ndc_tl[2],
6558 clip_ndc_tl[3],
6559 ],
6560 radii: radius.map(|r| r.0),
6561 fwd_mat: [1.0, 0.0, 0.0, 1.0],
6562 };
6563 let bytes = bytemuck::bytes_of(&inst);
6564 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
6565 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
6566
6567 let rounded = radius.iter().any(|&r| r.0 > 0.5);
6568
6569 current_pass.cmds.push(Cmd::ClipPush {
6570 off,
6571 cnt: 1,
6572 scissor,
6573 difference: is_diff,
6574 rounded,
6575 });
6576 clip_cmd_stack.push((off, 1, is_diff));
6577 }
6578 SceneNode::PopClip => {
6579 flush_batch!();
6580
6581 if !scissor_stack.is_empty() {
6582 scissor_stack.pop();
6583 } else {
6584 log::warn!("PopClip with empty stack");
6585 }
6586
6587 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
6588 let scissor = to_scissor(
6589 &top,
6590 current_target_size.0 as u32,
6591 current_target_size.1 as u32,
6592 );
6593 let (off, cnt, difference) = clip_cmd_stack.pop().unwrap_or((0, 0, false));
6594 current_pass.cmds.push(Cmd::ClipPop {
6595 off,
6596 cnt,
6597 scissor,
6598 difference,
6599 });
6600 }
6601 SceneNode::Shadow {
6602 rect,
6603 radius,
6604 elevation: _,
6605 color,
6606 } => {
6607 flush_if_prim_changed!("rect", &self.rects);
6608 let (ndc, fwd_mat) = rect_to_instance_ndc(
6609 *rect,
6610 current_transform,
6611 current_target_size.0,
6612 current_target_size.1,
6613 );
6614 let (brush_type, color0, _color1, _grad_p0, _grad_p1) =
6615 brush_to_instance_fields(&Brush::Solid(*color));
6616 batch.rects.push(RectInstance {
6617 xywh: ndc,
6618 radii: radius.map(|r| r.0),
6619 brush_type,
6620 grad_kind: 0,
6621 _pad: [0.0; 2],
6622 color0,
6623 color1: [0.0; 4],
6624 grad_p0: [0.0; 2],
6625 grad_p1: [0.0; 2],
6626 tile_mode: 0,
6627 _pad2: [0.0; 3],
6628 fwd_mat,
6629 });
6630 }
6631 SceneNode::PushTransform { transform } => {
6632 flush_batch!(); if transform.has_perspective() {
6634 let top = *transform_stack.last().unwrap_or(&t_identity);
6639 self.push_perspective_layer(
6640 *transform,
6641 top,
6642 &mut transform_stack,
6643 &mut scissor_stack,
6644 &mut root_clip_rect,
6645 &mut current_target_size,
6646 &mut current_pass,
6647 &mut passes,
6648 &mut target_stack,
6649 &mut flatten_stack,
6650 &mut flatten_id_head,
6651 &mut flatten_ids_used,
6652 );
6653 } else {
6654 let combined = current_transform.combine(transform);
6655 transform_stack.push(combined);
6656 }
6657 }
6658 SceneNode::PopTransform => {
6659 flush_batch!(); if let Some(rec) = flatten_stack.last() {
6661 if transform_stack.len() == rec.stack_len + 2 {
6665 let rec = flatten_stack.pop().expect("checked above");
6666 transform_stack.pop();
6667 transform_stack.pop();
6668 self.pop_perspective_layer(
6669 rec,
6670 &mut scissor_stack,
6671 &mut root_clip_rect,
6672 &mut current_target_size,
6673 &mut current_pass,
6674 &mut passes,
6675 &mut target_stack,
6676 );
6677 continue;
6678 }
6679 }
6680 transform_stack.pop();
6681 }
6682 SceneNode::BeginLayer {
6683 rect,
6684 layer_id,
6685 alpha,
6686 blur_radius_x,
6687 blur_radius_y,
6688 rectangle_edge: _,
6689 } => {
6690 flush_batch!();
6691 let w = (rect.w.round().max(1.0)) as u32;
6694 let h = (rect.h.round().max(1.0)) as u32;
6695 saved_scissor_stack =
6696 std::mem::replace(&mut scissor_stack, Vec::with_capacity(8));
6697 saved_root_clip_rect = std::mem::replace(
6698 &mut root_clip_rect,
6699 repose_core::Rect {
6700 x: 0.0,
6701 y: 0.0,
6702 w: w as f32,
6703 h: h as f32,
6704 },
6705 );
6706 scissor_stack.push(root_clip_rect);
6707 let prev_target = current_pass.target;
6709 let prev_scissor = current_pass.initial_scissor;
6710 let saved = std::mem::replace(
6711 &mut current_pass,
6712 Pass {
6713 target: PassTarget::Layer(*layer_id),
6714 initial_scissor: (0, 0, w, h),
6715 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
6716 cmds: Vec::new(),
6717 },
6718 );
6719 passes.push(saved);
6720 target_stack.push(prev_target);
6721 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
6725 current_target_size = (w as f32, h as f32);
6726 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
6727 if blur_radius_x.0 > 0.0 || blur_radius_y.0 > 0.0 {
6729 layer_blurs.push((*layer_id, blur_radius_x.0, blur_radius_y.0));
6730 }
6731 }
6732 SceneNode::EndLayer { layer_id } => {
6733 flush_batch!();
6734 scissor_stack = std::mem::take(&mut saved_scissor_stack);
6735 root_clip_rect = saved_root_clip_rect;
6736 let saved = std::mem::replace(
6738 &mut current_pass,
6739 Pass {
6740 target: target_stack.pop().unwrap_or(PassTarget::Surface),
6741 initial_scissor: (0, 0, self.output_width, self.output_height),
6742 clear_color: None, cmds: Vec::new(),
6744 },
6745 );
6746 passes.push(saved);
6747 current_target_size = (fb_w, fb_h);
6748 if let Some((_, layer_alpha, _)) = layer_alphas
6750 .iter()
6751 .find(|(id, _, _)| id == layer_id)
6752 .copied()
6753 {
6754 let layer = self.layer_pool.get(layer_id).expect("layer target");
6755 let ndc_tl = to_ndc(
6756 layer.rect_px.0,
6757 layer.rect_px.1,
6758 layer.rect_px.2,
6759 layer.rect_px.3,
6760 fb_w,
6761 fb_h,
6762 );
6763 let uv_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
6764 let uv_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
6765 let blur_px_val = layer_blurs
6767 .iter()
6768 .find(|(id, _, _)| id == layer_id)
6769 .map(|(_, bx, by)| (*bx, *by));
6770 if let Some((blur_x, blur_y)) =
6771 blur_px_val.filter(|(bx, by)| *bx > 0.0 || *by > 0.0)
6772 {
6773 let bw_uv = (blur_x * 1.5) / layer.width.max(1) as f32;
6775 let bh_uv = (blur_y * 1.5) / layer.height.max(1) as f32;
6776 let inst = BlurInstance {
6777 xywh: [
6778 ndc_tl[0] + ndc_tl[2] * 0.5,
6779 ndc_tl[1] + ndc_tl[3] * 0.5,
6780 ndc_tl[2],
6781 ndc_tl[3],
6782 ],
6783 uv: [0.0, 0.0, uv_u1, uv_v1],
6784 color: [1.0, 1.0, 1.0, layer_alpha],
6785 blur_uv: [bw_uv, bh_uv],
6786 fwd_mat: [1.0, 0.0, 0.0, 1.0],
6787 };
6788 self.blur_ring.grow_to_fit(
6789 &self.device,
6790 std::mem::size_of::<BlurInstance>() as u64,
6791 );
6792 let bytes = bytemuck::bytes_of(&inst);
6793 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
6794 current_pass.cmds.push(Cmd::CompositeBlur {
6795 off,
6796 cnt: 1,
6797 layer_id: *layer_id,
6798 });
6799 } else {
6800 let inst = GlyphInstance {
6802 xywh: [
6803 ndc_tl[0] + ndc_tl[2] * 0.5,
6804 ndc_tl[1] + ndc_tl[3] * 0.5,
6805 ndc_tl[2],
6806 ndc_tl[3],
6807 ],
6808 uv: [0.0, uv_v1, uv_u1, 0.0],
6809 color: [1.0, 1.0, 1.0, layer_alpha],
6810 fwd_mat: [1.0, 0.0, 0.0, 1.0],
6811 };
6812 if let Some((off, cnt)) =
6813 self.glyph_color.upload(&self.device, &self.queue, &[inst])
6814 {
6815 current_pass.cmds.push(Cmd::CompositeLayer {
6816 off,
6817 cnt,
6818 layer_id: *layer_id,
6819 });
6820 }
6821 }
6822 }
6823 }
6824 SceneNode::CompositeShadow {
6825 layer_id,
6826 blur_px,
6827 offset_px,
6828 color,
6829 } => {
6830 flush_batch!();
6831 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
6832 let sx = layer.rect_px.0 + offset_px.0.0;
6834 let sy = layer.rect_px.1 + offset_px.1.0;
6835 let sw = layer.rect_px.2;
6836 let sh = layer.rect_px.3;
6837 let bw_uv = (blur_px.0 * 1.5) / layer.width.max(1) as f32;
6840 let bh_uv = (blur_px.0 * 1.5) / layer.height.max(1) as f32;
6841 let shadow_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
6842 let shadow_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
6843 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
6844 let inst = BlurInstance {
6845 xywh: [
6846 ndc_tl[0] + ndc_tl[2] * 0.5,
6847 ndc_tl[1] + ndc_tl[3] * 0.5,
6848 ndc_tl[2],
6849 ndc_tl[3],
6850 ],
6851 uv: [0.0, 0.0, shadow_u1, shadow_v1],
6852 color: [
6853 color.0 as f32 / 255.0,
6854 color.1 as f32 / 255.0,
6855 color.2 as f32 / 255.0,
6856 color.3 as f32 / 255.0,
6857 ],
6858 blur_uv: [bw_uv, bh_uv],
6859 fwd_mat: [1.0, 0.0, 0.0, 1.0],
6860 };
6861 self.blur_ring
6862 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
6863 let bytes = bytemuck::bytes_of(&inst);
6864 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
6865 current_pass.cmds.push(Cmd::CompositeShadow {
6866 off,
6867 cnt: 1,
6868 layer_id: *layer_id,
6869 });
6870 }
6871 }
6872 SceneNode::VectorMesh {
6873 mesh,
6874 transform,
6875 paint,
6876 clip: _,
6877 blend,
6878 } => {
6879 flush_batch!();
6880 if blend.needs_isolation() {
6881 self.emit_isolated_blend(
6882 mesh.clone(),
6883 *transform,
6884 *paint,
6885 *blend,
6886 current_transform,
6887 &mut current_pass,
6888 &mut passes,
6889 &mut target_stack,
6890 &mut flatten_id_head,
6891 &mut flatten_ids_used,
6892 &mut current_target_size,
6893 fb_w,
6894 fb_h,
6895 );
6896 } else {
6897 let t_identity = Transform::identity();
6898 let current_transform =
6899 transform_stack.last().unwrap_or(&t_identity);
6900 self.emit_vector_mesh(
6901 current_transform,
6902 mesh,
6903 *transform,
6904 paint,
6905 *blend,
6906 &mut current_pass.cmds,
6907 );
6908 }
6909 }
6910 SceneNode::VectorOverlay { meshes } => {
6911 flush_batch!();
6912 for m in meshes.iter() {
6913 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(m);
6914 let uoff = self.alloc_mesh_uniform(MeshUniform::identity());
6915 current_pass.cmds.push(Cmd::VectorOverlay {
6916 voff,
6917 vcnt,
6918 ioff,
6919 icnt,
6920 uoff,
6921 });
6922 }
6923 }
6924 SceneNode::PushVectorClip { mesh, op } => {
6925 flush_batch!();
6926 let difference = matches!(op, repose_core::ClipOp::Difference);
6927 let t_identity = Transform::identity();
6928 let current_transform = transform_stack.last().unwrap_or(&t_identity);
6929 let affine =
6930 combine_mesh_affine(current_transform, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
6931 let aabb = mesh_aabb(mesh, affine);
6932 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
6933 let next = if difference {
6937 top
6938 } else {
6939 intersect(top, aabb)
6940 };
6941 scissor_stack.push(next);
6942 let scissor = to_scissor(
6943 &next,
6944 current_target_size.0 as u32,
6945 current_target_size.1 as u32,
6946 );
6947 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
6948 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(
6949 affine,
6950 &repose_core::PaintDesc::Solid,
6951 ));
6952 current_pass.cmds.push(Cmd::VectorClipPush {
6953 voff,
6954 vcnt,
6955 ioff,
6956 icnt,
6957 uoff,
6958 scissor,
6959 difference,
6960 });
6961 self.mesh_clip_stack
6962 .push((voff, vcnt, ioff, icnt, uoff, difference));
6963 }
6964 SceneNode::PopVectorClip => {
6965 flush_batch!();
6966 if !scissor_stack.is_empty() {
6967 scissor_stack.pop();
6968 } else {
6969 log::warn!("PopVectorClip with empty scissor stack");
6970 }
6971 if let Some((voff, vcnt, ioff, icnt, uoff, difference)) =
6972 self.mesh_clip_stack.pop()
6973 {
6974 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
6975 let scissor = to_scissor(
6976 &top,
6977 current_target_size.0 as u32,
6978 current_target_size.1 as u32,
6979 );
6980 current_pass.cmds.push(Cmd::VectorClipPop {
6981 voff,
6982 vcnt,
6983 ioff,
6984 icnt,
6985 uoff,
6986 scissor,
6987 difference,
6988 });
6989 } else {
6990 log::warn!("PopVectorClip with empty clip stack");
6991 }
6992 }
6993 SceneNode::Callback { rect, payload } => {
6994 flush_batch!();
6995 let t = transform_stack
6996 .last()
6997 .copied()
6998 .unwrap_or(Transform::identity());
6999 let transformed = affine_aabb(&t, rect);
7000 current_pass.cmds.push(Cmd::Callback {
7001 rect: transformed,
7002 payload: payload.clone(),
7003 });
7004 }
7005 _ => {}
7006 }
7007 }
7008
7009 flush_batch!();
7010
7011 {
7012 let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
7013 let mut prepare_list: Vec<Arc<Callback>> = Vec::new();
7014 for node in &scene.nodes {
7015 if let SceneNode::Callback { payload, .. } = node
7016 && payload.downcast_ref::<Callback>().is_some()
7017 {
7018 let ptr = Arc::as_ptr(payload) as *const () as usize;
7019 if seen.insert(ptr)
7020 && let Ok(cb_arc) = payload.clone().downcast::<Callback>()
7021 {
7022 prepare_list.push(cb_arc);
7023 }
7024 }
7025 }
7026 if !prepare_list.is_empty() {
7027 let screen_desc = ScreenDescriptor {
7028 size_in_pixels: [self.output_width, self.output_height],
7029 pixels_per_point: self.pixels_per_point,
7030 target_format: self.output_format,
7031 sample_count: self.msaa_samples.max(1),
7032 };
7033 let mut user_cmd_bufs: Vec<wgpu::CommandBuffer> = Vec::new();
7034 for cb in &prepare_list {
7035 user_cmd_bufs.extend(cb.0.prepare(
7036 &self.device,
7037 &self.queue,
7038 encoder,
7039 &screen_desc,
7040 &mut self.callback_resources,
7041 ));
7042 }
7043 for cb in &prepare_list {
7044 user_cmd_bufs.extend(cb.0.finish_prepare(
7045 &self.device,
7046 &self.queue,
7047 encoder,
7048 &screen_desc,
7049 &mut self.callback_resources,
7050 ));
7051 }
7052 if !user_cmd_bufs.is_empty() {
7055 self.queue.submit(user_cmd_bufs);
7056 }
7057 }
7058 }
7059
7060 passes.push(current_pass);
7062
7063 let globals_bytes = std::mem::size_of::<Globals>() as u64;
7064 let globals_staging = self.device.create_buffer(&wgpu::BufferDescriptor {
7065 label: Some("globals staging"),
7066 size: (passes.len().max(1) as u64) * globals_bytes,
7067 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
7068 mapped_at_creation: false,
7069 });
7070 for (i, pass) in passes.iter().enumerate() {
7071 let (target_w, target_h) = match pass.target {
7072 PassTarget::Surface => (fb_w, fb_h),
7073 PassTarget::Layer(layer_id) => {
7074 let lt = self.layer_pool.get(&layer_id);
7075 (
7076 lt.map_or(fb_w, |l| l.width as f32),
7077 lt.map_or(fb_h, |l| l.height as f32),
7078 )
7079 }
7080 };
7081 self.queue.write_buffer(
7082 &globals_staging,
7083 (i as u64) * globals_bytes,
7084 bytemuck::bytes_of(&make_globals(target_w, target_h)),
7085 );
7086 }
7087
7088 let bind_mask = self.atlas_bind_group_mask();
7089 let bind_color = self.atlas_bind_group_color();
7090 let mut clip_depth: u32 = 0;
7091 let mut clip_depth_stack: Vec<u32> = Vec::new();
7092
7093 let snapshot_source = target_texture.cloned();
7094 for (pass_index, pass) in std::mem::take(&mut passes).into_iter().enumerate() {
7095 let needs_snapshot = pass.cmds.iter().any(|c| matches!(c, Cmd::BlendLayer { .. }));
7103 if needs_snapshot {
7104 let copies = std::mem::take(&mut self.blend_copies);
7105 let mut remaining = Vec::with_capacity(copies.len());
7106 for (blend_id, target, region) in copies {
7107 let (src_tex, tw, th) = match target {
7108 PassTarget::Layer(parent_id) => match self.layer_pool.get(&parent_id) {
7109 Some(lt) => (lt.texture.clone(), lt.width, lt.height),
7110 None => continue,
7111 },
7112 PassTarget::Surface => match &snapshot_source {
7113 Some(t) => (t.clone(), self.output_width, self.output_height),
7114 None => {
7115 remaining.push((blend_id, target, region));
7116 continue;
7117 }
7118 },
7119 };
7120 let Some(dst_tex) = self.blend_snapshot_texture(blend_id) else {
7121 continue;
7122 };
7123 let sx = (region.x.max(0.0) as u32).min(tw.saturating_sub(1));
7124 let sy = (region.y.max(0.0) as u32).min(th.saturating_sub(1));
7125 let cw = (region.w.ceil() as u32)
7126 .max(1)
7127 .min(tw.saturating_sub(sx).max(1));
7128 let ch = (region.h.ceil() as u32)
7129 .max(1)
7130 .min(th.saturating_sub(sy).max(1));
7131 encoder.copy_texture_to_texture(
7132 wgpu::TexelCopyTextureInfo {
7133 texture: &src_tex,
7134 mip_level: 0,
7135 origin: wgpu::Origin3d { x: sx, y: sy, z: 0 },
7136 aspect: wgpu::TextureAspect::All,
7137 },
7138 wgpu::TexelCopyTextureInfo {
7139 texture: &dst_tex,
7140 mip_level: 0,
7141 origin: wgpu::Origin3d::ZERO,
7142 aspect: wgpu::TextureAspect::All,
7143 },
7144 wgpu::Extent3d {
7145 width: cw,
7146 height: ch,
7147 depth_or_array_layers: 1,
7148 },
7149 );
7150 }
7151 self.blend_copies = remaining;
7152 }
7153 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
7154 PassTarget::Surface => {
7155 let swap_view = target_view.clone();
7156 let use_ws = self.working_space && self.ws_view.is_some();
7157 let (color, resolve) = if use_ws {
7158 let ws_view = self.ws_view.as_ref().unwrap();
7159 if let Some(msaa_view) = &self.msaa_view {
7160 (msaa_view.clone(), Some(ws_view.clone()))
7162 } else {
7163 (ws_view.clone(), None)
7165 }
7166 } else if let Some(msaa_view) = &self.msaa_view {
7167 (msaa_view.clone(), Some(swap_view))
7168 } else {
7169 (swap_view, None)
7170 };
7171 (color, resolve, self.depth_stencil_view.clone(), false)
7172 }
7173 PassTarget::Layer(layer_id) => {
7174 if let Some(lt) = self.layer_pool.get(&layer_id) {
7175 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
7176 } else {
7177 log::warn!("missing layer target {layer_id}");
7178 continue;
7179 }
7180 }
7181 };
7182
7183 encoder.copy_buffer_to_buffer(
7184 &globals_staging,
7185 (pass_index as u64) * globals_bytes,
7186 &self.globals_buf,
7187 0,
7188 globals_bytes,
7189 );
7190
7191 if is_layer {
7192 clip_depth_stack.push(clip_depth);
7193 clip_depth = 0;
7194 }
7195
7196 let (tw, th) = match pass.target {
7197 PassTarget::Surface => (self.output_width, self.output_height),
7198 PassTarget::Layer(layer_id) => self
7199 .layer_pool
7200 .get(&layer_id)
7201 .map(|l| (l.width, l.height))
7202 .unwrap_or((self.output_width, self.output_height)),
7203 };
7204 let initial_scissor = clamp_scissor(
7205 pass.initial_scissor.0,
7206 pass.initial_scissor.1,
7207 pass.initial_scissor.2,
7208 pass.initial_scissor.3,
7209 tw,
7210 th,
7211 );
7212
7213 let pipes: &Pipelines = if is_layer {
7214 &self.layer_pipes
7215 } else {
7216 &self.surface_pipes
7217 };
7218
7219 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
7220 label: Some("pass"),
7221 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
7222 view: &color_view,
7223 resolve_target: resolve_target.as_ref(),
7224 ops: wgpu::Operations {
7225 load: match pass.clear_color {
7226 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
7227 r: c[0] as f64,
7228 g: c[1] as f64,
7229 b: c[2] as f64,
7230 a: c[3] as f64,
7231 }),
7232 None => wgpu::LoadOp::Load,
7233 },
7234 store: wgpu::StoreOp::Store,
7235 },
7236 depth_slice: None,
7237 })],
7238 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
7239 view: &depth_stencil_view,
7240 depth_ops: None,
7241 stencil_ops: Some(wgpu::Operations {
7242 load: if is_layer || pass.clear_color.is_some() {
7243 wgpu::LoadOp::Clear(0)
7244 } else {
7245 wgpu::LoadOp::Load
7246 },
7247 store: wgpu::StoreOp::Store,
7248 }),
7249 }),
7250 timestamp_writes: None,
7251 occlusion_query_set: None,
7252 multiview_mask: None,
7253 });
7254
7255 rpass.set_bind_group(0, &self.globals_bind, &[]);
7256 rpass.set_stencil_reference(clip_depth);
7257 rpass.set_scissor_rect(
7258 initial_scissor.0,
7259 initial_scissor.1,
7260 initial_scissor.2,
7261 initial_scissor.3,
7262 );
7263
7264 macro_rules! draw_simple {
7265 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
7266 rpass.set_pipeline($pipeline);
7267 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
7268 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
7269 rpass.draw(0..6, 0..$n);
7270 }};
7271 }
7272
7273 macro_rules! draw_with_bind {
7274 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
7275 rpass.set_pipeline($pipeline);
7276 rpass.set_bind_group(1, $bind, &[]);
7277 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
7278 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
7279 rpass.draw(0..6, 0..$n);
7280 }};
7281 }
7282
7283 macro_rules! draw_indexed_mesh {
7284 ($pipeline:expr, $uoff:ident, $voff:ident, $vcnt:ident, $ioff:ident, $icnt:ident) => {{
7285 rpass.set_pipeline($pipeline);
7286 rpass.set_bind_group(1, &self.mesh_bind, &[$uoff as u32]);
7287 let vbytes = ($vcnt as u64) * std::mem::size_of::<MeshVertex>() as u64;
7288 rpass.set_vertex_buffer(0, self.mesh_verts.buf.slice($voff..$voff + vbytes));
7289 let ibytes = ($icnt as u64) * std::mem::size_of::<u32>() as u64;
7290 rpass.set_index_buffer(
7291 self.mesh_indices.buf.slice($ioff..$ioff + ibytes),
7292 wgpu::IndexFormat::Uint32,
7293 );
7294 rpass.draw_indexed(0..$icnt, 0, 0..1);
7295 }};
7296 }
7297
7298 for cmd in pass.cmds {
7299 match cmd {
7300 Cmd::ClipPush {
7301 off,
7302 cnt: n,
7303 scissor,
7304 difference,
7305 rounded: _,
7306 } => {
7307 let scissor =
7308 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
7309 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
7310 rpass.set_stencil_reference(clip_depth);
7311
7312 if difference {
7313 rpass.set_pipeline(&pipes.clip_dec);
7314 } else {
7315 rpass.set_pipeline(&pipes.clip_bin);
7324 }
7325
7326 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
7327 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
7328 rpass.draw(0..6, 0..n);
7329
7330 if !difference {
7331 clip_depth = (clip_depth + 1).min(255);
7332 rpass.set_stencil_reference(clip_depth);
7333 }
7334 }
7335
7336 Cmd::ClipPop {
7337 off,
7338 cnt: n,
7339 scissor,
7340 difference,
7341 } => {
7342 let scissor =
7343 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
7344 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
7345
7346 if !difference && n > 0 {
7347 rpass.set_stencil_reference(clip_depth);
7348 rpass.set_pipeline(&pipes.clip_dec);
7349 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
7350 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
7351 rpass.draw(0..6, 0..n);
7352 clip_depth = clip_depth.saturating_sub(1);
7353 } else if !difference {
7354 clip_depth = clip_depth.saturating_sub(1);
7355 }
7356 rpass.set_stencil_reference(clip_depth);
7357 }
7358
7359 Cmd::Rect { off, cnt: n } => {
7360 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
7361 }
7362
7363 Cmd::Border { off, cnt: n } => {
7364 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
7365 }
7366
7367 Cmd::GlyphsMask { off, cnt: n } => {
7368 draw_with_bind!(
7369 &pipes.text_mask,
7370 self.glyph_mask.ring,
7371 GlyphInstance,
7372 &bind_mask,
7373 off,
7374 n
7375 );
7376 }
7377
7378 Cmd::GlyphsColor { off, cnt: n } => {
7379 draw_with_bind!(
7380 &pipes.text_color,
7381 self.glyph_color.ring,
7382 GlyphInstance,
7383 &bind_color,
7384 off,
7385 n
7386 );
7387 }
7388
7389 Cmd::GlyphsVector { off, cnt: n } => {
7390 if let Some(slug_pipe) = pipes.slug.as_ref() {
7391 rpass.set_pipeline(slug_pipe);
7392 let bytes = (n as u64) * std::mem::size_of::<slug::TessVertex>() as u64;
7393 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
7394 rpass.draw(0..n, 0..1);
7395 }
7396 }
7397
7398 Cmd::ImageRgba {
7399 off,
7400 cnt: n,
7401 handle,
7402 } => {
7403 let bind_opt = match self.images.get(&handle) {
7404 Some(ImageTex::Rgba { bind, .. }) => Some(bind),
7405 Some(ImageTex::User { bind, .. }) => Some(bind),
7406 _ => None,
7407 };
7408 if let Some(bind) = bind_opt {
7409 draw_with_bind!(
7410 &pipes.image_rgba,
7411 self.glyph_color.ring,
7412 GlyphInstance,
7413 bind,
7414 off,
7415 n
7416 );
7417 }
7418 }
7419 Cmd::Coverage {
7420 off,
7421 cnt: n,
7422 handle,
7423 } => {
7424 if let Some(tile) = self.coverages.get(&handle) {
7425 draw_with_bind!(
7426 &pipes.coverage,
7427 self.glyph_color.ring,
7428 GlyphInstance,
7429 &tile.bind,
7430 off,
7431 n
7432 );
7433 }
7434 }
7435
7436 Cmd::ImageNv12 {
7437 off,
7438 cnt: n,
7439 handle,
7440 } => {
7441 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
7442 draw_with_bind!(
7443 &pipes.image_nv12,
7444 self.nv12.ring,
7445 Nv12Instance,
7446 bind,
7447 off,
7448 n
7449 );
7450 }
7451 }
7452
7453 Cmd::Ellipse { off, cnt: n } => {
7454 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
7455 }
7456
7457 Cmd::EllipseBorder { off, cnt: n } => {
7458 draw_simple!(
7459 &pipes.ellipse_borders,
7460 self.ellipse_borders.ring,
7461 EllipseBorderInstance,
7462 off,
7463 n
7464 );
7465 }
7466
7467 Cmd::Arc { off, cnt: n } => {
7468 draw_simple!(&pipes.arcs, self.arcs.ring, ArcInstance, off, n);
7469 }
7470
7471 Cmd::CompositeLayer {
7472 off,
7473 cnt: n,
7474 layer_id,
7475 } => {
7476 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
7477 draw_with_bind!(
7478 &pipes.image_rgba,
7479 self.glyph_color.ring,
7480 GlyphInstance,
7481 <.bind,
7482 off,
7483 n
7484 );
7485 }
7486 }
7487 Cmd::CompositeShadow {
7488 off,
7489 cnt: n,
7490 layer_id,
7491 } => {
7492 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
7493 draw_with_bind!(
7494 &pipes.blur,
7495 self.blur_ring,
7496 BlurInstance,
7497 <.bind_linear,
7498 off,
7499 n
7500 );
7501 }
7502 }
7503 Cmd::CompositeBlur {
7504 off,
7505 cnt: n,
7506 layer_id,
7507 } => {
7508 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
7509 draw_with_bind!(
7510 &pipes.blur_content,
7511 self.blur_ring,
7512 BlurInstance,
7513 <.bind_linear,
7514 off,
7515 n
7516 );
7517 }
7518 }
7519 Cmd::CompositeProjective {
7520 off,
7521 cnt: n,
7522 layer_id,
7523 } => {
7524 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
7525 draw_with_bind!(
7529 &pipes.projective_layer,
7530 self.projective_ring,
7531 ProjectiveInstance,
7532 <.bind,
7533 off,
7534 n
7535 );
7536 }
7537 }
7538
7539 Cmd::BlendLayer {
7540 off,
7541 cnt: n,
7542 src_layer,
7543 dst_layer,
7544 parent,
7545 } => {
7546 let src = self.layer_pool.get(&src_layer).cloned();
7547 let dst = dst_layer
7554 .and_then(|id| self.blend_snapshots.get(&id).cloned());
7555 let in_parent = match parent {
7556 PassTarget::Surface => !is_layer,
7557 PassTarget::Layer(id) => {
7558 matches!(pass.target, PassTarget::Layer(pid) if pid == id)
7559 }
7560 };
7561 if let (Some(src_lt), Some(dst_snap)) = (src, dst)
7562 && in_parent
7563 {
7564 let bytes =
7565 (n as u64) * std::mem::size_of::<BlendInstance>() as u64;
7566 rpass.set_pipeline(&pipes.blend_layer);
7567 rpass.set_scissor_rect(0, 0, tw, th);
7568 rpass.set_bind_group(0, &self.globals_bind, &[]);
7569 rpass.set_bind_group(1, &src_lt.bind, &[]);
7570 rpass.set_bind_group(2, &dst_snap.bind, &[]);
7571 rpass.set_vertex_buffer(
7572 0,
7573 self.blend_ring.buf.slice(off..off + bytes),
7574 );
7575 rpass.draw(0..6, 0..n);
7576 }
7577 }
7578
7579 Cmd::VectorMesh {
7580 voff,
7581 vcnt,
7582 ioff,
7583 icnt,
7584 uoff,
7585 blend,
7586 } => {
7587 let pipe = match blend {
7588 repose_core::BlendMode::Add => &pipes.mesh_add,
7589 repose_core::BlendMode::Multiply => &pipes.mesh_multiply,
7590 repose_core::BlendMode::Screen => &pipes.mesh_screen,
7591 repose_core::BlendMode::Darken => &pipes.mesh_darken,
7592 repose_core::BlendMode::Lighten => &pipes.mesh_lighten,
7593 _ => &pipes.mesh,
7594 };
7595 draw_indexed_mesh!(pipe, uoff, voff, vcnt, ioff, icnt);
7596 }
7597
7598 Cmd::VectorOverlay {
7599 voff,
7600 vcnt,
7601 ioff,
7602 icnt,
7603 uoff,
7604 } => {
7605 draw_indexed_mesh!(&pipes.mesh_overlay, uoff, voff, vcnt, ioff, icnt);
7606 }
7607
7608 Cmd::VectorClipPush {
7609 voff,
7610 vcnt,
7611 ioff,
7612 icnt,
7613 uoff,
7614 scissor,
7615 difference,
7616 } => {
7617 let scissor =
7618 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
7619 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
7620 rpass.set_stencil_reference(clip_depth);
7621 draw_indexed_mesh!(&pipes.mesh_clip_inc, uoff, voff, vcnt, ioff, icnt);
7622 if !difference {
7623 clip_depth = (clip_depth + 1).min(255);
7624 rpass.set_stencil_reference(clip_depth);
7625 }
7626 }
7631
7632 Cmd::VectorClipPop {
7633 voff,
7634 vcnt,
7635 ioff,
7636 icnt,
7637 uoff,
7638 scissor,
7639 difference,
7640 } => {
7641 if difference {
7647 rpass.set_stencil_reference((clip_depth + 1).min(255));
7648 } else {
7649 rpass.set_stencil_reference(clip_depth);
7650 }
7651 let scissor =
7652 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
7653 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
7654 draw_indexed_mesh!(&pipes.mesh_clip_dec, uoff, voff, vcnt, ioff, icnt);
7655 if !difference {
7656 clip_depth = clip_depth.saturating_sub(1);
7657 }
7658 rpass.set_stencil_reference(clip_depth);
7659 }
7660
7661 Cmd::Callback { rect, payload } => {
7662 if let Some(cb) = payload.downcast_ref::<Callback>() {
7663 let vp_x = rect.x.floor().max(0.0);
7664 let vp_y = rect.y.floor().max(0.0);
7665 let vp_w = rect.w.ceil().max(1.0);
7666 let vp_h = rect.h.ceil().max(1.0);
7667 if vp_w > 0.0 && vp_h > 0.0 {
7668 rpass.set_viewport(vp_x, vp_y, vp_w, vp_h, 0.0, 1.0);
7669 let info = repose_core::PaintCallbackInfo {
7670 viewport: rect,
7671 clip_rect: rect,
7672 pixels_per_point: self.pixels_per_point,
7673 screen_size_px: [tw, th],
7674 };
7675 let rpass_static: &mut wgpu::RenderPass<'static> = unsafe {
7676 std::mem::transmute::<
7677 &mut wgpu::RenderPass<'_>,
7678 &mut wgpu::RenderPass<'static>,
7679 >(&mut rpass)
7680 };
7681 cb.0.paint(info, rpass_static, &self.callback_resources);
7682 rpass.set_viewport(0.0, 0.0, tw as f32, th as f32, 0.0, 1.0);
7683 rpass.set_bind_group(0, &self.globals_bind, &[]);
7684 rpass.set_stencil_reference(clip_depth);
7685 }
7686 } else {
7687 log::warn!("Unknown paint callback payload");
7688 }
7689 }
7690 }
7691 }
7692 if is_layer {
7693 clip_depth = clip_depth_stack.pop().unwrap_or(0);
7694 }
7695 }
7696
7697 self.flatten_layer_ids = flatten_ids_used;
7699
7700 if self.working_space
7702 && let (Some(_ws_view), Some(ws_bind), Some(display_pipeline)) =
7703 (&self.ws_view, &self.ws_bind, &self.display_pipeline)
7704 {
7705 let swap_view = target_view.clone();
7706 let mut display_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
7707 label: Some("display transform"),
7708 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
7709 view: &swap_view,
7710 resolve_target: None,
7711 ops: wgpu::Operations {
7712 load: wgpu::LoadOp::Load,
7713 store: wgpu::StoreOp::Store,
7714 },
7715 depth_slice: None,
7716 })],
7717 depth_stencil_attachment: None,
7718 timestamp_writes: None,
7719 occlusion_query_set: None,
7720 multiview_mask: None,
7721 });
7722 display_pass.set_pipeline(display_pipeline);
7723 display_pass.set_bind_group(1, ws_bind, &[]);
7724 display_pass.draw(0..3, 0..1);
7725 }
7726
7727 self.evict_unused_images();
7729 }
7730
7731 pub fn render_to_view(
7735 &mut self,
7736 scene: &Scene,
7737 encoder: &mut wgpu::CommandEncoder,
7738 target_view: &wgpu::TextureView,
7739 width: u32,
7740 height: u32,
7741 clear_color: Option<[f64; 4]>,
7742 ) {
7743 self.resize(width, height);
7744
7745 self.frame_index = self.frame_index.wrapping_add(1);
7746 self.slug_cache.next_frame();
7747
7748 if width == 0 || height == 0 {
7749 return;
7750 }
7751
7752 self.render_scene_to_encoder(scene, encoder, target_view, clear_color);
7753 }
7754}
7755
7756fn clamp_scissor(x: u32, y: u32, w: u32, h: u32, tw: u32, th: u32) -> (u32, u32, u32, u32) {
7757 let x = x.min(tw.saturating_sub(1));
7758 let y = y.min(th.saturating_sub(1));
7759 let w = w.min(tw.saturating_sub(x)).max(1);
7760 let h = h.min(th.saturating_sub(y)).max(1);
7761 (x, y, w, h)
7762}
7763
7764fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
7765 let x0 = a.x.max(b.x);
7766 let y0 = a.y.max(b.y);
7767 let x1 = (a.x + a.w).min(b.x + b.w);
7768 let y1 = (a.y + a.h).min(b.y + b.h);
7769 repose_core::Rect {
7770 x: x0,
7771 y: y0,
7772 w: (x1 - x0).max(0.0),
7773 h: (y1 - y0).max(0.0),
7774 }
7775}