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