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