1use std::any::TypeId;
2use std::borrow::Cow;
3use std::collections::{HashMap, HashSet};
4use std::hash::Hash;
5use std::num::NonZeroU64;
6#[cfg(feature = "winit-surface")]
7use std::panic::{AssertUnwindSafe, catch_unwind};
8use std::sync::{Arc, Mutex, Weak};
9
10use repose_core::color::{ChromaSiting, ColorInfo, PixelFormat};
11use repose_core::{
12 Brush, FontStyle, ImageAlignment, ImageFilter, ImageFit, ImagePaintStyle, ImageSourceRect,
13 Scene, SceneNode, StrokeCap, TextPaintStyle, Transform, Vec2,
14};
15#[cfg(feature = "winit-surface")]
16use repose_core::{GlyphRasterConfig, PresentModePref, RenderBackend, request_frame};
17#[cfg(feature = "winit-surface")]
18use wgpu::Instance;
19
20mod slug;
21
22fn align_up(value: u64, alignment: u64) -> anyhow::Result<u64> {
23 if !alignment.is_power_of_two() {
24 anyhow::bail!("alignment must be a power of two");
25 }
26 value
27 .checked_add(alignment - 1)
28 .map(|v| v & !(alignment - 1))
29 .ok_or_else(|| anyhow::anyhow!("alignment overflow"))
30}
31
32mod commands;
33pub use commands::RenderCommandFailure;
34pub use commands::RenderCommandReport;
35pub use commands::apply_render_commands;
36
37pub mod offscreen;
38
39mod callback;
40pub use callback::{
41 Callback, CallbackRenderPass, CallbackResources, ScreenDescriptor, WgpuCallback,
42};
43
44mod depth_composite;
45pub use depth_composite::{BlitRenderPass, DepthComposite};
46
47#[derive(Clone)]
48struct UploadRing {
49 buf: wgpu::Buffer,
50 cap: u64,
51 head: u64,
52 usage: wgpu::BufferUsages,
53}
54
55impl UploadRing {
56 fn new(device: &wgpu::Device, label: &str, cap: u64, usage: wgpu::BufferUsages) -> Self {
57 let max = device.limits().max_buffer_size;
58 let cap = cap.max(4).min(max).min(MAX_UPLOAD_RING_BYTES).max(4);
59 let buf = device.create_buffer(&wgpu::BufferDescriptor {
60 label: Some(label),
61 size: cap,
62 usage,
63 mapped_at_creation: false,
64 });
65 Self {
66 buf,
67 cap,
68 head: 0,
69 usage,
70 }
71 }
72
73 fn reset(&mut self) {
74 self.head = 0;
75 }
76
77 fn grow_to_fit(
78 &mut self,
79 device: &wgpu::Device,
80 encoder: &mut wgpu::CommandEncoder,
81 needed: u64,
82 ) -> anyhow::Result<()> {
83 if needed == 0 {
84 return Ok(());
85 }
86 let start = align_up(self.head, 4)?;
87 let end = start
88 .checked_add(needed)
89 .ok_or_else(|| anyhow::anyhow!("upload ring offset overflow"))?;
90 if end <= self.cap {
91 return Ok(());
92 }
93 let required = align_up(end, 4)?;
94 let doubled = self.cap.checked_mul(2).unwrap_or(0);
95 let new_cap = required.max(doubled).max(256);
96 let new_cap = align_up(new_cap, 4)?;
97 if new_cap > device.limits().max_buffer_size || new_cap > MAX_UPLOAD_RING_BYTES {
98 anyhow::bail!("upload ring exceeds resource budget");
99 }
100 if !self.usage.contains(wgpu::BufferUsages::COPY_SRC) {
101 anyhow::bail!("upload ring growth requires COPY_SRC");
102 }
103 let old = self.buf.clone();
104 self.buf = device.create_buffer(&wgpu::BufferDescriptor {
105 label: Some("upload ring (grown)"),
106 size: new_cap,
107 usage: self.usage,
108 mapped_at_creation: false,
109 });
110 self.cap = new_cap;
111 if self.head > 0 {
112 let copy_size = align_up(self.head, 4)?;
113 if copy_size > old.size() {
114 anyhow::bail!("upload ring old data exceeds old buffer");
115 }
116 encoder.copy_buffer_to_buffer(&old, 0, &self.buf, 0, copy_size);
117 }
118 Ok(())
119 }
120
121 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> anyhow::Result<u64> {
122 let len = u64::try_from(bytes.len())
123 .map_err(|_| anyhow::anyhow!("upload byte length exceeds u64"))?;
124 let start = align_up(self.head, 4)?;
125 let end = start
126 .checked_add(len)
127 .ok_or_else(|| anyhow::anyhow!("upload ring offset overflow"))?;
128 if end > self.cap {
129 anyhow::bail!(
130 "upload ring overflow: start={start} len={len} cap={}",
131 self.cap
132 );
133 }
134 queue.write_buffer(&self.buf, start, bytes);
135 self.head = end;
136 Ok(start)
137 }
138}
139
140struct InstancedPipe<I: bytemuck::Pod> {
141 ring: UploadRing,
142 _marker: std::marker::PhantomData<I>,
143}
144
145impl<I: bytemuck::Pod> InstancedPipe<I> {
146 fn new(ring: UploadRing) -> Self {
147 Self {
148 ring,
149 _marker: std::marker::PhantomData,
150 }
151 }
152
153 fn upload(
154 &mut self,
155 device: &wgpu::Device,
156 queue: &wgpu::Queue,
157 encoder: &mut wgpu::CommandEncoder,
158 data: &[I],
159 ) -> Option<(u64, u32)> {
160 if data.is_empty() {
161 return None;
162 }
163 let count = u32::try_from(data.len()).ok()?;
164 let bytes = bytemuck::cast_slice(data);
165 let byte_len = u64::try_from(bytes.len()).ok()?;
166 if byte_len == 0 || byte_len > device.limits().max_buffer_size {
167 return None;
168 }
169 self.ring
170 .grow_to_fit(device, encoder, byte_len)
171 .map_err(|error| log::error!("{error:#}"))
172 .ok()?;
173 let off = self
174 .ring
175 .alloc_write(queue, bytes)
176 .map_err(|error| log::error!("{error:#}"))
177 .ok()?;
178 Some((off, count))
179 }
180
181 fn reset(&mut self) {
182 self.ring.reset();
183 }
184}
185
186#[repr(C)]
187#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
188struct Globals {
189 ndc_to_px: [f32; 2],
190 _pad: [f32; 2],
191}
192
193fn make_globals(target_w: f32, target_h: f32) -> Globals {
194 Globals {
195 ndc_to_px: [target_w * 0.5, target_h * 0.5],
196 _pad: [0.0, 0.0],
197 }
198}
199
200pub struct WgpuSceneRenderer {
201 pub device: wgpu::Device,
202 pub queue: wgpu::Queue,
203 pub output_format: wgpu::TextureFormat,
204 surface_format: wgpu::TextureFormat,
206 pub output_width: u32,
207 pub output_height: u32,
208 pub pixels_per_point: f32,
210
211 surface_pipes: Pipelines,
214 working_space_pipes: Pipelines,
215 layer_pipes: Pipelines,
216 working_space_layer_pipes: Pipelines,
217
218 rects: InstancedPipe<RectInstance>,
220 borders: InstancedPipe<BorderInstance>,
221 ellipses: InstancedPipe<EllipseInstance>,
222 ellipse_borders: InstancedPipe<EllipseBorderInstance>,
223 arcs: InstancedPipe<ArcInstance>,
224 glyph_mask: InstancedPipe<GlyphInstance>,
225 glyph_color: InstancedPipe<GlyphInstance>,
226
227 image_bind_layout_rgba: wgpu::BindGroupLayout,
229 image_bind_layout_nv12: wgpu::BindGroupLayout,
230 image_sampler: wgpu::Sampler,
231 image_sampler_nearest: wgpu::Sampler,
232 image_sampler_repeat: wgpu::Sampler,
233 image_sampler_repeat_nearest: wgpu::Sampler,
234 layer_sampler: wgpu::Sampler,
235 layer_sampler_linear: wgpu::Sampler,
236
237 blur_ring: UploadRing,
239
240 text_bind_layout: wgpu::BindGroupLayout,
241
242 clip_ring: UploadRing,
244
245 projective_ring: UploadRing,
248
249 blend_ring: UploadRing,
251
252 slug_enabled: bool,
254 slug_ring: UploadRing,
255 slug_cache: slug::GlyphSlugCache,
256 slug_draw_cache: HashMap<SlugDrawKey, SlugDrawCacheEntry>,
257 slug_draw_cache_bytes_total: u64,
258
259 nv12: InstancedPipe<Nv12Instance>,
261
262 mesh_cache: HashMap<MeshCacheKey, MeshCacheEntry>,
263 mesh_cache_bytes_total: u64,
264 mesh_uniform_buf: wgpu::Buffer,
265 mesh_bind: wgpu::BindGroup,
266 mesh_uniform_head: u64,
267 mesh_uniform_alignment: u64,
268 mesh_uniform_cap: u64,
269
270 flatten_layer_ids: Vec<u32>,
274
275 blur_scratch_ids: HashMap<(u32, u32), u32>,
280 blur_scratch_next: u32,
281
282 blend_snapshots: std::collections::HashMap<u32, BlendSnapshot>,
286 blend_snapshot_pool: HashMap<BlendSnapshotKey, BlendSnapshot>,
287 blend_copies: Vec<BlendCopy>,
291
292 msaa_samples: u32,
293 working_space_msaa_samples: u32,
294
295 depth_stencil_tex: wgpu::Texture,
297 depth_stencil_view: wgpu::TextureView,
298
299 msaa_tex: Option<wgpu::Texture>,
301 msaa_view: Option<wgpu::TextureView>,
302 ws_msaa_tex: Option<wgpu::Texture>,
303 ws_msaa_view: Option<wgpu::TextureView>,
304 surface_resolve_tex: Option<wgpu::Texture>,
305 surface_resolve_view: Option<wgpu::TextureView>,
306 surface_resolve_bytes: u64,
307 msaa_bytes: u64,
308 ws_msaa_bytes: u64,
309 depth_stencil_bytes: u64,
310 working_space_bytes: u64,
311 gpu_budget_bytes: u64,
312
313 globals_buf: wgpu::Buffer,
314 globals_bind: wgpu::BindGroup,
315 globals_staging: wgpu::Buffer,
316 globals_staging_cap: u64,
317
318 atlas_mask: AtlasA8,
320 atlas_color: AtlasRGBA,
321 atlas_mask_bind: wgpu::BindGroup,
322 atlas_color_bind: wgpu::BindGroup,
323 atlas_generation: u64,
324 atlas_mask_pending: AtlasPending,
325 atlas_color_pending: AtlasPending,
326 atlas_last_compact_frame: u64,
327 atlas_mask_failures: HashSet<repose_text::CacheKey>,
328 atlas_color_failures: HashSet<repose_text::CacheKey>,
329 atlas_mask_deferred: HashSet<repose_text::CacheKey>,
332 atlas_color_deferred: HashSet<repose_text::CacheKey>,
333
334 glyph_outline_cache: HashMap<repose_text::CacheKey, GlyphOutlineEntry>,
335 legacy_shaped_text_cache: HashMap<TextShapeKey, TextShapeCacheEntry>,
336
337 next_image_handle: u64,
339 images: HashMap<u64, ImageTex>,
340 retained: HashMap<u64, RetainedImage>,
341 retained_bytes_total: u64,
342
343 next_coverage_handle: u64,
346 coverages: HashMap<u64, CoverageTex>,
347
348 frame_index: u64,
350 cache_touch_clock: u64,
351 image_bytes_total: u64,
352 image_evict_after_frames: u64,
353 image_budget_bytes: u64,
354
355 layer_pool: HashMap<u32, LayerTarget>,
358 producer_layer_ids: HashSet<u32>,
359 layer_bytes_total: u64,
360 transient_layer_bytes_total: u64,
361
362 working_space: bool,
366 ws_tex: Option<wgpu::Texture>,
367 ws_view: Option<wgpu::TextureView>,
368 ws_bind: Option<wgpu::BindGroup>,
369 display_pipeline: Option<wgpu::RenderPipeline>,
370 display_layout: Option<wgpu::BindGroupLayout>,
371
372 pub callback_resources: CallbackResources,
373 callback_scoped_resources: HashMap<CallbackScopeKey, CallbackResources>,
374 callback_scope_uses: HashMap<CallbackScopeKey, CallbackScopeUse>,
375 callback_scope_payloads: HashMap<CallbackScopeKey, Weak<Callback>>,
376 callback_scope_clock: u64,
377 blend_snapshot_bytes_total: u64,
378 frame_active: bool,
379 last_render_error: Option<String>,
380}
381
382pub struct WgpuSurfaceBackend {
383 #[cfg(feature = "winit-surface")]
384 instance: Option<wgpu::Instance>,
385 #[cfg(feature = "winit-surface")]
386 window: Option<Arc<winit::window::Window>>,
387 pub surface: Option<wgpu::Surface<'static>>,
388 pub surface_config: Option<wgpu::SurfaceConfiguration>,
389 #[cfg_attr(not(feature = "winit-surface"), allow(dead_code))]
390 pending_reconfigure: bool,
391 pub renderer: WgpuSceneRenderer,
392 device_lost: Arc<Mutex<Option<(wgpu::DeviceLostReason, String)>>>,
393}
394
395impl std::ops::Deref for WgpuSurfaceBackend {
396 type Target = WgpuSceneRenderer;
397 fn deref(&self) -> &Self::Target {
398 &self.renderer
399 }
400}
401impl std::ops::DerefMut for WgpuSurfaceBackend {
402 fn deref_mut(&mut self) -> &mut Self::Target {
403 &mut self.renderer
404 }
405}
406
407#[cfg(feature = "winit-surface")]
408pub type WgpuBackend = WgpuSurfaceBackend;
409
410pub fn clamp_output_size(width: u32, height: u32, max: u32) -> (u32, u32) {
420 if width == 0 || height == 0 {
421 return (0, 0);
422 }
423 let max = max.max(1);
424 if width <= max && height <= max {
425 return (width, height);
426 }
427 let scale = max as f64 / width.max(height) as f64;
428 let clamped = |v: u32| ((v as f64 * scale).round() as u32).clamp(1, max);
429 (clamped(width), clamped(height))
430}
431
432fn blur_sigma(radius: f32) -> f32 {
435 (radius.max(0.0) * 0.577_350_3 + 0.5).max(0.0)
436}
437
438fn blur_pad(radius: f32) -> f32 {
441 (blur_sigma(radius) * 3.0).ceil().max(0.0)
442}
443
444const BLUR_SCRATCH_ID_BASE: u32 = 0xE000_0000;
447
448const BLUR_SLOT_CONTENT: u32 = 0;
450const BLUR_SLOT_SHADOW: u32 = 1;
454
455impl Drop for WgpuSceneRenderer {
456 fn drop(&mut self) {
457 let _ = self.device.poll(wgpu::PollType::Poll);
458 #[cfg(not(target_arch = "wasm32"))]
459 {
460 let _ = self.device.poll(wgpu::PollType::Wait {
461 submission_index: None,
462 timeout: Some(web_time::Duration::from_millis(100)),
463 });
464 }
465 }
466}
467
468#[derive(Clone)]
469struct LayerTarget {
470 texture: wgpu::Texture,
471 view: wgpu::TextureView,
472 bind: wgpu::BindGroup,
473 bind_linear: wgpu::BindGroup,
474 depth_stencil_view: wgpu::TextureView,
475 width: u32,
476 height: u32,
477 format: wgpu::TextureFormat,
478 bytes: u64,
479 rect_px: (f32, f32, f32, f32),
480 transient: bool,
481}
482
483#[derive(Clone, Copy, PartialEq, Eq, Hash)]
487struct BlendSnapshotKey {
488 format: wgpu::TextureFormat,
489 width: u32,
490 height: u32,
491}
492
493#[derive(Clone)]
494struct BlendSnapshot {
495 key: BlendSnapshotKey,
496 texture: wgpu::Texture,
497 bind: wgpu::BindGroup,
498 bytes: u64,
499}
500
501struct BlendCopy {
502 pass_id: u64,
503 blend_id: u32,
504 target: PassTarget,
505 region: repose_core::Rect,
506}
507
508#[derive(Clone)]
509enum ActiveClip {
510 Rect {
511 off: u64,
512 cnt: u32,
513 rect: repose_core::Rect,
514 radii: [f32; 4],
515 scissor: (u32, u32, u32, u32),
520 difference: bool,
521 applied: bool,
522 blocked: bool,
523 },
524 Vector {
525 mesh: Option<MeshGpu>,
526 source: Arc<repose_core::VectorMeshData>,
527 uoff: u64,
528 affine: [f32; 6],
529 scissor: (u32, u32, u32, u32),
530 difference: bool,
531 applied: bool,
532 blocked: bool,
533 },
534}
535
536impl ActiveClip {
537 fn difference(&self) -> bool {
538 match self {
539 Self::Rect { difference, .. } | Self::Vector { difference, .. } => *difference,
540 }
541 }
542
543 fn blocked(&self) -> bool {
544 match self {
545 Self::Rect { blocked, .. } | Self::Vector { blocked, .. } => *blocked,
546 }
547 }
548}
549
550#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
552enum PassTarget {
553 Surface,
554 Layer(u32),
555}
556
557#[derive(Clone, Debug, PartialEq, Eq, Hash)]
558struct CallbackIdentity {
559 type_id: TypeId,
560 key: Option<Arc<str>>,
563 reusable: bool,
564}
565
566#[derive(Clone, Debug, PartialEq, Eq, Hash)]
578struct CallbackScopeKey {
579 callback: CallbackIdentity,
580 target: PassTarget,
581 target_format: wgpu::TextureFormat,
582 sample_count: u32,
583}
584
585#[derive(Clone, Copy)]
586struct CallbackScopeUse {
587 tick: u64,
588 frame: u64,
589}
590
591fn callback_identity(callback: &Callback, fallback: usize) -> CallbackIdentity {
592 match callback.0.resource_key() {
593 Some(key) => CallbackIdentity {
594 type_id: callback.0.resource_type_id(),
595 key: Some(Arc::from(key)),
596 reusable: true,
597 },
598 None => CallbackIdentity {
599 type_id: TypeId::of::<Callback>(),
600 key: Some(Arc::from(fallback.to_string())),
601 reusable: false,
602 },
603 }
604}
605
606fn callback_scope_key(
607 callback: &Callback,
608 fallback: usize,
609 target: PassTarget,
610 descriptor: &ScreenDescriptor,
611) -> CallbackScopeKey {
612 CallbackScopeKey {
613 callback: callback_identity(callback, fallback),
614 target,
615 target_format: descriptor.target_format,
616 sample_count: descriptor.sample_count,
617 }
618}
619
620#[derive(Clone)]
625struct Pipelines {
626 rects: wgpu::RenderPipeline,
627 borders: wgpu::RenderPipeline,
628 ellipses: wgpu::RenderPipeline,
629 ellipse_borders: wgpu::RenderPipeline,
630 arcs: wgpu::RenderPipeline,
631 text_mask: wgpu::RenderPipeline,
632 text_color: wgpu::RenderPipeline,
633 image_rgba: wgpu::RenderPipeline,
634 coverage: wgpu::RenderPipeline,
638 image_nv12: wgpu::RenderPipeline,
639 blur: wgpu::RenderPipeline,
640 blur_content: wgpu::RenderPipeline,
641 clip_bin: wgpu::RenderPipeline,
642 clip_dec: wgpu::RenderPipeline,
643 slug: Option<wgpu::RenderPipeline>,
644 mesh: wgpu::RenderPipeline,
648 mesh_overlay: wgpu::RenderPipeline,
651 mesh_add: wgpu::RenderPipeline,
656 mesh_multiply: wgpu::RenderPipeline,
657 mesh_screen: wgpu::RenderPipeline,
658 mesh_darken: wgpu::RenderPipeline,
659 mesh_lighten: wgpu::RenderPipeline,
660 blend_layer: wgpu::RenderPipeline,
664 mesh_clip_inc: wgpu::RenderPipeline,
666 mesh_clip_dec: wgpu::RenderPipeline,
668 projective_layer: wgpu::RenderPipeline,
672}
673
674fn vattrs(spec: &[(u32, u64, wgpu::VertexFormat)]) -> Vec<wgpu::VertexAttribute> {
678 spec.iter()
679 .map(|&(shader_location, offset, format)| wgpu::VertexAttribute {
680 shader_location,
681 offset,
682 format,
683 })
684 .collect()
685}
686
687macro_rules! field_offset {
690 ($ty:ty, $field:ident) => {
691 std::mem::offset_of!($ty, $field) as u64
692 };
693}
694
695impl Pipelines {
696 fn create(
697 device: &wgpu::Device,
698 format: wgpu::TextureFormat,
699 sample_count: u32,
700 globals_layout: &wgpu::BindGroupLayout,
701 text_bind_layout: &wgpu::BindGroupLayout,
702 image_bind_layout_nv12: &wgpu::BindGroupLayout,
703 clip_pipeline_layout: &wgpu::PipelineLayout,
704 stencil_for_content: &wgpu::DepthStencilState,
705 stencil_for_clip_inc: &wgpu::DepthStencilState,
706 stencil_for_clip_dec: &wgpu::DepthStencilState,
707 clip_vertex_layout: &wgpu::VertexBufferLayout,
708 mesh_bind_layout: &wgpu::BindGroupLayout,
709 ) -> Self {
710 let msaa_state = wgpu::MultisampleState {
711 count: sample_count,
712 mask: !0,
713 alpha_to_coverage_enabled: false,
714 };
715
716 macro_rules! make_content_pipeline {
717 ($name:ident, $shader:literal, $inst_type:ty, $attrs:expr) => {
718 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
719 label: Some(concat!($shader, ".wgsl")),
720 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(concat!(
721 "shaders/", $shader, ".wgsl"
722 )))),
723 });
724 let pipeline_layout =
725 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
726 label: Some(concat!($shader, " pipeline layout")),
727 bind_group_layouts: &[Some(globals_layout)],
728 immediate_size: 0,
729 });
730 let $name = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
731 label: Some(concat!($shader, " pipeline")),
732 layout: Some(&pipeline_layout),
733 vertex: wgpu::VertexState {
734 module: &shader_module,
735 entry_point: Some("vs_main"),
736 buffers: &[Some(wgpu::VertexBufferLayout {
737 array_stride: std::mem::size_of::<$inst_type>() as u64,
738 step_mode: wgpu::VertexStepMode::Instance,
739 attributes: $attrs,
740 })],
741 compilation_options: wgpu::PipelineCompilationOptions::default(),
742 },
743 fragment: Some(wgpu::FragmentState {
744 module: &shader_module,
745 entry_point: Some("fs_main"),
746 targets: &[Some(wgpu::ColorTargetState {
747 format,
748 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
749 write_mask: wgpu::ColorWrites::ALL,
750 })],
751 compilation_options: wgpu::PipelineCompilationOptions::default(),
752 }),
753 primitive: wgpu::PrimitiveState::default(),
754 depth_stencil: Some(stencil_for_content.clone()),
755 multisample: msaa_state,
756 multiview_mask: None,
757 cache: None,
758 });
759 };
760 }
761
762 let rect_attrs = vattrs(&[
763 (
764 0,
765 field_offset!(RectInstance, xywh),
766 wgpu::VertexFormat::Float32x4,
767 ),
768 (
769 1,
770 field_offset!(RectInstance, radii),
771 wgpu::VertexFormat::Float32x4,
772 ),
773 (
774 2,
775 field_offset!(RectInstance, color0),
776 wgpu::VertexFormat::Float32x4,
777 ),
778 (
779 3,
780 field_offset!(RectInstance, color1),
781 wgpu::VertexFormat::Float32x4,
782 ),
783 (
784 4,
785 field_offset!(RectInstance, fwd_mat),
786 wgpu::VertexFormat::Float32x4,
787 ),
788 (
789 5,
790 field_offset!(RectInstance, grad),
791 wgpu::VertexFormat::Float32x4,
792 ),
793 (
794 6,
795 field_offset!(RectInstance, bits),
796 wgpu::VertexFormat::Uint32,
797 ),
798 ]);
799 let border_attrs = vattrs(&[
800 (
801 0,
802 field_offset!(BorderInstance, xywh),
803 wgpu::VertexFormat::Float32x4,
804 ),
805 (
806 1,
807 field_offset!(BorderInstance, radii),
808 wgpu::VertexFormat::Float32x4,
809 ),
810 (
811 2,
812 field_offset!(BorderInstance, color0),
813 wgpu::VertexFormat::Float32x4,
814 ),
815 (
816 3,
817 field_offset!(BorderInstance, color1),
818 wgpu::VertexFormat::Float32x4,
819 ),
820 (
821 4,
822 field_offset!(BorderInstance, fwd_mat),
823 wgpu::VertexFormat::Float32x4,
824 ),
825 (
826 5,
827 field_offset!(BorderInstance, grad),
828 wgpu::VertexFormat::Float32x4,
829 ),
830 (
831 6,
832 field_offset!(BorderInstance, bits),
833 wgpu::VertexFormat::Uint32,
834 ),
835 (
836 7,
837 field_offset!(BorderInstance, stroke),
838 wgpu::VertexFormat::Float32,
839 ),
840 ]);
841 let ellipse_attrs = vattrs(&[
842 (
843 0,
844 field_offset!(EllipseInstance, xywh),
845 wgpu::VertexFormat::Float32x4,
846 ),
847 (
848 1,
849 field_offset!(EllipseInstance, color0),
850 wgpu::VertexFormat::Float32x4,
851 ),
852 (
853 2,
854 field_offset!(EllipseInstance, color1),
855 wgpu::VertexFormat::Float32x4,
856 ),
857 (
858 3,
859 field_offset!(EllipseInstance, fwd_mat),
860 wgpu::VertexFormat::Float32x4,
861 ),
862 (
863 4,
864 field_offset!(EllipseInstance, grad),
865 wgpu::VertexFormat::Float32x4,
866 ),
867 (
868 5,
869 field_offset!(EllipseInstance, bits),
870 wgpu::VertexFormat::Uint32,
871 ),
872 ]);
873 let ellipse_border_attrs = vattrs(&[
874 (
875 0,
876 field_offset!(EllipseBorderInstance, xywh),
877 wgpu::VertexFormat::Float32x4,
878 ),
879 (
880 1,
881 field_offset!(EllipseBorderInstance, color0),
882 wgpu::VertexFormat::Float32x4,
883 ),
884 (
885 2,
886 field_offset!(EllipseBorderInstance, color1),
887 wgpu::VertexFormat::Float32x4,
888 ),
889 (
890 3,
891 field_offset!(EllipseBorderInstance, fwd_mat),
892 wgpu::VertexFormat::Float32x4,
893 ),
894 (
895 4,
896 field_offset!(EllipseBorderInstance, grad),
897 wgpu::VertexFormat::Float32x4,
898 ),
899 (
900 5,
901 field_offset!(EllipseBorderInstance, bits),
902 wgpu::VertexFormat::Uint32,
903 ),
904 (
905 6,
906 field_offset!(EllipseBorderInstance, stroke),
907 wgpu::VertexFormat::Float32,
908 ),
909 (
910 7,
911 field_offset!(EllipseBorderInstance, pad),
912 wgpu::VertexFormat::Float32,
913 ),
914 ]);
915
916 make_content_pipeline!(rects, "rect", RectInstance, &rect_attrs);
917 make_content_pipeline!(borders, "border", BorderInstance, &border_attrs);
918 make_content_pipeline!(ellipses, "ellipse", EllipseInstance, &ellipse_attrs);
919 make_content_pipeline!(
920 ellipse_borders,
921 "ellipse_border",
922 EllipseBorderInstance,
923 &ellipse_border_attrs
924 );
925
926 let arc_attrs = vattrs(&[
927 (
928 0,
929 field_offset!(ArcInstance, xywh),
930 wgpu::VertexFormat::Float32x4,
931 ),
932 (
933 1,
934 field_offset!(ArcInstance, color0),
935 wgpu::VertexFormat::Float32x4,
936 ),
937 (
938 2,
939 field_offset!(ArcInstance, color1),
940 wgpu::VertexFormat::Float32x4,
941 ),
942 (
943 3,
944 field_offset!(ArcInstance, fwd_mat),
945 wgpu::VertexFormat::Float32x4,
946 ),
947 (
948 4,
949 field_offset!(ArcInstance, grad),
950 wgpu::VertexFormat::Float32x4,
951 ),
952 (
953 5,
954 field_offset!(ArcInstance, bits),
955 wgpu::VertexFormat::Uint32,
956 ),
957 (
958 6,
959 field_offset!(ArcInstance, start_angle),
960 wgpu::VertexFormat::Float32,
961 ),
962 (
963 7,
964 field_offset!(ArcInstance, sweep_angle),
965 wgpu::VertexFormat::Float32,
966 ),
967 (
968 8,
969 field_offset!(ArcInstance, stroke),
970 wgpu::VertexFormat::Float32,
971 ),
972 (
973 9,
974 field_offset!(ArcInstance, pad),
975 wgpu::VertexFormat::Float32,
976 ),
977 ]);
978
979 make_content_pipeline!(arcs, "arc", ArcInstance, &arc_attrs);
980
981 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
983 label: Some("text.wgsl"),
984 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
985 });
986 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
988 label: Some("text_color.wgsl"),
989 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
990 "shaders/text_color.wgsl"
991 ))),
992 });
993 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
994 label: Some("text pipeline layout"),
995 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
996 immediate_size: 0,
997 });
998 let glyph_vertex = wgpu::VertexBufferLayout {
999 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
1000 step_mode: wgpu::VertexStepMode::Instance,
1001 attributes: &[
1002 wgpu::VertexAttribute {
1003 shader_location: 0,
1004 offset: 0,
1005 format: wgpu::VertexFormat::Float32x4,
1006 },
1007 wgpu::VertexAttribute {
1008 shader_location: 1,
1009 offset: 16,
1010 format: wgpu::VertexFormat::Float32x4,
1011 },
1012 wgpu::VertexAttribute {
1013 shader_location: 2,
1014 offset: 32,
1015 format: wgpu::VertexFormat::Float32x4,
1016 },
1017 wgpu::VertexAttribute {
1018 shader_location: 3,
1019 offset: 48,
1020 format: wgpu::VertexFormat::Float32x4,
1021 },
1022 ],
1023 };
1024 let text_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1025 label: Some("text pipeline (mask)"),
1026 layout: Some(&text_pipeline_layout),
1027 vertex: wgpu::VertexState {
1028 module: &text_mask_shader,
1029 entry_point: Some("vs_main"),
1030 buffers: &[Some(glyph_vertex.clone())],
1031 compilation_options: wgpu::PipelineCompilationOptions::default(),
1032 },
1033 fragment: Some(wgpu::FragmentState {
1034 module: &text_mask_shader,
1035 entry_point: Some("fs_main"),
1036 targets: &[Some(wgpu::ColorTargetState {
1037 format,
1038 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1039 write_mask: wgpu::ColorWrites::ALL,
1040 })],
1041 compilation_options: wgpu::PipelineCompilationOptions::default(),
1042 }),
1043 primitive: wgpu::PrimitiveState::default(),
1044 depth_stencil: Some(stencil_for_content.clone()),
1045 multisample: msaa_state,
1046 multiview_mask: None,
1047 cache: None,
1048 });
1049 let text_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1050 label: Some("text pipeline (color)"),
1051 layout: Some(&text_pipeline_layout),
1052 vertex: wgpu::VertexState {
1053 module: &text_color_shader,
1054 entry_point: Some("vs_main"),
1055 buffers: &[Some(glyph_vertex.clone())],
1056 compilation_options: wgpu::PipelineCompilationOptions::default(),
1057 },
1058 fragment: Some(wgpu::FragmentState {
1059 module: &text_color_shader,
1060 entry_point: Some("fs_main"),
1061 targets: &[Some(wgpu::ColorTargetState {
1062 format,
1063 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1064 write_mask: wgpu::ColorWrites::ALL,
1065 })],
1066 compilation_options: wgpu::PipelineCompilationOptions::default(),
1067 }),
1068 primitive: wgpu::PrimitiveState::default(),
1069 depth_stencil: Some(stencil_for_content.clone()),
1070 multisample: msaa_state,
1071 multiview_mask: None,
1072 cache: None,
1073 });
1074 let image_rgba = text_color.clone();
1076
1077 let coverage_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1081 label: Some("coverage.wgsl"),
1082 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/coverage.wgsl"))),
1083 });
1084 let coverage = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1085 label: Some("coverage pipeline (tinted a8)"),
1086 layout: Some(&text_pipeline_layout),
1087 vertex: wgpu::VertexState {
1088 module: &coverage_shader,
1089 entry_point: Some("vs_main"),
1090 buffers: &[Some(glyph_vertex.clone())],
1091 compilation_options: wgpu::PipelineCompilationOptions::default(),
1092 },
1093 fragment: Some(wgpu::FragmentState {
1094 module: &coverage_shader,
1095 entry_point: Some("fs_main"),
1096 targets: &[Some(wgpu::ColorTargetState {
1097 format,
1098 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1099 write_mask: wgpu::ColorWrites::ALL,
1100 })],
1101 compilation_options: wgpu::PipelineCompilationOptions::default(),
1102 }),
1103 primitive: wgpu::PrimitiveState::default(),
1104 depth_stencil: Some(stencil_for_content.clone()),
1105 multisample: msaa_state,
1106 multiview_mask: None,
1107 cache: None,
1108 });
1109
1110 let blur_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1111 label: Some("blur_separable.wgsl"),
1112 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1113 "shaders/blur_separable.wgsl"
1114 ))),
1115 });
1116 let blur_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1117 label: Some("blur pipeline layout"),
1118 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
1119 immediate_size: 0,
1120 });
1121 let blur_layout = wgpu::VertexBufferLayout {
1122 array_stride: std::mem::size_of::<BlurInstance>() as u64,
1123 step_mode: wgpu::VertexStepMode::Instance,
1124 attributes: &[
1125 wgpu::VertexAttribute {
1126 shader_location: 0,
1127 offset: 0,
1128 format: wgpu::VertexFormat::Float32x4,
1129 },
1130 wgpu::VertexAttribute {
1131 shader_location: 1,
1132 offset: 16,
1133 format: wgpu::VertexFormat::Float32x4,
1134 },
1135 wgpu::VertexAttribute {
1136 shader_location: 2,
1137 offset: 32,
1138 format: wgpu::VertexFormat::Float32x4,
1139 },
1140 wgpu::VertexAttribute {
1141 shader_location: 3,
1142 offset: 48,
1143 format: wgpu::VertexFormat::Float32x2,
1144 },
1145 wgpu::VertexAttribute {
1146 shader_location: 4,
1147 offset: 56,
1148 format: wgpu::VertexFormat::Float32x4,
1149 },
1150 wgpu::VertexAttribute {
1151 shader_location: 5,
1152 offset: 72,
1153 format: wgpu::VertexFormat::Uint32,
1154 },
1155 wgpu::VertexAttribute {
1156 shader_location: 6,
1157 offset: 76,
1158 format: wgpu::VertexFormat::Uint32,
1159 },
1160 wgpu::VertexAttribute {
1161 shader_location: 7,
1162 offset: 80,
1163 format: wgpu::VertexFormat::Float32x4,
1164 },
1165 ],
1166 };
1167 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1168 label: Some("blur pipeline"),
1169 layout: Some(&blur_pipeline_layout),
1170 vertex: wgpu::VertexState {
1171 module: &blur_shader,
1172 entry_point: Some("vs_main"),
1173 buffers: &[Some(blur_layout.clone())],
1174 compilation_options: wgpu::PipelineCompilationOptions::default(),
1175 },
1176 fragment: Some(wgpu::FragmentState {
1177 module: &blur_shader,
1178 entry_point: Some("fs_alpha"),
1179 targets: &[Some(wgpu::ColorTargetState {
1180 format,
1181 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1182 write_mask: wgpu::ColorWrites::ALL,
1183 })],
1184 compilation_options: wgpu::PipelineCompilationOptions::default(),
1185 }),
1186 primitive: wgpu::PrimitiveState::default(),
1187 depth_stencil: Some(stencil_for_content.clone()),
1188 multisample: msaa_state,
1189 multiview_mask: None,
1190 cache: None,
1191 });
1192
1193 let blur_content = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1194 label: Some("blur content pipeline"),
1195 layout: Some(&blur_pipeline_layout),
1196 vertex: wgpu::VertexState {
1197 module: &blur_shader,
1198 entry_point: Some("vs_main"),
1199 buffers: &[Some(blur_layout)],
1200 compilation_options: wgpu::PipelineCompilationOptions::default(),
1201 },
1202 fragment: Some(wgpu::FragmentState {
1203 module: &blur_shader,
1204 entry_point: Some("fs_color"),
1205 targets: &[Some(wgpu::ColorTargetState {
1206 format,
1207 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1208 write_mask: wgpu::ColorWrites::ALL,
1209 })],
1210 compilation_options: wgpu::PipelineCompilationOptions::default(),
1211 }),
1212 primitive: wgpu::PrimitiveState::default(),
1213 depth_stencil: Some(stencil_for_content.clone()),
1214 multisample: msaa_state,
1215 multiview_mask: None,
1216 cache: None,
1217 });
1218
1219 let image_nv12_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1221 label: Some("image_nv12.wgsl"),
1222 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1223 "shaders/image_nv12.wgsl"
1224 ))),
1225 });
1226 let image_nv12_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1227 label: Some("image nv12 pipeline layout"),
1228 bind_group_layouts: &[Some(globals_layout), Some(image_bind_layout_nv12)],
1229 immediate_size: 0,
1230 });
1231 let image_nv12 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1232 label: Some("image nv12 pipeline"),
1233 layout: Some(&image_nv12_layout),
1234 vertex: wgpu::VertexState {
1235 module: &image_nv12_shader,
1236 entry_point: Some("vs_main"),
1237 buffers: &[Some(wgpu::VertexBufferLayout {
1238 array_stride: std::mem::size_of::<Nv12Instance>() as u64,
1239 step_mode: wgpu::VertexStepMode::Instance,
1240 attributes: &[
1241 wgpu::VertexAttribute {
1242 shader_location: 0,
1243 offset: 0,
1244 format: wgpu::VertexFormat::Float32x4,
1245 },
1246 wgpu::VertexAttribute {
1247 shader_location: 1,
1248 offset: 16,
1249 format: wgpu::VertexFormat::Float32x4,
1250 },
1251 wgpu::VertexAttribute {
1252 shader_location: 2,
1253 offset: 32,
1254 format: wgpu::VertexFormat::Float32x4,
1255 },
1256 wgpu::VertexAttribute {
1257 shader_location: 3,
1258 offset: 48,
1259 format: wgpu::VertexFormat::Float32,
1260 },
1261 wgpu::VertexAttribute {
1262 shader_location: 4,
1263 offset: 52,
1264 format: wgpu::VertexFormat::Float32,
1265 },
1266 wgpu::VertexAttribute {
1267 shader_location: 5,
1268 offset: 56,
1269 format: wgpu::VertexFormat::Float32x4,
1270 },
1271 ],
1272 })],
1273 compilation_options: wgpu::PipelineCompilationOptions::default(),
1274 },
1275 fragment: Some(wgpu::FragmentState {
1276 module: &image_nv12_shader,
1277 entry_point: Some("fs_main"),
1278 targets: &[Some(wgpu::ColorTargetState {
1279 format,
1280 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1281 write_mask: wgpu::ColorWrites::ALL,
1282 })],
1283 compilation_options: wgpu::PipelineCompilationOptions::default(),
1284 }),
1285 primitive: wgpu::PrimitiveState::default(),
1286 depth_stencil: Some(stencil_for_content.clone()),
1287 multisample: msaa_state,
1288 multiview_mask: None,
1289 cache: None,
1290 });
1291
1292 let clip_color_target = wgpu::ColorTargetState {
1293 format,
1294 blend: None,
1295 write_mask: wgpu::ColorWrites::empty(),
1296 };
1297
1298 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1300 label: Some("clip_round_rect_bin.wgsl"),
1301 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1302 "shaders/clip_round_rect_bin.wgsl"
1303 ))),
1304 });
1305 let clip_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1306 label: Some("clip pipeline (bin)"),
1307 layout: Some(clip_pipeline_layout),
1308 vertex: wgpu::VertexState {
1309 module: &clip_shader_bin,
1310 entry_point: Some("vs_main"),
1311 buffers: &[Some(clip_vertex_layout.clone())],
1312 compilation_options: wgpu::PipelineCompilationOptions::default(),
1313 },
1314 fragment: Some(wgpu::FragmentState {
1315 module: &clip_shader_bin,
1316 entry_point: Some("fs_main"),
1317 targets: &[Some(clip_color_target.clone())],
1318 compilation_options: wgpu::PipelineCompilationOptions::default(),
1319 }),
1320 primitive: wgpu::PrimitiveState::default(),
1321 depth_stencil: Some(stencil_for_clip_inc.clone()),
1322 multisample: wgpu::MultisampleState {
1323 count: sample_count,
1324 mask: !0,
1325 alpha_to_coverage_enabled: false,
1326 },
1327 multiview_mask: None,
1328 cache: None,
1329 });
1330 let clip_dec = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1331 label: Some("clip pipeline (dec)"),
1332 layout: Some(clip_pipeline_layout),
1333 vertex: wgpu::VertexState {
1334 module: &clip_shader_bin,
1335 entry_point: Some("vs_main"),
1336 buffers: &[Some(clip_vertex_layout.clone())],
1337 compilation_options: wgpu::PipelineCompilationOptions::default(),
1338 },
1339 fragment: Some(wgpu::FragmentState {
1340 module: &clip_shader_bin,
1341 entry_point: Some("fs_main"),
1342 targets: &[Some(clip_color_target.clone())],
1343 compilation_options: wgpu::PipelineCompilationOptions::default(),
1344 }),
1345 primitive: wgpu::PrimitiveState::default(),
1346 depth_stencil: Some(stencil_for_clip_dec.clone()),
1347 multisample: wgpu::MultisampleState {
1348 count: sample_count,
1349 mask: !0,
1350 alpha_to_coverage_enabled: false,
1351 },
1352 multiview_mask: None,
1353 cache: None,
1354 });
1355
1356 let slug = Some(slug::create_pipeline(
1357 device,
1358 format,
1359 sample_count,
1360 stencil_for_content,
1361 ));
1362
1363 let mesh_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1365 label: Some("mesh.wgsl"),
1366 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/mesh.wgsl"))),
1367 });
1368 let mesh_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1369 label: Some("mesh pipeline layout"),
1370 bind_group_layouts: &[Some(globals_layout), Some(mesh_bind_layout)],
1371 immediate_size: 0,
1372 });
1373 let mesh_vertex_layout = wgpu::VertexBufferLayout {
1374 array_stride: std::mem::size_of::<MeshVertex>() as u64,
1375 step_mode: wgpu::VertexStepMode::Vertex,
1376 attributes: &[
1377 wgpu::VertexAttribute {
1378 shader_location: 0,
1379 offset: 0,
1380 format: wgpu::VertexFormat::Float32x2,
1381 },
1382 wgpu::VertexAttribute {
1383 shader_location: 1,
1384 offset: 8,
1385 format: wgpu::VertexFormat::Float32x4,
1386 },
1387 wgpu::VertexAttribute {
1388 shader_location: 2,
1389 offset: 24,
1390 format: wgpu::VertexFormat::Float32x2,
1391 },
1392 ],
1393 };
1394 let make_mesh_pipeline =
1395 |label: &str, depth: &wgpu::DepthStencilState, color: &wgpu::ColorTargetState| {
1396 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1397 label: Some(label),
1398 layout: Some(&mesh_pipeline_layout),
1399 vertex: wgpu::VertexState {
1400 module: &mesh_shader,
1401 entry_point: Some("vs_main"),
1402 buffers: &[Some(mesh_vertex_layout.clone())],
1403 compilation_options: wgpu::PipelineCompilationOptions::default(),
1404 },
1405 fragment: Some(wgpu::FragmentState {
1406 module: &mesh_shader,
1407 entry_point: Some("fs_main"),
1408 targets: &[Some(color.clone())],
1409 compilation_options: wgpu::PipelineCompilationOptions::default(),
1410 }),
1411 primitive: wgpu::PrimitiveState {
1412 topology: wgpu::PrimitiveTopology::TriangleList,
1413 ..Default::default()
1414 },
1415 depth_stencil: Some(depth.clone()),
1416 multisample: msaa_state,
1417 multiview_mask: None,
1418 cache: None,
1419 })
1420 };
1421 let mesh_color_target = wgpu::ColorTargetState {
1422 format,
1423 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1424 write_mask: wgpu::ColorWrites::ALL,
1425 };
1426 let mut stencil_for_mesh = stencil_for_content.clone();
1427 stencil_for_mesh.stencil.front.compare = wgpu::CompareFunction::Equal;
1428 stencil_for_mesh.stencil.back.compare = wgpu::CompareFunction::Equal;
1429 let mut stencil_for_overlay = stencil_for_content.clone();
1430 stencil_for_overlay.stencil.front.compare = wgpu::CompareFunction::LessEqual;
1431 stencil_for_overlay.stencil.back.compare = wgpu::CompareFunction::LessEqual;
1432 let mesh = make_mesh_pipeline("mesh pipeline", &stencil_for_mesh, &mesh_color_target);
1433 let mesh_overlay = make_mesh_pipeline(
1434 "mesh overlay pipeline",
1435 &stencil_for_overlay,
1436 &mesh_color_target,
1437 );
1438 let mesh_clip_inc = make_mesh_pipeline(
1439 "mesh clip (inc) pipeline",
1440 stencil_for_clip_inc,
1441 &clip_color_target,
1442 );
1443 let mesh_clip_dec = make_mesh_pipeline(
1444 "mesh clip (dec) pipeline",
1445 stencil_for_clip_dec,
1446 &clip_color_target,
1447 );
1448 let mesh_blend_target = |blend: wgpu::BlendState| wgpu::ColorTargetState {
1449 format,
1450 blend: Some(blend),
1451 write_mask: wgpu::ColorWrites::ALL,
1452 };
1453 let premult_alpha = wgpu::BlendComponent::OVER;
1454 let mesh_add = make_mesh_pipeline(
1455 "mesh pipeline (add)",
1456 &stencil_for_mesh,
1457 &mesh_blend_target(wgpu::BlendState {
1458 color: wgpu::BlendComponent {
1459 src_factor: wgpu::BlendFactor::One,
1460 dst_factor: wgpu::BlendFactor::One,
1461 operation: wgpu::BlendOperation::Add,
1462 },
1463 alpha: wgpu::BlendComponent {
1464 src_factor: wgpu::BlendFactor::One,
1465 dst_factor: wgpu::BlendFactor::One,
1466 operation: wgpu::BlendOperation::Add,
1467 },
1468 }),
1469 );
1470 let mesh_multiply = make_mesh_pipeline(
1471 "mesh pipeline (multiply)",
1472 &stencil_for_mesh,
1473 &mesh_blend_target(wgpu::BlendState {
1474 color: wgpu::BlendComponent {
1475 src_factor: wgpu::BlendFactor::Dst,
1476 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
1477 operation: wgpu::BlendOperation::Add,
1478 },
1479 alpha: premult_alpha,
1480 }),
1481 );
1482 let mesh_screen = make_mesh_pipeline(
1483 "mesh pipeline (screen)",
1484 &stencil_for_mesh,
1485 &mesh_blend_target(wgpu::BlendState {
1486 color: wgpu::BlendComponent {
1487 src_factor: wgpu::BlendFactor::One,
1488 dst_factor: wgpu::BlendFactor::OneMinusSrc,
1489 operation: wgpu::BlendOperation::Add,
1490 },
1491 alpha: premult_alpha,
1492 }),
1493 );
1494 let mesh_darken = make_mesh_pipeline(
1495 "mesh pipeline (darken)",
1496 &stencil_for_mesh,
1497 &mesh_blend_target(wgpu::BlendState {
1498 color: wgpu::BlendComponent {
1499 src_factor: wgpu::BlendFactor::One,
1500 dst_factor: wgpu::BlendFactor::One,
1501 operation: wgpu::BlendOperation::Min,
1502 },
1503 alpha: premult_alpha,
1504 }),
1505 );
1506 let mesh_lighten = make_mesh_pipeline(
1507 "mesh pipeline (lighten)",
1508 &stencil_for_mesh,
1509 &mesh_blend_target(wgpu::BlendState {
1510 color: wgpu::BlendComponent {
1511 src_factor: wgpu::BlendFactor::One,
1512 dst_factor: wgpu::BlendFactor::One,
1513 operation: wgpu::BlendOperation::Max,
1514 },
1515 alpha: premult_alpha,
1516 }),
1517 );
1518 let projective_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1523 label: Some("projective_layer.wgsl"),
1524 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1525 "shaders/projective_layer.wgsl"
1526 ))),
1527 });
1528 let projective_pipeline_layout =
1529 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1530 label: Some("projective layer pipeline layout"),
1531 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
1532 immediate_size: 0,
1533 });
1534 let projective_vertex_layout = wgpu::VertexBufferLayout {
1535 array_stride: std::mem::size_of::<ProjectiveInstance>() as u64,
1536 step_mode: wgpu::VertexStepMode::Instance,
1537 attributes: &[
1538 wgpu::VertexAttribute {
1539 shader_location: 0,
1540 offset: 0,
1541 format: wgpu::VertexFormat::Float32x2,
1542 },
1543 wgpu::VertexAttribute {
1544 shader_location: 1,
1545 offset: 8,
1546 format: wgpu::VertexFormat::Float32x2,
1547 },
1548 wgpu::VertexAttribute {
1549 shader_location: 2,
1550 offset: 16,
1551 format: wgpu::VertexFormat::Float32x2,
1552 },
1553 wgpu::VertexAttribute {
1554 shader_location: 3,
1555 offset: 24,
1556 format: wgpu::VertexFormat::Float32x2,
1557 },
1558 wgpu::VertexAttribute {
1559 shader_location: 4,
1560 offset: 32,
1561 format: wgpu::VertexFormat::Float32x4,
1562 },
1563 wgpu::VertexAttribute {
1564 shader_location: 5,
1565 offset: 48,
1566 format: wgpu::VertexFormat::Float32x4,
1567 },
1568 wgpu::VertexAttribute {
1569 shader_location: 6,
1570 offset: 64,
1571 format: wgpu::VertexFormat::Float32,
1572 },
1573 ],
1574 };
1575 let projective_layer = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1576 label: Some("projective layer composite pipeline"),
1577 layout: Some(&projective_pipeline_layout),
1578 vertex: wgpu::VertexState {
1579 module: &projective_shader,
1580 entry_point: Some("vs_main"),
1581 buffers: &[Some(projective_vertex_layout)],
1582 compilation_options: wgpu::PipelineCompilationOptions::default(),
1583 },
1584 fragment: Some(wgpu::FragmentState {
1585 module: &projective_shader,
1586 entry_point: Some("fs_main"),
1587 targets: &[Some(wgpu::ColorTargetState {
1588 format,
1589 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1590 write_mask: wgpu::ColorWrites::ALL,
1591 })],
1592 compilation_options: wgpu::PipelineCompilationOptions::default(),
1593 }),
1594 primitive: wgpu::PrimitiveState::default(),
1595 depth_stencil: Some(stencil_for_content.clone()),
1596 multisample: msaa_state,
1597 multiview_mask: None,
1598 cache: None,
1599 });
1600
1601 let blend_layer_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1602 label: Some("blend_layer.wgsl"),
1603 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1604 "shaders/blend_layer.wgsl"
1605 ))),
1606 });
1607 let blend_layer_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1608 label: Some("blend layer pipeline layout"),
1609 bind_group_layouts: &[
1610 Some(globals_layout),
1611 Some(text_bind_layout),
1612 Some(text_bind_layout),
1613 ],
1614 immediate_size: 0,
1615 });
1616 let blend_vertex_layout = wgpu::VertexBufferLayout {
1617 array_stride: std::mem::size_of::<BlendInstance>() as u64,
1618 step_mode: wgpu::VertexStepMode::Instance,
1619 attributes: &[
1620 wgpu::VertexAttribute {
1621 shader_location: 0,
1622 offset: 0,
1623 format: wgpu::VertexFormat::Float32x4,
1624 },
1625 wgpu::VertexAttribute {
1626 shader_location: 1,
1627 offset: 16,
1628 format: wgpu::VertexFormat::Float32x4,
1629 },
1630 wgpu::VertexAttribute {
1631 shader_location: 2,
1632 offset: 32,
1633 format: wgpu::VertexFormat::Float32x4,
1634 },
1635 wgpu::VertexAttribute {
1636 shader_location: 3,
1637 offset: 48,
1638 format: wgpu::VertexFormat::Float32x4,
1639 },
1640 wgpu::VertexAttribute {
1641 shader_location: 4,
1642 offset: 64,
1643 format: wgpu::VertexFormat::Uint32,
1644 },
1645 ],
1646 };
1647 let blend_layer = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1648 label: Some("blend layer pipeline"),
1649 layout: Some(&blend_layer_layout),
1650 vertex: wgpu::VertexState {
1651 module: &blend_layer_shader,
1652 entry_point: Some("vs_main"),
1653 buffers: &[Some(blend_vertex_layout)],
1654 compilation_options: wgpu::PipelineCompilationOptions::default(),
1655 },
1656 fragment: Some(wgpu::FragmentState {
1657 module: &blend_layer_shader,
1658 entry_point: Some("fs_main"),
1659 targets: &[Some(wgpu::ColorTargetState {
1660 format,
1661 blend: None,
1662 write_mask: wgpu::ColorWrites::ALL,
1663 })],
1664 compilation_options: wgpu::PipelineCompilationOptions::default(),
1665 }),
1666 primitive: wgpu::PrimitiveState::default(),
1667 depth_stencil: Some(stencil_for_content.clone()),
1668 multisample: msaa_state,
1669 multiview_mask: None,
1670 cache: None,
1671 });
1672
1673 Self {
1674 rects,
1675 borders,
1676 ellipses,
1677 ellipse_borders,
1678 arcs,
1679 text_mask,
1680 text_color,
1681 image_rgba,
1682 image_nv12,
1683 coverage,
1684 blur,
1685 blur_content,
1686 clip_bin,
1687 clip_dec,
1688 slug,
1689 mesh,
1690 mesh_add,
1691 mesh_multiply,
1692 mesh_screen,
1693 mesh_darken,
1694 mesh_lighten,
1695 blend_layer,
1696 mesh_overlay,
1697 mesh_clip_inc,
1698 mesh_clip_dec,
1699 projective_layer,
1700 }
1701 }
1702}
1703
1704struct Pass {
1706 id: u64,
1707 target: PassTarget,
1708 initial_scissor: (u32, u32, u32, u32),
1710 active_scissor: Option<(u32, u32, u32, u32)>,
1713 clear_color: Option<[f32; 4]>,
1716 cmds: Vec<Cmd>,
1717}
1718
1719struct LayerState {
1720 layer_id: u32,
1721 parent_target: PassTarget,
1722 parent_scissors: Vec<repose_core::Rect>,
1723 parent_root_clip: repose_core::Rect,
1724 parent_size: (f32, f32),
1725 parent_transform: Transform,
1726 parent_scissor: Option<(u32, u32, u32, u32)>,
1727 parent_clips: Vec<ActiveClip>,
1728 alpha: f32,
1729 blur: (f32, f32),
1730 rectangle_edge: bool,
1731}
1732
1733struct FlattenRecord {
1737 stack_len: usize,
1740 layer_id: u32,
1741 map: [f32; 9],
1744 layer_rect: repose_core::Rect,
1746 saved_scissor_stack: Vec<repose_core::Rect>,
1747 saved_root: repose_core::Rect,
1748 saved_size: (f32, f32),
1749 saved_active_scissor: Option<(u32, u32, u32, u32)>,
1750 saved_clips: Vec<ActiveClip>,
1751}
1752
1753const FLATTEN_ID_BASE: u32 = 0xF000_0000;
1757const MAX_GRAPHICS_LAYERS: usize = 128;
1758const MAX_GRAPHICS_LAYER_BYTES: u64 = 256 * 1024 * 1024;
1759const MAX_TRANSIENT_GRAPHICS_LAYERS: usize = 64;
1760const MAX_TRANSIENT_GRAPHICS_LAYER_BYTES: u64 = 128 * 1024 * 1024;
1761const STENCIL_BASE: u32 = 128;
1762const STENCIL_MAX_DEPTH: u32 = 127;
1763
1764#[allow(non_snake_case)]
1765enum Cmd {
1766 ClipPush {
1767 off: u64,
1768 cnt: u32,
1769 scissor: (u32, u32, u32, u32),
1770 difference: bool,
1771 applied: bool,
1772 },
1773 ClipPop {
1774 off: u64,
1775 cnt: u32,
1776 scissor: (u32, u32, u32, u32),
1778 stencil_scissor: (u32, u32, u32, u32),
1781 difference: bool,
1782 applied: bool,
1783 },
1784 Rect {
1785 off: u64,
1786 cnt: u32,
1787 },
1788 Border {
1789 off: u64,
1790 cnt: u32,
1791 },
1792 Ellipse {
1793 off: u64,
1794 cnt: u32,
1795 },
1796 EllipseBorder {
1797 off: u64,
1798 cnt: u32,
1799 },
1800 Arc {
1801 off: u64,
1802 cnt: u32,
1803 },
1804 GlyphsMask {
1805 off: u64,
1806 cnt: u32,
1807 },
1808 GlyphsColor {
1809 off: u64,
1810 cnt: u32,
1811 },
1812 GlyphsVector {
1813 off: u64,
1814 cnt: u32,
1815 },
1816 ImageRgba {
1817 off: u64,
1818 cnt: u32,
1819 handle: u64,
1820 filter: ImageFilter,
1821 tile: bool,
1823 },
1824 Coverage {
1828 off: u64,
1829 cnt: u32,
1830 handle: u64,
1831 },
1832 ImageNv12 {
1833 off: u64,
1834 cnt: u32,
1835 handle: u64,
1836 filter: ImageFilter,
1837 },
1838 CompositeLayer {
1842 off: u64,
1843 cnt: u32,
1844 layer_id: u32,
1845 },
1846 CompositeShadow {
1852 off: u64,
1853 cnt: u32,
1854 layer_id: u32,
1855 },
1856 CompositeBlur {
1859 off: u64,
1860 cnt: u32,
1861 layer_id: u32,
1862 },
1863 CompositeProjective {
1868 off: u64,
1869 cnt: u32,
1870 layer_id: u32,
1871 },
1872 VectorMesh {
1874 mesh: MeshGpu,
1875 uoff: u64,
1876 blend: repose_core::BlendMode,
1877 },
1878 BlendLayer {
1885 off: u64,
1886 cnt: u32,
1887 src_layer: u32,
1888 dst_layer: Option<u32>,
1889 parent: PassTarget,
1890 scissor: (u32, u32, u32, u32),
1891 },
1892 VectorOverlay {
1894 mesh: MeshGpu,
1895 uoff: u64,
1896 },
1897 VectorClipPush {
1902 mesh: MeshGpu,
1903 uoff: u64,
1904 scissor: (u32, u32, u32, u32),
1905 difference: bool,
1906 applied: bool,
1907 },
1908 VectorClipPop {
1910 mesh: MeshGpu,
1911 uoff: u64,
1912 scissor: (u32, u32, u32, u32),
1913 stencil_scissor: (u32, u32, u32, u32),
1914 difference: bool,
1915 applied: bool,
1916 },
1917 Callback {
1918 rect: repose_core::Rect,
1919 clip_rect: repose_core::Rect,
1920 scissor: (u32, u32, u32, u32),
1921 restore_scissor: (u32, u32, u32, u32),
1922 callback_id: usize,
1923 payload: repose_core::PaintCallbackPayload,
1924 },
1925}
1926
1927struct CoverageTex {
1932 #[allow(dead_code)]
1934 tex: wgpu::Texture,
1935 bind: wgpu::BindGroup,
1936 w: u32,
1937 h: u32,
1938 last_used_frame: u64,
1939 bytes: u64,
1940}
1941
1942struct ImageBinds {
1943 linear: wgpu::BindGroup,
1944 nearest: wgpu::BindGroup,
1945 tile_linear: wgpu::BindGroup,
1947 tile_nearest: wgpu::BindGroup,
1948}
1949
1950impl ImageBinds {
1951 fn get(&self, filter: ImageFilter, tile: bool) -> &wgpu::BindGroup {
1952 match (filter, tile) {
1953 (ImageFilter::Linear, false) => &self.linear,
1954 (ImageFilter::Nearest, false) => &self.nearest,
1955 (ImageFilter::Linear, true) => &self.tile_linear,
1956 (ImageFilter::Nearest, true) => &self.tile_nearest,
1957 }
1958 }
1959}
1960
1961enum ImageTex {
1962 Rgba {
1963 tex: wgpu::Texture,
1964 binds: ImageBinds,
1965 w: u32,
1966 h: u32,
1967 format: wgpu::TextureFormat,
1968 last_used_frame: u64,
1969 bytes: u64,
1970 },
1971 User {
1973 binds: ImageBinds,
1974 w: u32,
1975 h: u32,
1976 last_used_frame: u64,
1977 bytes: u64,
1978 },
1979 Nv12 {
1980 tex_y: wgpu::Texture,
1981 tex_uv: wgpu::Texture,
1982 binds: ImageBinds,
1983 yuv_buf: wgpu::Buffer,
1984 w: u32,
1985 h: u32,
1986 pixel_format: PixelFormat,
1987 fourcc: Option<u32>,
1988 color_info: ColorInfo,
1989 external: bool,
1990 last_used_frame: u64,
1991 bytes: u64,
1992 },
1993}
1994
1995impl ImageTex {
1996 fn evictable(&self) -> bool {
1997 match self {
1998 Self::Rgba { .. } => true,
1999 Self::User { .. } => false,
2000 Self::Nv12 { external, .. } => !*external,
2001 }
2002 }
2003}
2004
2005#[derive(Clone)]
2006struct RetainedImage {
2007 w: u32,
2008 h: u32,
2009 format: wgpu::TextureFormat,
2010 rgba: Vec<u8>,
2011 last_used_frame: u64,
2012}
2013
2014struct ResolvedImageSource {
2015 width: f32,
2016 height: f32,
2017 uv_min: [f32; 2],
2018 uv_max: [f32; 2],
2019}
2020
2021fn resolve_image_source(
2022 source_rect: Option<ImageSourceRect>,
2023 image_width: u32,
2024 image_height: u32,
2025) -> Option<ResolvedImageSource> {
2026 if image_width == 0 || image_height == 0 {
2027 return None;
2028 }
2029 let Some(source) = source_rect else {
2030 return Some(ResolvedImageSource {
2031 width: image_width as f32,
2032 height: image_height as f32,
2033 uv_min: [0.0, 0.0],
2034 uv_max: [1.0, 1.0],
2035 });
2036 };
2037 if source.is_empty() {
2038 return None;
2039 }
2040 let right = source.x.checked_add(source.width)?;
2041 let bottom = source.y.checked_add(source.height)?;
2042 if right > image_width || bottom > image_height {
2043 return None;
2044 }
2045 Some(ResolvedImageSource {
2046 width: source.width as f32,
2047 height: source.height as f32,
2048 uv_min: [
2049 source.x as f32 / image_width as f32,
2050 source.y as f32 / image_height as f32,
2051 ],
2052 uv_max: [
2053 right as f32 / image_width as f32,
2054 bottom as f32 / image_height as f32,
2055 ],
2056 })
2057}
2058
2059fn image_fit_geometry(
2068 rect: repose_core::Rect,
2069 fit: ImageFit,
2070 alignment: ImageAlignment,
2071 source: &ResolvedImageSource,
2072) -> Option<(repose_core::Rect, [f32; 4])> {
2073 let src_w = source.width;
2074 let src_h = source.height;
2075 let dst_w = rect.w.max(0.0);
2076 let dst_h = rect.h.max(0.0);
2077 if src_w <= 0.0 || src_h <= 0.0 || dst_w <= 0.0 || dst_h <= 0.0 {
2078 return None;
2079 }
2080
2081 let slack = |content: f32, extent: f32| match alignment {
2084 ImageAlignment::Center => (extent - content) * 0.5,
2085 ImageAlignment::Begin => 0.0,
2086 ImageAlignment::End => extent - content,
2087 };
2088
2089 let tiled = matches!(fit, ImageFit::Tile);
2092 let (scale_x, scale_y) = fit.scale_factor((src_w, src_h), (dst_w, dst_h));
2093 let drawn = if tiled {
2094 repose_core::Rect {
2095 x: rect.x,
2096 y: rect.y,
2097 w: dst_w,
2098 h: dst_h,
2099 }
2100 } else {
2101 let (w, h) = (src_w * scale_x, src_h * scale_y);
2102 repose_core::Rect {
2103 x: rect.x + slack(w, dst_w),
2104 y: rect.y + slack(h, dst_h),
2105 w,
2106 h,
2107 }
2108 };
2109
2110 let uv_min = source.uv_min;
2111 let uv_max = source.uv_max;
2112 let repeats = if tiled {
2113 [dst_w / src_w, dst_h / src_h]
2114 } else {
2115 [1.0, 1.0]
2116 };
2117 let mut uv = [
2118 uv_min[0],
2119 uv_max[1],
2120 uv_min[0] + (uv_max[0] - uv_min[0]) * repeats[0],
2121 uv_max[1] - (uv_max[1] - uv_min[1]) * repeats[1],
2122 ];
2123
2124 let clipped = intersect(drawn, rect);
2125 if clipped.w <= 0.0 || clipped.h <= 0.0 {
2126 return None;
2127 }
2128 let du = uv[2] - uv[0];
2129 let dv = uv[3] - uv[1];
2130 uv = [
2131 uv[0] + du * (clipped.x - drawn.x) / drawn.w,
2132 uv[1] + dv * (clipped.y - drawn.y) / drawn.h,
2133 uv[0] + du * (clipped.x + clipped.w - drawn.x) / drawn.w,
2134 uv[1] + dv * (clipped.y + clipped.h - drawn.y) / drawn.h,
2135 ];
2136 Some((clipped, uv))
2137}
2138
2139struct AtlasA8 {
2140 tex: wgpu::Texture,
2141 view: wgpu::TextureView,
2142 sampler: wgpu::Sampler,
2143 size: u32,
2144 next_x: u32,
2145 next_y: u32,
2146 row_h: u32,
2147 map: HashMap<repose_text::CacheKey, GlyphInfo>,
2148 used_area: u64,
2151}
2152
2153struct AtlasRGBA {
2154 tex: wgpu::Texture,
2155 view: wgpu::TextureView,
2156 sampler: wgpu::Sampler,
2157 size: u32,
2158 next_x: u32,
2159 next_y: u32,
2160 row_h: u32,
2161 map: HashMap<repose_text::CacheKey, GlyphInfo>,
2162 used_area: u64,
2163}
2164
2165#[derive(Default, Clone, Copy)]
2169struct AtlasPending {
2170 grow: bool,
2172 compact: bool,
2175}
2176
2177#[derive(Clone, Copy)]
2178struct GlyphInfo {
2179 u0: f32,
2180 v0: f32,
2181 u1: f32,
2182 v1: f32,
2183 w: f32,
2184 h: f32,
2185 last_touch: u64,
2189 last_used_frame: u64,
2190}
2191
2192#[derive(Clone, Hash, PartialEq, Eq)]
2193struct TextShapeKey {
2194 text: Arc<str>,
2195 px_bits: u32,
2196 line_height_bits: u32,
2197 font_family: Option<&'static str>,
2198 font_weight: u16,
2199 font_style: u8,
2200 letter_spacing_bits: u32,
2201 variation: Option<Arc<str>>,
2202 font_generation: u64,
2203}
2204
2205struct TextShapeCacheEntry {
2206 glyphs: Arc<[repose_text::ShapedGlyph]>,
2207 last_used_frame: u64,
2208}
2209
2210#[repr(C)]
2215#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2216struct RectInstance {
2217 xywh: [f32; 4],
2218 radii: [f32; 4],
2219 color0: [f32; 4],
2220 color1: [f32; 4],
2221 fwd_mat: [f32; 4],
2222 grad: [f32; 4],
2223 bits: u32,
2224 _pad: [f32; 3],
2225}
2226
2227#[repr(C)]
2228#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2229struct BorderInstance {
2230 xywh: [f32; 4],
2231 radii: [f32; 4],
2232 color0: [f32; 4],
2233 color1: [f32; 4],
2234 fwd_mat: [f32; 4],
2235 grad: [f32; 4],
2236 bits: u32,
2237 stroke: f32,
2238 _pad: [f32; 2],
2239}
2240
2241#[repr(C)]
2242#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2243struct EllipseInstance {
2244 xywh: [f32; 4],
2245 color0: [f32; 4],
2246 color1: [f32; 4],
2247 fwd_mat: [f32; 4],
2248 grad: [f32; 4],
2249 bits: u32,
2250 _pad: [f32; 1],
2251}
2252
2253#[repr(C)]
2254#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2255struct EllipseBorderInstance {
2256 xywh: [f32; 4],
2257 color0: [f32; 4],
2258 color1: [f32; 4],
2259 fwd_mat: [f32; 4],
2260 grad: [f32; 4],
2261 bits: u32,
2262 stroke: f32,
2263 pad: f32,
2264 _pad: f32,
2265}
2266
2267#[repr(C)]
2268#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2269struct ArcInstance {
2270 xywh: [f32; 4],
2271 color0: [f32; 4],
2272 color1: [f32; 4],
2273 fwd_mat: [f32; 4],
2274 grad: [f32; 4],
2275 bits: u32,
2276 start_angle: f32,
2277 sweep_angle: f32,
2278 stroke: f32,
2279 pad: f32,
2280 _pad: f32,
2281}
2282
2283#[repr(C)]
2284#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2285struct GlyphInstance {
2286 xywh: [f32; 4],
2287 uv: [f32; 4],
2288 color: [f32; 4],
2289 fwd_mat: [f32; 4],
2290}
2291
2292#[repr(C)]
2293#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2294struct BlurInstance {
2295 xywh: [f32; 4],
2296 uv: [f32; 4],
2297 color: [f32; 4],
2298 sigma_px: [f32; 2],
2299 fwd_mat: [f32; 4],
2300 axis: u32,
2301 edge_mode: u32,
2302 shape: [f32; 4],
2306}
2307
2308#[repr(C)]
2314#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2315struct ProjectiveInstance {
2316 c0: [f32; 2],
2317 c1: [f32; 2],
2318 c2: [f32; 2],
2319 c3: [f32; 2],
2320 uv: [f32; 4],
2321 w: [f32; 4],
2322 alpha: f32,
2323 _pad: [f32; 3],
2324}
2325
2326#[repr(C)]
2329#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2330struct YuvTransformRaw {
2331 row0: [f32; 4],
2332 row1: [f32; 4],
2333 row2: [f32; 4],
2334 b: [f32; 4],
2335 transfer: [f32; 4],
2336}
2337
2338fn make_yuv_transform_raw(color_info: ColorInfo, pixel_format: PixelFormat) -> YuvTransformRaw {
2339 let yuv = color_info.to_yuv_transform();
2340 let sample_scale = match pixel_format {
2341 PixelFormat::P010 => 65535.0 / (64.0 * 1020.0),
2342 _ => 1.0,
2343 };
2344 let transfer_mode = match color_info.transfer {
2345 repose_core::color::Transfer::Srgb => 0.0,
2346 repose_core::color::Transfer::Bt709 => 1.0,
2347 repose_core::color::Transfer::Linear => 2.0,
2348 repose_core::color::Transfer::Pq => 3.0,
2349 repose_core::color::Transfer::Hlg => 4.0,
2350 };
2351 YuvTransformRaw {
2352 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2353 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2354 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2355 b: [yuv.b[0], yuv.b[1], yuv.b[2], sample_scale],
2356 transfer: [transfer_mode, 0.0, 0.0, 0.0],
2357 }
2358}
2359
2360#[repr(C)]
2361#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2362struct Nv12Instance {
2363 xywh: [f32; 4],
2364 uv: [f32; 4],
2365 color: [f32; 4],
2366 uv_x_offset: f32,
2367 uv_y_offset: f32,
2368 fwd_mat: [f32; 4],
2369}
2370
2371#[repr(C)]
2372#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2373struct ClipInstance {
2374 xywh: [f32; 4],
2375 radii: [f32; 4],
2376 fwd_mat: [f32; 4],
2377}
2378
2379#[repr(C)]
2382#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2383struct BlendInstance {
2384 xywh: [f32; 4],
2385 uv: [f32; 4],
2386 color: [f32; 4],
2387 fwd_mat: [f32; 4],
2388 mode: u32,
2389 _pad: [f32; 3],
2390}
2391
2392#[repr(C)]
2393#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2394struct MeshVertex {
2395 pos: [f32; 2],
2396 color: [f32; 4],
2397 uv: [f32; 2],
2398}
2399
2400#[derive(Clone)]
2401struct MeshGpu {
2402 vertices: wgpu::Buffer,
2403 indices: wgpu::Buffer,
2404 vertex_count: u32,
2405 index_count: u32,
2406 bytes: u64,
2407}
2408
2409#[derive(Clone, Copy, PartialEq, Eq, Hash)]
2410struct MeshCacheKey {
2411 source: usize,
2412 index: usize,
2413 kind: u8,
2414 vertex_len: usize,
2415 index_len: usize,
2416}
2417
2418#[derive(Clone)]
2419enum MeshOwner {
2420 Object(Arc<repose_core::VectorMeshData>),
2421 Overlay(Arc<[repose_core::VectorMeshData]>),
2422}
2423
2424struct MeshCacheEntry {
2425 owner: MeshOwner,
2426 gpu: MeshGpu,
2427 last_used_frame: u64,
2428 last_touch_frame: u64,
2429}
2430
2431#[derive(Clone, PartialEq, Eq, Hash)]
2432struct SlugDrawKey {
2433 cache_key: repose_text::CacheKey,
2434 fill: bool,
2435 stroke: Option<slug::StrokeTessKey>,
2438 transform: [u32; 11],
2439 origin: [u32; 2],
2440 px_bits: u32,
2441 target: [u32; 2],
2442 color: [u32; 4],
2443}
2444
2445struct SlugDrawCacheEntry {
2446 vertices: Arc<[slug::TessVertex]>,
2447 bytes: u64,
2448 last_used_frame: u64,
2449 last_touch_frame: u64,
2450}
2451
2452struct GlyphOutlineEntry {
2456 commands: Option<Arc<[repose_text::Command]>>,
2457 last_touch: u64,
2458}
2459
2460fn evict_atlas_lru(
2465 map: &mut HashMap<repose_text::CacheKey, GlyphInfo>,
2466 used_area: &mut u64,
2467 frame_index: u64,
2468 target_area: u64,
2469) -> bool {
2470 let mut victims: Vec<(u64, repose_text::CacheKey)> = map
2471 .iter()
2472 .filter(|(_, info)| info.last_used_frame != frame_index)
2473 .map(|(key, info)| (info.last_touch, key.clone()))
2474 .collect();
2475 if victims.is_empty() {
2476 return false;
2477 }
2478 victims.sort_unstable_by_key(|victim| victim.0);
2480 let mut evicted = false;
2481 for (_, key) in victims {
2482 if *used_area <= target_area || map.len() <= 1 {
2483 break;
2484 }
2485 if let Some(info) = map.remove(&key) {
2486 *used_area = used_area.saturating_sub(glyph_cell_area(&info));
2487 evicted = true;
2488 }
2489 }
2490 evicted
2491}
2492
2493fn glyph_cell_area(info: &GlyphInfo) -> u64 {
2496 (info.w as u64).saturating_mul(info.h as u64)
2497}
2498
2499fn defer_atlas_glyph(deferred: &mut HashSet<repose_text::CacheKey>, key: repose_text::CacheKey) {
2503 if deferred.len() >= MAX_GLYPH_ATLAS_ENTRIES {
2504 deferred.clear();
2505 }
2506 deferred.insert(key);
2507}
2508
2509fn slug_draw_cache_entry_bytes(vertices: usize) -> u64 {
2513 let per_entry = (std::mem::size_of::<SlugDrawKey>()
2514 + std::mem::size_of::<SlugDrawCacheEntry>()
2515 + std::mem::size_of::<(SlugDrawKey, SlugDrawCacheEntry)>()) as u64;
2516 u64::try_from(vertices)
2517 .ok()
2518 .and_then(|count| count.checked_mul(std::mem::size_of::<slug::TessVertex>() as u64))
2519 .and_then(|payload| payload.checked_add(per_entry))
2520 .unwrap_or(u64::MAX)
2521}
2522
2523#[repr(C)]
2524#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
2525struct MeshUniform {
2526 m0: [f32; 4],
2527 m1: [f32; 4],
2528 paint: [u32; 4],
2529 color0: [f32; 4],
2530 color1: [f32; 4],
2531 grad_start: [f32; 2],
2532 _p3: [f32; 2],
2533 grad_end: [f32; 2],
2534 _p4: [f32; 2],
2535}
2536
2537const MESH_UNIFORM_CAP: u64 = 4 * 1024 * 1024;
2538const MAX_RETAINED_IMAGE_BYTES: u64 = 256 * 1024 * 1024;
2539const MAX_RETAINED_IMAGES: usize = 512;
2540const MAX_BLEND_SNAPSHOT_BYTES: u64 = 64 * 1024 * 1024;
2541const MAX_BLEND_SNAPSHOT_POOL_ENTRIES: usize = 64;
2542const MAX_GLYPH_ATLAS_ENTRIES: usize = 32768;
2543const MAX_GLYPH_ATLAS_SIZE: u32 = 4096;
2546const ATLAS_COMPACT_KEEP_NUM: u64 = 3;
2550const ATLAS_COMPACT_KEEP_DEN: u64 = 4;
2551const ATLAS_COMPACT_MIN_FRAME_INTERVAL: u64 = 30;
2555const MAX_GPU_RESOURCE_BYTES: u64 = 1024 * 1024 * 1024;
2556const MAX_UPLOAD_RING_BYTES: u64 = 64 * 1024 * 1024;
2557const MAX_MESH_CACHE_BYTES: u64 = 256 * 1024 * 1024;
2558const MAX_MESH_CACHE_ENTRIES: usize = 512;
2559const MAX_SLUG_DRAW_CACHE_ENTRIES: usize = 2048;
2560const MAX_SLUG_DRAW_CACHE_BYTES: u64 = 32 * 1024 * 1024;
2561const MAX_LEGACY_SHAPED_TEXT_CACHE_ENTRIES: usize = 256;
2562const MAX_GLYPH_OUTLINE_CACHE_ENTRIES: usize = 4096;
2563const MAX_IMAGE_DECODE_BYTES: u64 = 64 * 1024 * 1024;
2564const MAX_CALLBACK_SCOPES: usize = 256;
2565const NV12_FOURCC: u32 = 0x3231_564e;
2566const P010_FOURCC: u32 = 0x3031_3050;
2567
2568fn canonical_fourcc(format: PixelFormat) -> u32 {
2569 match format {
2570 PixelFormat::P010 => P010_FOURCC,
2571 _ => NV12_FOURCC,
2572 }
2573}
2574
2575impl MeshUniform {
2576 fn identity() -> Self {
2577 Self {
2578 m0: [1.0, 0.0, 0.0, 0.0],
2579 m1: [0.0, 1.0, 0.0, 0.0],
2580 paint: [0; 4],
2581 color0: [0.0; 4],
2582 color1: [0.0; 4],
2583 grad_start: [0.0; 2],
2584 _p3: [0.0; 2],
2585 grad_end: [0.0; 2],
2586 _p4: [0.0; 2],
2587 }
2588 }
2589}
2590
2591fn mesh_uniform_from_paint(affine: [f32; 6], paint: &repose_core::PaintDesc) -> MeshUniform {
2592 let (paint_type, paint_kind, color0, color1, grad_start, grad_end) = match paint {
2593 repose_core::PaintDesc::Solid => (0u32, 0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
2594 repose_core::PaintDesc::Linear {
2595 start,
2596 end,
2597 start_color,
2598 end_color,
2599 } => (
2600 1u32,
2601 0u32,
2602 start_color.to_linear(),
2603 end_color.to_linear(),
2604 [start.x, start.y],
2605 [end.x, end.y],
2606 ),
2607 repose_core::PaintDesc::Radial {
2608 center,
2609 radius,
2610 start_color,
2611 end_color,
2612 } => (
2613 1u32,
2614 1u32,
2615 start_color.to_linear(),
2616 end_color.to_linear(),
2617 [center.x, center.y],
2618 [radius.max(0.0), 0.0],
2619 ),
2620 repose_core::PaintDesc::Sweep {
2621 center,
2622 start_color,
2623 end_color,
2624 } => (
2625 1u32,
2626 2u32,
2627 start_color.to_linear(),
2628 end_color.to_linear(),
2629 [center.x, center.y],
2630 [0.0, 0.0],
2631 ),
2632 _ => (0u32, 0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
2633 };
2634 MeshUniform {
2635 m0: [affine[0], affine[1], affine[2], 0.0],
2636 m1: [affine[3], affine[4], affine[5], 0.0],
2637 paint: [paint_type, paint_kind, 0, 0],
2638 color0,
2639 color1,
2640 grad_start,
2641 _p3: [0.0; 2],
2642 grad_end,
2643 _p4: [0.0; 2],
2644 }
2645}
2646
2647fn combine_mesh_affine(current: &Transform, mesh: [f32; 6]) -> [f32; 6] {
2648 let cm = current.linear();
2649 let (cm00, cm01, cm10, cm11) = (cm[0], cm[1], cm[2], cm[3]);
2650 let mm00 = mesh[0];
2651 let mm01 = mesh[1];
2652 let mm10 = mesh[2];
2653 let mm11 = mesh[3];
2654 let mtx = mesh[4];
2655 let mty = mesh[5];
2656 let r00 = cm00 * mm00 + cm01 * mm10;
2657 let r01 = cm00 * mm01 + cm01 * mm11;
2658 let r10 = cm10 * mm00 + cm11 * mm10;
2659 let r11 = cm10 * mm01 + cm11 * mm11;
2660 let tx = cm00 * mtx + cm01 * mty + current.translate_x;
2661 let ty = cm10 * mtx + cm11 * mty + current.translate_y;
2662 [r00, r01, tx, r10, r11, ty]
2665}
2666
2667fn mesh_aabb(mesh: &repose_core::VectorMeshData, affine: [f32; 6]) -> repose_core::Rect {
2668 let mut min_x = f32::INFINITY;
2669 let mut min_y = f32::INFINITY;
2670 let mut max_x = f32::NEG_INFINITY;
2671 let mut max_y = f32::NEG_INFINITY;
2672 for v in mesh.vertices.iter() {
2673 let x = affine[0] * v.pos[0] + affine[1] * v.pos[1] + affine[2];
2674 let y = affine[3] * v.pos[0] + affine[4] * v.pos[1] + affine[5];
2675 min_x = min_x.min(x);
2676 min_y = min_y.min(y);
2677 max_x = max_x.max(x);
2678 max_y = max_y.max(y);
2679 }
2680 let w = (max_x - min_x).max(0.0);
2681 let h = (max_y - min_y).max(0.0);
2682 if !min_x.is_finite() || !min_y.is_finite() {
2683 return repose_core::Rect {
2684 x: 0.0,
2685 y: 0.0,
2686 w: 0.0,
2687 h: 0.0,
2688 };
2689 }
2690 repose_core::Rect {
2691 x: min_x,
2692 y: min_y,
2693 w,
2694 h,
2695 }
2696}
2697
2698fn quantize_glyph_px(px: f32) -> f32 {
2699 if !px.is_finite() || px <= 0.0 {
2700 return 1.0;
2701 }
2702 ((px * 4.0).round() / 4.0).max(0.25)
2703}
2704
2705fn swash_to_a8_coverage(content: repose_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
2706 match content {
2707 repose_text::SwashContent::Mask => Some(data.to_vec()),
2708 repose_text::SwashContent::SubpixelMask => {
2709 let mut out = Vec::with_capacity(data.len() / 4);
2710 for px in data.as_chunks::<4>().0 {
2711 let r = px[0];
2712 let g = px[1];
2713 let b = px[2];
2714 out.push(r.max(g).max(b));
2715 }
2716 Some(out)
2717 }
2718 repose_text::SwashContent::Color => None,
2719 }
2720}
2721
2722impl WgpuSceneRenderer {
2723 pub fn from_device(
2734 device: wgpu::Device,
2735 queue: wgpu::Queue,
2736 output_format: wgpu::TextureFormat,
2737 msaa_samples: u32,
2738 ) -> anyhow::Result<Self> {
2739 validate_sample_count("msaa_samples", msaa_samples)?;
2740 Self::from_device_with_working_space_msaa(
2741 device,
2742 queue,
2743 output_format,
2744 msaa_samples,
2745 msaa_samples,
2746 )
2747 }
2748
2749 pub fn try_from_device_with_adapter(
2753 adapter: &wgpu::Adapter,
2754 device: wgpu::Device,
2755 queue: wgpu::Queue,
2756 output_format: wgpu::TextureFormat,
2757 requested_msaa: u32,
2758 ) -> anyhow::Result<Self> {
2759 let msaa = pick_surface_msaa(adapter, output_format, requested_msaa);
2760 validate_render_target_format(adapter, output_format, msaa)?;
2761 let working_space_msaa =
2762 pick_surface_msaa_for_mode(adapter, wgpu::TextureFormat::Rgba16Float, msaa, true);
2763 Self::from_device_with_working_space_msaa(
2764 device,
2765 queue,
2766 output_format,
2767 msaa,
2768 working_space_msaa,
2769 )
2770 }
2771
2772 pub fn with_surface_format(
2776 mut self,
2777 surface_format: wgpu::TextureFormat,
2778 ) -> anyhow::Result<Self> {
2779 if surface_format != self.output_format
2780 && surface_format.add_srgb_suffix() != self.output_format
2781 {
2782 anyhow::bail!(
2783 "surface format {surface_format:?} cannot be reinterpreted as {:?}",
2784 self.output_format
2785 );
2786 }
2787 if self.surface_format != surface_format {
2788 self.surface_format = surface_format;
2789 self.recreate_msaa_and_depth_stencil();
2790 }
2791 Ok(self)
2792 }
2793
2794 pub fn from_device_with_working_space_msaa(
2795 device: wgpu::Device,
2796 queue: wgpu::Queue,
2797 output_format: wgpu::TextureFormat,
2798 msaa_samples: u32,
2799 working_space_msaa_samples: u32,
2800 ) -> anyhow::Result<Self> {
2801 validate_sample_count("msaa_samples", msaa_samples)?;
2802 validate_sample_count("working_space_msaa_samples", working_space_msaa_samples)?;
2803 let msaa_samples = msaa_samples.max(1);
2804 let working_space_msaa_samples = working_space_msaa_samples.max(1);
2805 let mesh_uniform_size = std::mem::size_of::<MeshUniform>() as u64;
2806 let base_mesh_alignment =
2807 u64::from(device.limits().min_uniform_buffer_offset_alignment).max(4);
2808 let mesh_uniform_alignment =
2809 align_up(mesh_uniform_size, base_mesh_alignment).unwrap_or(base_mesh_alignment);
2810 let mesh_uniform_cap = align_up(
2811 MESH_UNIFORM_CAP
2812 .min(device.limits().max_buffer_size)
2813 .max(mesh_uniform_alignment),
2814 mesh_uniform_alignment,
2815 )
2816 .unwrap_or(mesh_uniform_alignment);
2817 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2818 label: Some("globals layout"),
2819 entries: &[wgpu::BindGroupLayoutEntry {
2820 binding: 0,
2821 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
2822 ty: wgpu::BindingType::Buffer {
2823 ty: wgpu::BufferBindingType::Uniform,
2824 has_dynamic_offset: false,
2825 min_binding_size: None,
2826 },
2827 count: None,
2828 }],
2829 });
2830
2831 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
2832 label: Some("globals buf"),
2833 size: std::mem::size_of::<Globals>() as u64,
2834 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2835 mapped_at_creation: false,
2836 });
2837
2838 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
2839 label: Some("globals bind"),
2840 layout: &globals_layout,
2841 entries: &[wgpu::BindGroupEntry {
2842 binding: 0,
2843 resource: globals_buf.as_entire_binding(),
2844 }],
2845 });
2846 let globals_staging_cap =
2847 (64 * std::mem::size_of::<Globals>() as u64).max(std::mem::size_of::<Globals>() as u64);
2848 let globals_staging = device.create_buffer(&wgpu::BufferDescriptor {
2849 label: Some("globals staging"),
2850 size: globals_staging_cap,
2851 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
2852 mapped_at_creation: false,
2853 });
2854
2855 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
2856
2857 let stencil_for_content = wgpu::DepthStencilState {
2858 format: ds_format,
2859 depth_write_enabled: Some(false),
2860 depth_compare: Some(wgpu::CompareFunction::Always),
2861 stencil: wgpu::StencilState {
2862 front: wgpu::StencilFaceState {
2863 compare: wgpu::CompareFunction::Equal,
2870 fail_op: wgpu::StencilOperation::Keep,
2871 depth_fail_op: wgpu::StencilOperation::Keep,
2872 pass_op: wgpu::StencilOperation::Keep,
2873 },
2874 back: wgpu::StencilFaceState {
2875 compare: wgpu::CompareFunction::Equal,
2876 fail_op: wgpu::StencilOperation::Keep,
2877 depth_fail_op: wgpu::StencilOperation::Keep,
2878 pass_op: wgpu::StencilOperation::Keep,
2879 },
2880 read_mask: 0xFF,
2881 write_mask: 0x00,
2882 },
2883 bias: wgpu::DepthBiasState::default(),
2884 };
2885
2886 let stencil_for_clip_inc = wgpu::DepthStencilState {
2887 format: ds_format,
2888 depth_write_enabled: Some(false),
2889 depth_compare: Some(wgpu::CompareFunction::Always),
2890 stencil: wgpu::StencilState {
2891 front: wgpu::StencilFaceState {
2892 compare: wgpu::CompareFunction::Always,
2893 fail_op: wgpu::StencilOperation::Keep,
2894 depth_fail_op: wgpu::StencilOperation::Keep,
2895 pass_op: wgpu::StencilOperation::IncrementClamp,
2896 },
2897 back: wgpu::StencilFaceState {
2898 compare: wgpu::CompareFunction::Always,
2899 fail_op: wgpu::StencilOperation::Keep,
2900 depth_fail_op: wgpu::StencilOperation::Keep,
2901 pass_op: wgpu::StencilOperation::IncrementClamp,
2902 },
2903 read_mask: 0xFF,
2904 write_mask: 0xFF,
2905 },
2906 bias: wgpu::DepthBiasState::default(),
2907 };
2908
2909 let stencil_for_clip_dec = wgpu::DepthStencilState {
2910 format: ds_format,
2911 depth_write_enabled: Some(false),
2912 depth_compare: Some(wgpu::CompareFunction::Always),
2913 stencil: wgpu::StencilState {
2914 front: wgpu::StencilFaceState {
2915 compare: wgpu::CompareFunction::Always,
2916 fail_op: wgpu::StencilOperation::Keep,
2917 depth_fail_op: wgpu::StencilOperation::Keep,
2918 pass_op: wgpu::StencilOperation::DecrementClamp,
2919 },
2920 back: wgpu::StencilFaceState {
2921 compare: wgpu::CompareFunction::Always,
2922 fail_op: wgpu::StencilOperation::Keep,
2923 depth_fail_op: wgpu::StencilOperation::Keep,
2924 pass_op: wgpu::StencilOperation::DecrementClamp,
2925 },
2926 read_mask: 0xFF,
2927 write_mask: 0xFF,
2928 },
2929 bias: wgpu::DepthBiasState::default(),
2930 };
2931
2932 let _multisample_state = wgpu::MultisampleState {
2933 count: msaa_samples,
2934 mask: !0,
2935 alpha_to_coverage_enabled: false,
2936 };
2937
2938 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
2942 label: Some("image/text sampler"),
2943 address_mode_u: wgpu::AddressMode::ClampToEdge,
2944 address_mode_v: wgpu::AddressMode::ClampToEdge,
2945 mag_filter: wgpu::FilterMode::Linear,
2946 min_filter: wgpu::FilterMode::Linear,
2947 mipmap_filter: wgpu::MipmapFilterMode::Linear,
2948 ..Default::default()
2949 });
2950 let image_sampler_nearest = device.create_sampler(&wgpu::SamplerDescriptor {
2951 label: Some("image nearest sampler"),
2952 address_mode_u: wgpu::AddressMode::ClampToEdge,
2953 address_mode_v: wgpu::AddressMode::ClampToEdge,
2954 mag_filter: wgpu::FilterMode::Nearest,
2955 min_filter: wgpu::FilterMode::Nearest,
2956 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
2957 ..Default::default()
2958 });
2959
2960 let image_sampler_repeat = device.create_sampler(&wgpu::SamplerDescriptor {
2963 label: Some("image repeat sampler"),
2964 address_mode_u: wgpu::AddressMode::Repeat,
2965 address_mode_v: wgpu::AddressMode::Repeat,
2966 mag_filter: wgpu::FilterMode::Linear,
2967 min_filter: wgpu::FilterMode::Linear,
2968 mipmap_filter: wgpu::MipmapFilterMode::Linear,
2969 ..Default::default()
2970 });
2971 let image_sampler_repeat_nearest = device.create_sampler(&wgpu::SamplerDescriptor {
2972 label: Some("image repeat nearest sampler"),
2973 address_mode_u: wgpu::AddressMode::Repeat,
2974 address_mode_v: wgpu::AddressMode::Repeat,
2975 mag_filter: wgpu::FilterMode::Nearest,
2976 min_filter: wgpu::FilterMode::Nearest,
2977 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
2978 ..Default::default()
2979 });
2980
2981 let layer_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
2983 label: Some("layer nearest sampler"),
2984 address_mode_u: wgpu::AddressMode::ClampToEdge,
2985 address_mode_v: wgpu::AddressMode::ClampToEdge,
2986 mag_filter: wgpu::FilterMode::Nearest,
2987 min_filter: wgpu::FilterMode::Nearest,
2988 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
2989 ..Default::default()
2990 });
2991
2992 let layer_sampler_linear = device.create_sampler(&wgpu::SamplerDescriptor {
2995 label: Some("layer linear sampler"),
2996 address_mode_u: wgpu::AddressMode::ClampToEdge,
2997 address_mode_v: wgpu::AddressMode::ClampToEdge,
2998 mag_filter: wgpu::FilterMode::Linear,
2999 min_filter: wgpu::FilterMode::Linear,
3000 mipmap_filter: wgpu::MipmapFilterMode::Linear,
3001 ..Default::default()
3002 });
3003
3004 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3006 label: Some("text/rgba bind layout"),
3007 entries: &[
3008 wgpu::BindGroupLayoutEntry {
3009 binding: 0,
3010 visibility: wgpu::ShaderStages::FRAGMENT,
3011 ty: wgpu::BindingType::Texture {
3012 multisampled: false,
3013 view_dimension: wgpu::TextureViewDimension::D2,
3014 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3015 },
3016 count: None,
3017 },
3018 wgpu::BindGroupLayoutEntry {
3019 binding: 1,
3020 visibility: wgpu::ShaderStages::FRAGMENT,
3021 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3022 count: None,
3023 },
3024 ],
3025 });
3026 let image_bind_layout_rgba = text_bind_layout.clone();
3028
3029 let image_bind_layout_nv12 =
3031 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3032 label: Some("image bind layout nv12"),
3033 entries: &[
3034 wgpu::BindGroupLayoutEntry {
3036 binding: 0,
3037 visibility: wgpu::ShaderStages::FRAGMENT,
3038 ty: wgpu::BindingType::Texture {
3039 multisampled: false,
3040 view_dimension: wgpu::TextureViewDimension::D2,
3041 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3042 },
3043 count: None,
3044 },
3045 wgpu::BindGroupLayoutEntry {
3047 binding: 1,
3048 visibility: wgpu::ShaderStages::FRAGMENT,
3049 ty: wgpu::BindingType::Texture {
3050 multisampled: false,
3051 view_dimension: wgpu::TextureViewDimension::D2,
3052 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3053 },
3054 count: None,
3055 },
3056 wgpu::BindGroupLayoutEntry {
3058 binding: 2,
3059 visibility: wgpu::ShaderStages::FRAGMENT,
3060 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3061 count: None,
3062 },
3063 wgpu::BindGroupLayoutEntry {
3065 binding: 3,
3066 visibility: wgpu::ShaderStages::FRAGMENT,
3067 ty: wgpu::BindingType::Buffer {
3068 ty: wgpu::BufferBindingType::Uniform,
3069 has_dynamic_offset: false,
3070 min_binding_size: None,
3071 },
3072 count: None,
3073 },
3074 ],
3075 });
3076
3077 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3079 label: Some("clip pipeline layout"),
3080 bind_group_layouts: &[Some(&globals_layout)],
3081 immediate_size: 0,
3082 });
3083 let clip_vertex_layout = wgpu::VertexBufferLayout {
3084 array_stride: std::mem::size_of::<ClipInstance>() as u64,
3085 step_mode: wgpu::VertexStepMode::Instance,
3086 attributes: &[
3087 wgpu::VertexAttribute {
3088 shader_location: 0,
3089 offset: 0,
3090 format: wgpu::VertexFormat::Float32x4,
3091 },
3092 wgpu::VertexAttribute {
3093 shader_location: 1,
3094 offset: 16,
3095 format: wgpu::VertexFormat::Float32x4,
3096 },
3097 wgpu::VertexAttribute {
3098 shader_location: 2,
3099 offset: 32,
3100 format: wgpu::VertexFormat::Float32x4,
3101 },
3102 ],
3103 };
3104 let mesh_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3106 label: Some("mesh uniform layout"),
3107 entries: &[wgpu::BindGroupLayoutEntry {
3108 binding: 0,
3109 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
3110 ty: wgpu::BindingType::Buffer {
3111 ty: wgpu::BufferBindingType::Uniform,
3112 has_dynamic_offset: true,
3113 min_binding_size: NonZeroU64::new(mesh_uniform_size),
3114 },
3115 count: None,
3116 }],
3117 });
3118 let mesh_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
3119 label: Some("mesh uniform buffer"),
3120 size: mesh_uniform_cap,
3121 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3122 mapped_at_creation: false,
3123 });
3124 let mesh_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
3125 label: Some("mesh uniform bind"),
3126 layout: &mesh_bind_layout,
3127 entries: &[wgpu::BindGroupEntry {
3128 binding: 0,
3129 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3130 buffer: &mesh_uniform_buf,
3131 offset: 0,
3132 size: NonZeroU64::new(mesh_uniform_size),
3133 }),
3134 }],
3135 });
3136
3137 let surface_pipes = Pipelines::create(
3140 &device,
3141 output_format,
3142 msaa_samples,
3143 &globals_layout,
3144 &text_bind_layout,
3145 &image_bind_layout_nv12,
3146 &clip_pipeline_layout,
3147 &stencil_for_content,
3148 &stencil_for_clip_inc,
3149 &stencil_for_clip_dec,
3150 &clip_vertex_layout,
3151 &mesh_bind_layout,
3152 );
3153 let working_space_pipes = Pipelines::create(
3154 &device,
3155 wgpu::TextureFormat::Rgba16Float,
3156 working_space_msaa_samples,
3157 &globals_layout,
3158 &text_bind_layout,
3159 &image_bind_layout_nv12,
3160 &clip_pipeline_layout,
3161 &stencil_for_content,
3162 &stencil_for_clip_inc,
3163 &stencil_for_clip_dec,
3164 &clip_vertex_layout,
3165 &mesh_bind_layout,
3166 );
3167 let layer_pipes = if msaa_samples == 1 {
3168 surface_pipes.clone()
3169 } else {
3170 Pipelines::create(
3171 &device,
3172 output_format,
3173 1,
3174 &globals_layout,
3175 &text_bind_layout,
3176 &image_bind_layout_nv12,
3177 &clip_pipeline_layout,
3178 &stencil_for_content,
3179 &stencil_for_clip_inc,
3180 &stencil_for_clip_dec,
3181 &clip_vertex_layout,
3182 &mesh_bind_layout,
3183 )
3184 };
3185 let working_space_layer_pipes = if working_space_msaa_samples == 1 {
3186 working_space_pipes.clone()
3187 } else {
3188 Pipelines::create(
3189 &device,
3190 wgpu::TextureFormat::Rgba16Float,
3191 1,
3192 &globals_layout,
3193 &text_bind_layout,
3194 &image_bind_layout_nv12,
3195 &clip_pipeline_layout,
3196 &stencil_for_content,
3197 &stencil_for_clip_inc,
3198 &stencil_for_clip_dec,
3199 &clip_vertex_layout,
3200 &mesh_bind_layout,
3201 )
3202 };
3203
3204 let slug_enabled = true;
3206
3207 let blur_ring = UploadRing::new(
3209 &device,
3210 "blur ring",
3211 1024 * 1024,
3212 wgpu::BufferUsages::VERTEX
3213 | wgpu::BufferUsages::COPY_DST
3214 | wgpu::BufferUsages::COPY_SRC,
3215 );
3216
3217 let atlas_mask = init_atlas_mask(&device);
3219 let atlas_color = init_atlas_color(&device);
3220 let atlas_mask_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
3221 label: Some("atlas mask bind"),
3222 layout: &text_bind_layout,
3223 entries: &[
3224 wgpu::BindGroupEntry {
3225 binding: 0,
3226 resource: wgpu::BindingResource::TextureView(&atlas_mask.view),
3227 },
3228 wgpu::BindGroupEntry {
3229 binding: 1,
3230 resource: wgpu::BindingResource::Sampler(&atlas_mask.sampler),
3231 },
3232 ],
3233 });
3234 let atlas_color_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
3235 label: Some("atlas color bind"),
3236 layout: &text_bind_layout,
3237 entries: &[
3238 wgpu::BindGroupEntry {
3239 binding: 0,
3240 resource: wgpu::BindingResource::TextureView(&atlas_color.view),
3241 },
3242 wgpu::BindGroupEntry {
3243 binding: 1,
3244 resource: wgpu::BindingResource::Sampler(&atlas_color.sampler),
3245 },
3246 ],
3247 });
3248
3249 let ring_rect = UploadRing::new(
3251 &device,
3252 "ring rect",
3253 1 << 20,
3254 wgpu::BufferUsages::VERTEX
3255 | wgpu::BufferUsages::COPY_DST
3256 | wgpu::BufferUsages::COPY_SRC,
3257 );
3258 let ring_border = UploadRing::new(
3259 &device,
3260 "ring border",
3261 1 << 20,
3262 wgpu::BufferUsages::VERTEX
3263 | wgpu::BufferUsages::COPY_DST
3264 | wgpu::BufferUsages::COPY_SRC,
3265 );
3266 let ring_ellipse = UploadRing::new(
3267 &device,
3268 "ring ellipse",
3269 1 << 20,
3270 wgpu::BufferUsages::VERTEX
3271 | wgpu::BufferUsages::COPY_DST
3272 | wgpu::BufferUsages::COPY_SRC,
3273 );
3274 let ring_ellipse_border = UploadRing::new(
3275 &device,
3276 "ring ellipse border",
3277 1 << 20,
3278 wgpu::BufferUsages::VERTEX
3279 | wgpu::BufferUsages::COPY_DST
3280 | wgpu::BufferUsages::COPY_SRC,
3281 );
3282 let ring_arc = UploadRing::new(
3283 &device,
3284 "ring arc",
3285 1 << 20,
3286 wgpu::BufferUsages::VERTEX
3287 | wgpu::BufferUsages::COPY_DST
3288 | wgpu::BufferUsages::COPY_SRC,
3289 );
3290 let ring_glyph_mask = UploadRing::new(
3291 &device,
3292 "ring glyph mask",
3293 1 << 20,
3294 wgpu::BufferUsages::VERTEX
3295 | wgpu::BufferUsages::COPY_DST
3296 | wgpu::BufferUsages::COPY_SRC,
3297 );
3298 let ring_glyph_color = UploadRing::new(
3299 &device,
3300 "ring glyph color",
3301 1 << 20,
3302 wgpu::BufferUsages::VERTEX
3303 | wgpu::BufferUsages::COPY_DST
3304 | wgpu::BufferUsages::COPY_SRC,
3305 );
3306 let ring_slug = UploadRing::new(
3307 &device,
3308 "ring slug",
3309 1 << 22,
3310 wgpu::BufferUsages::VERTEX
3311 | wgpu::BufferUsages::COPY_DST
3312 | wgpu::BufferUsages::COPY_SRC,
3313 );
3314 let ring_clip = UploadRing::new(
3315 &device,
3316 "ring clip",
3317 1 << 16,
3318 wgpu::BufferUsages::VERTEX
3319 | wgpu::BufferUsages::COPY_DST
3320 | wgpu::BufferUsages::COPY_SRC,
3321 );
3322 let blend_ring = UploadRing::new(
3323 &device,
3324 "ring blend",
3325 1 << 16,
3326 wgpu::BufferUsages::VERTEX
3327 | wgpu::BufferUsages::COPY_DST
3328 | wgpu::BufferUsages::COPY_SRC,
3329 );
3330 let ring_projective = UploadRing::new(
3331 &device,
3332 "ring projective",
3333 1 << 16,
3334 wgpu::BufferUsages::VERTEX
3335 | wgpu::BufferUsages::COPY_DST
3336 | wgpu::BufferUsages::COPY_SRC,
3337 );
3338 let ring_nv12 = UploadRing::new(
3339 &device,
3340 "ring nv12",
3341 1 << 20,
3342 wgpu::BufferUsages::VERTEX
3343 | wgpu::BufferUsages::COPY_DST
3344 | wgpu::BufferUsages::COPY_SRC,
3345 );
3346 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
3348 label: Some("temp ds"),
3349 size: wgpu::Extent3d {
3350 width: 1,
3351 height: 1,
3352 depth_or_array_layers: 1,
3353 },
3354 mip_level_count: 1,
3355 sample_count: 1,
3356 dimension: wgpu::TextureDimension::D2,
3357 format: wgpu::TextureFormat::Depth24PlusStencil8,
3358 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3359 view_formats: &[],
3360 });
3361 let depth_stencil_view =
3362 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
3363
3364 let mut renderer = WgpuSceneRenderer {
3365 device,
3366 queue,
3367 output_format,
3368 surface_format: output_format,
3369 output_width: 0,
3370 output_height: 0,
3371 pixels_per_point: 1.0,
3372
3373 surface_pipes,
3374 working_space_pipes,
3375 layer_pipes,
3376 working_space_layer_pipes,
3377
3378 rects: InstancedPipe::new(ring_rect),
3379 borders: InstancedPipe::new(ring_border),
3380 ellipses: InstancedPipe::new(ring_ellipse),
3381 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
3382 arcs: InstancedPipe::new(ring_arc),
3383 glyph_mask: InstancedPipe::new(ring_glyph_mask),
3384 glyph_color: InstancedPipe::new(ring_glyph_color),
3385
3386 text_bind_layout,
3387
3388 image_bind_layout_rgba,
3389 image_bind_layout_nv12,
3390 image_sampler,
3391 image_sampler_nearest,
3392 image_sampler_repeat,
3393 image_sampler_repeat_nearest,
3394 layer_sampler,
3395 layer_sampler_linear,
3396
3397 blur_ring,
3398
3399 slug_enabled,
3400 slug_ring: ring_slug,
3401 slug_cache: slug::GlyphSlugCache::new(),
3402 slug_draw_cache: HashMap::new(),
3403 slug_draw_cache_bytes_total: 0,
3404
3405 clip_ring: ring_clip,
3406
3407 nv12: InstancedPipe::new(ring_nv12),
3408
3409 mesh_cache: HashMap::new(),
3410 mesh_cache_bytes_total: 0,
3411 mesh_uniform_buf,
3412 mesh_bind,
3413 mesh_uniform_head: 0,
3414 mesh_uniform_alignment,
3415 mesh_uniform_cap,
3416
3417 projective_ring: ring_projective,
3418 blend_ring,
3419 flatten_layer_ids: Vec::new(),
3420 blur_scratch_ids: HashMap::new(),
3421 blur_scratch_next: 0,
3422 blend_snapshots: std::collections::HashMap::new(),
3423 blend_snapshot_pool: HashMap::new(),
3424 blend_copies: Vec::new(),
3425
3426 msaa_samples,
3427 working_space_msaa_samples,
3428 depth_stencil_tex,
3429 depth_stencil_view,
3430 msaa_tex: None,
3431 msaa_view: None,
3432 ws_msaa_tex: None,
3433 ws_msaa_view: None,
3434 surface_resolve_tex: None,
3435 surface_resolve_view: None,
3436 surface_resolve_bytes: 0,
3437 msaa_bytes: 0,
3438 ws_msaa_bytes: 0,
3439 depth_stencil_bytes: 0,
3440 working_space_bytes: 0,
3441 gpu_budget_bytes: MAX_GPU_RESOURCE_BYTES,
3442 globals_bind,
3443 globals_buf,
3444 globals_staging,
3445 globals_staging_cap,
3446
3447 atlas_mask,
3448 atlas_color,
3449 atlas_mask_bind,
3450 atlas_color_bind,
3451 atlas_generation: repose_text::font_generation(),
3452 atlas_mask_pending: AtlasPending::default(),
3453 atlas_color_pending: AtlasPending::default(),
3454 atlas_last_compact_frame: 0,
3455 atlas_mask_failures: HashSet::new(),
3456 atlas_color_failures: HashSet::new(),
3457 atlas_mask_deferred: HashSet::new(),
3458 atlas_color_deferred: HashSet::new(),
3459 glyph_outline_cache: HashMap::new(),
3460 legacy_shaped_text_cache: HashMap::new(),
3461
3462 next_image_handle: 1,
3463 images: HashMap::new(),
3464 retained: HashMap::new(),
3465 retained_bytes_total: 0,
3466
3467 next_coverage_handle: 1,
3468 coverages: HashMap::new(),
3469
3470 frame_index: 0,
3471 cache_touch_clock: 0,
3472 image_bytes_total: 0,
3473 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
3476 producer_layer_ids: HashSet::new(),
3477 layer_bytes_total: 0,
3478 transient_layer_bytes_total: 0,
3479
3480 working_space: false,
3481 ws_tex: None,
3482 ws_view: None,
3483 ws_bind: None,
3484 display_pipeline: None,
3485 display_layout: None,
3486
3487 callback_resources: CallbackResources::default(),
3488 callback_scoped_resources: HashMap::new(),
3489 callback_scope_uses: HashMap::new(),
3490 callback_scope_payloads: HashMap::new(),
3491 callback_scope_clock: 0,
3492 blend_snapshot_bytes_total: 0,
3493 frame_active: false,
3494 last_render_error: None,
3495 };
3496
3497 renderer.recreate_msaa_and_depth_stencil();
3498 Ok(renderer)
3499 }
3500}
3501
3502#[cfg(not(target_arch = "wasm32"))]
3506fn backend_by_name(name: &str) -> Option<wgpu::Backend> {
3507 match name {
3508 "gl" | "opengl" | "opengles" | "webgl" => Some(wgpu::Backend::Gl),
3509 "webgpu" | "browser-webgpu" => Some(wgpu::Backend::BrowserWebGpu),
3510 "metal" => Some(wgpu::Backend::Metal),
3511 "vulkan" => Some(wgpu::Backend::Vulkan),
3512 "dx12" | "d3d12" => Some(wgpu::Backend::Dx12),
3513 _ => None,
3514 }
3515}
3516
3517#[cfg(not(target_arch = "wasm32"))]
3521pub fn parse_backends(spec: &str) -> Option<wgpu::Backends> {
3522 let mut bits = wgpu::Backends::empty();
3523 for name in spec.split([',', ' ', '+']).filter(|n| !n.is_empty()) {
3524 match backend_by_name(&name.to_ascii_lowercase()) {
3525 Some(backend) => bits |= wgpu::Backends::from(backend),
3526 None => log::warn!(
3527 "REPOSE_WGPU_BACKENDS: ignoring unknown backend {name:?}; \
3528 expected one of noop, vulkan, metal, dx12, gl, webgpu"
3529 ),
3530 }
3531 }
3532 (!bits.is_empty()).then_some(bits)
3533}
3534
3535#[cfg(feature = "winit-surface")]
3536pub(crate) async fn surface_instance(
3537 display: Option<winit::event_loop::OwnedDisplayHandle>,
3538) -> (Instance, wgpu::Backends) {
3539 let mut desc = match display {
3544 Some(handle) => wgpu::InstanceDescriptor::new_with_display_handle(Box::new(handle)),
3545 None => wgpu::InstanceDescriptor::new_without_display_handle(),
3546 };
3547 if cfg!(target_arch = "wasm32") {
3548 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
3549 }
3550 #[cfg(not(target_arch = "wasm32"))]
3551 if let Ok(spec) = std::env::var("REPOSE_WGPU_BACKENDS")
3552 && let Some(bits) = parse_backends(&spec)
3553 {
3554 log::info!("REPOSE_WGPU_BACKENDS={spec} -> {bits:?}");
3555 desc.backends = bits;
3556 }
3557
3558 let backends = desc.backends;
3559 let instance = if cfg!(target_arch = "wasm32") {
3560 wgpu::util::new_instance_with_webgpu_detection(desc).await
3561 } else {
3562 wgpu::Instance::new(desc)
3563 };
3564 (instance, backends)
3565}
3566
3567impl WgpuSurfaceBackend {
3568 #[cfg(feature = "winit-surface")]
3569 pub async fn new_async(
3570 window: Arc<winit::window::Window>,
3571 ) -> anyhow::Result<WgpuSurfaceBackend> {
3572 Self::new_async_with_options(window, None, 4, PresentModePref::Auto).await
3573 }
3574
3575 #[cfg(feature = "winit-surface")]
3578 pub async fn new_async_with_msaa(
3579 window: Arc<winit::window::Window>,
3580 msaa_samples: u32,
3581 ) -> anyhow::Result<WgpuSurfaceBackend> {
3582 Self::new_async_with_options(window, None, msaa_samples, PresentModePref::Auto).await
3583 }
3584
3585 #[cfg(feature = "winit-surface")]
3592 pub async fn new_async_with_options(
3593 window: Arc<winit::window::Window>,
3594 display: Option<winit::event_loop::OwnedDisplayHandle>,
3595 msaa_samples: u32,
3596 present_mode: PresentModePref,
3597 ) -> anyhow::Result<WgpuSurfaceBackend> {
3598 let (instance, backends) = surface_instance(display).await;
3599
3600 let surface = instance.create_surface(window.clone())?;
3601
3602 let adapter = instance
3603 .request_adapter(&wgpu::RequestAdapterOptions {
3604 power_preference: wgpu::PowerPreference::HighPerformance,
3605 compatible_surface: Some(&surface),
3606 force_fallback_adapter: false,
3607 apply_limit_buckets: false,
3608 })
3609 .await
3610 .map_err(|e| {
3611 anyhow::anyhow!("no adapter for this window (enabled backends {backends:?}): {e:?}")
3612 })?;
3613 log::info!(
3614 "wgpu adapter: {} backend={:?} driver={:?} type={:?}",
3615 adapter.get_info().name,
3616 adapter.get_info().backend,
3617 adapter.get_info().driver,
3618 adapter.get_info().device_type,
3619 );
3620
3621 let limits = adapter.limits();
3622
3623 let features = {
3624 let available = adapter.features();
3625 let mut features = wgpu::Features::empty();
3626 if available.contains(wgpu::Features::TEXTURE_FORMAT_16BIT_NORM) {
3627 features |= wgpu::Features::TEXTURE_FORMAT_16BIT_NORM;
3628 }
3629 #[cfg(target_os = "linux")]
3630 {
3631 if available.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD) {
3632 features |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD;
3633 }
3634 if available.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF) {
3635 features |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF;
3636 }
3637 }
3638 features
3639 };
3640
3641 let (device, queue) = adapter
3642 .request_device(&wgpu::DeviceDescriptor {
3643 label: Some("repose-rs device"),
3644 required_features: features,
3645 required_limits: limits,
3646 experimental_features: wgpu::ExperimentalFeatures::disabled(),
3647 memory_hints: wgpu::MemoryHints::default(),
3648 trace: wgpu::Trace::Off,
3649 })
3650 .await
3651 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
3652
3653 let device_lost = Arc::new(Mutex::new(None));
3654 {
3655 let device_lost = device_lost.clone();
3656 device.set_device_lost_callback(move |reason, message| {
3657 log::error!("wgpu device lost ({reason:?}): {message}");
3658 if let Ok(mut slot) = device_lost.lock() {
3659 *slot = Some((reason, message));
3660 }
3661 });
3662 }
3663
3664 let size = window.inner_size();
3665
3666 let caps = surface.get_capabilities(&adapter);
3667
3668 let Some(format) = caps.formats.first().copied() else {
3669 anyhow::bail!(
3670 "surface exposes no presentable formats (adapter {})",
3671 adapter.get_info().name
3672 );
3673 };
3674
3675 let (format, view_format) = if cfg!(target_arch = "wasm32")
3676 && adapter
3677 .get_downlevel_capabilities()
3678 .flags
3679 .contains(wgpu::DownlevelFlags::SURFACE_VIEW_FORMATS)
3680 {
3681 let non_srgb = caps
3682 .formats
3683 .iter()
3684 .copied()
3685 .find(|f| !f.is_srgb())
3686 .unwrap_or(format);
3687 (non_srgb, Some(non_srgb.add_srgb_suffix()))
3688 } else if cfg!(target_arch = "wasm32") {
3689 let fmt = caps
3690 .formats
3691 .iter()
3692 .copied()
3693 .find(|f| f.is_srgb())
3694 .unwrap_or(format);
3695 (fmt, None)
3696 } else {
3697 let fmt = caps
3698 .formats
3699 .iter()
3700 .copied()
3701 .find(|f| f.is_srgb())
3702 .unwrap_or(format);
3703 (fmt, None)
3704 };
3705
3706 let present_mode = pick_present_mode(&caps, present_mode);
3707 let alpha_mode = *caps
3708 .alpha_modes
3709 .first()
3710 .ok_or_else(|| anyhow::anyhow!("surface exposes no alpha modes"))?;
3711
3712 let render_format = view_format.unwrap_or(format);
3713 let msaa_samples = pick_surface_msaa(&adapter, render_format, msaa_samples);
3714 let mut renderer = WgpuSceneRenderer::try_from_device_with_adapter(
3715 &adapter,
3716 device,
3717 queue,
3718 render_format,
3719 msaa_samples,
3720 )?
3721 .with_surface_format(format)?;
3722 let (fw, fh) = clamp_output_size(
3723 size.width,
3724 size.height,
3725 renderer.device.limits().max_texture_dimension_2d,
3726 );
3727 renderer.resize(fw, fh);
3728
3729 let view_formats = view_format.into_iter().collect::<Vec<_>>();
3730
3731 let wanted_usage = wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC;
3739 let surface_usage = wanted_usage & caps.usages;
3740 if surface_usage != wanted_usage {
3741 log::warn!(
3742 "surface {format:?} cannot be used as {:?}; swapchain-sourced blend snapshots \
3743 are skipped on this backend",
3744 wanted_usage - surface_usage
3745 );
3746 }
3747 let config = wgpu::SurfaceConfiguration {
3748 usage: surface_usage,
3749 format,
3750 width: fw.max(1),
3751 height: fh.max(1),
3752 present_mode,
3753 alpha_mode,
3754 color_space: wgpu::SurfaceColorSpace::Auto,
3755 view_formats,
3756 desired_maximum_frame_latency: 1,
3757 };
3758 surface.configure(&renderer.device, &config);
3759
3760 Ok(WgpuSurfaceBackend {
3761 #[cfg(feature = "winit-surface")]
3762 instance: Some(instance),
3763 #[cfg(feature = "winit-surface")]
3764 window: Some(window),
3765 surface: Some(surface),
3766 surface_config: Some(config),
3767 pending_reconfigure: false,
3768 renderer,
3769 device_lost,
3770 })
3771 }
3772
3773 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
3774 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
3775 pollster::block_on(Self::new_async(window))
3776 }
3777
3778 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
3779 pub fn new_with_msaa(
3780 window: Arc<winit::window::Window>,
3781 msaa_samples: u32,
3782 ) -> anyhow::Result<WgpuSurfaceBackend> {
3783 pollster::block_on(Self::new_async_with_msaa(window, msaa_samples))
3784 }
3785
3786 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
3787 pub fn new_with_options(
3788 window: Arc<winit::window::Window>,
3789 display: Option<winit::event_loop::OwnedDisplayHandle>,
3790 msaa_samples: u32,
3791 present_mode: PresentModePref,
3792 ) -> anyhow::Result<WgpuSurfaceBackend> {
3793 pollster::block_on(Self::new_async_with_options(
3794 window,
3795 display,
3796 msaa_samples,
3797 present_mode,
3798 ))
3799 }
3800
3801 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
3802 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
3803 anyhow::bail!("Use WgpuSurfaceBackend::new_async(window).await on wasm32")
3804 }
3805
3806 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
3807 pub fn new_with_msaa(
3808 _window: Arc<winit::window::Window>,
3809 _msaa_samples: u32,
3810 ) -> anyhow::Result<WgpuSurfaceBackend> {
3811 anyhow::bail!("Use WgpuSurfaceBackend::new_async_with_msaa(window, msaa).await on wasm32")
3812 }
3813
3814 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
3815 pub fn new_with_options(
3816 _window: Arc<winit::window::Window>,
3817 _display: Option<winit::event_loop::OwnedDisplayHandle>,
3818 _msaa_samples: u32,
3819 _present_mode: PresentModePref,
3820 ) -> anyhow::Result<WgpuSurfaceBackend> {
3821 anyhow::bail!(
3822 "Use WgpuSurfaceBackend::new_async_with_options(window, display, msaa, mode).await on wasm32"
3823 )
3824 }
3825
3826 pub fn take_device_lost(&self) -> Option<String> {
3831 let (reason, message) = self.device_lost.lock().ok()?.take()?;
3832 (reason != wgpu::DeviceLostReason::Destroyed).then_some(message)
3833 }
3834}
3835
3836#[cfg(feature = "winit-surface")]
3839fn pick_present_mode(caps: &wgpu::SurfaceCapabilities, pref: PresentModePref) -> wgpu::PresentMode {
3840 let auto = || {
3841 caps.present_modes
3842 .iter()
3843 .copied()
3844 .find(|m| *m == wgpu::PresentMode::Fifo)
3845 .or_else(|| {
3846 caps.present_modes
3847 .iter()
3848 .copied()
3849 .find(|m| *m == wgpu::PresentMode::Mailbox)
3850 })
3851 .unwrap_or(wgpu::PresentMode::Immediate)
3852 };
3853 match pref {
3854 PresentModePref::Auto => auto(),
3855 PresentModePref::Fifo if caps.present_modes.contains(&wgpu::PresentMode::Fifo) => {
3856 wgpu::PresentMode::Fifo
3857 }
3858 PresentModePref::Mailbox if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) => {
3859 wgpu::PresentMode::Mailbox
3860 }
3861 PresentModePref::Immediate
3862 if caps.present_modes.contains(&wgpu::PresentMode::Immediate) =>
3863 {
3864 wgpu::PresentMode::Immediate
3865 }
3866 _ => auto(),
3867 }
3868}
3869
3870const VALID_SAMPLE_COUNTS: [u32; 5] = [1, 2, 4, 8, 16];
3873
3874fn validate_sample_count(label: &str, samples: u32) -> anyhow::Result<()> {
3875 if !VALID_SAMPLE_COUNTS.contains(&samples) {
3876 anyhow::bail!(
3877 "{label} must be one of {VALID_SAMPLE_COUNTS:?} (wgpu-supported sample counts), got {samples}"
3878 );
3879 }
3880 Ok(())
3881}
3882
3883fn validate_render_target_format(
3889 adapter: &wgpu::Adapter,
3890 output_format: wgpu::TextureFormat,
3891 samples: u32,
3892) -> anyhow::Result<()> {
3893 let features = adapter.get_texture_format_features(output_format);
3894 let flags = features.flags;
3895 for (needed, what) in [
3896 (wgpu::TextureUsages::RENDER_ATTACHMENT, "render attachment"),
3897 (wgpu::TextureUsages::TEXTURE_BINDING, "texture binding"),
3898 ] {
3899 if !features.allowed_usages.contains(needed) {
3900 anyhow::bail!("{output_format:?} cannot be used as a {what}");
3901 }
3902 }
3903 if !flags.contains(wgpu::TextureFormatFeatureFlags::BLENDABLE) {
3904 anyhow::bail!("{output_format:?} cannot be blended into");
3905 }
3906 if samples > 1 && !flags.contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE) {
3907 anyhow::bail!(
3908 "{output_format:?} cannot resolve the requested {samples}x multisampled attachment"
3909 );
3910 }
3911 let ws = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba16Float);
3913 if !ws
3914 .allowed_usages
3915 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
3916 || !ws
3917 .allowed_usages
3918 .contains(wgpu::TextureUsages::TEXTURE_BINDING)
3919 {
3920 anyhow::bail!(
3921 "{:?} cannot be used for linear working-space rendering",
3922 wgpu::TextureFormat::Rgba16Float
3923 );
3924 }
3925 Ok(())
3926}
3927
3928pub fn pick_surface_msaa(
3931 adapter: &wgpu::Adapter,
3932 format: wgpu::TextureFormat,
3933 requested: u32,
3934) -> u32 {
3935 pick_surface_msaa_for_mode(adapter, format, requested, false)
3936}
3937
3938pub fn pick_surface_msaa_for_mode(
3939 adapter: &wgpu::Adapter,
3940 format: wgpu::TextureFormat,
3941 requested: u32,
3942 working_space: bool,
3943) -> u32 {
3944 let requested = requested.max(1);
3945 let color_feat = adapter.get_texture_format_features(format);
3946 let working_space_feat = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba16Float);
3947 let depth_feat = adapter.get_texture_format_features(wgpu::TextureFormat::Depth24PlusStencil8);
3948 let supported = |n: u32| {
3949 color_feat
3950 .allowed_usages
3951 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
3952 && color_feat.flags.sample_count_supported(n)
3953 && (!working_space
3954 || (working_space_feat
3955 .allowed_usages
3956 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
3957 && working_space_feat.flags.sample_count_supported(n)
3958 && (n == 1
3959 || working_space_feat
3960 .flags
3961 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE))))
3962 && (n == 1
3963 || color_feat
3964 .flags
3965 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE))
3966 && depth_feat
3967 .allowed_usages
3968 .contains(wgpu::TextureUsages::RENDER_ATTACHMENT)
3969 && depth_feat.flags.sample_count_supported(n)
3970 };
3971 let mut candidates = vec![requested];
3972 for n in [8, 4, 2, 1] {
3973 if n < requested {
3974 candidates.push(n);
3975 }
3976 }
3977 let chosen = candidates.into_iter().find(|&n| supported(n)).unwrap_or(1);
3978 if chosen != requested {
3979 log::info!("requested MSAA x{requested}, using x{chosen}");
3980 }
3981 chosen
3982}
3983
3984impl WgpuSceneRenderer {
3985 pub fn set_image_from_bytes(
3988 &mut self,
3989 handle: u64,
3990 data: &[u8],
3991 srgb: bool,
3992 ) -> anyhow::Result<()> {
3993 validate_image_handle(handle)?;
3994 let max_dimension = self.device.limits().max_texture_dimension_2d;
3995 let (w, h, rgba) = decode_image_rgba8(data, max_dimension)?;
3996 self.set_image_rgba8(handle, w, h, &rgba, srgb)?;
3997 repose_core::set_image_intrinsic_size(handle, w, h);
4000 Ok(())
4001 }
4002
4003 pub fn set_image_rgba8(
4004 &mut self,
4005 handle: u64,
4006 w: u32,
4007 h: u32,
4008 rgba: &[u8],
4009 srgb: bool,
4010 ) -> anyhow::Result<()> {
4011 validate_image_handle(handle)?;
4012 validate_texture_dimensions(&self.device, w, h)?;
4013 let expected = checked_image_bytes_usize(w, h, 4)?;
4014 if rgba.len() < expected {
4015 return Err(anyhow::anyhow!(
4016 "RGBA buffer too small: {} < {}",
4017 rgba.len(),
4018 expected
4019 ));
4020 }
4021
4022 let format = if srgb {
4023 wgpu::TextureFormat::Rgba8UnormSrgb
4024 } else {
4025 wgpu::TextureFormat::Rgba8Unorm
4026 };
4027
4028 let needs_recreate = match self.images.get(&handle) {
4029 Some(ImageTex::Rgba {
4030 w: cw,
4031 h: ch,
4032 format: cf,
4033 ..
4034 }) => *cw != w || *ch != h || *cf != format,
4035 _ => true,
4036 };
4037
4038 if needs_recreate {
4039 self.remove_image(handle);
4040
4041 let (tex, binds) = self.create_rgba_tex(w, h, format);
4042 let bytes = checked_image_bytes(w, h, 4)?;
4043 self.image_bytes_total = self.image_bytes_total.saturating_add(bytes);
4044
4045 self.images.insert(
4046 handle,
4047 ImageTex::Rgba {
4048 tex,
4049 binds,
4050 w,
4051 h,
4052 format,
4053 last_used_frame: self.frame_index,
4054 bytes,
4055 },
4056 );
4057 }
4058
4059 if let Some(old) = self.retained.remove(&handle) {
4060 self.retained_bytes_total = self
4061 .retained_bytes_total
4062 .saturating_sub(old.rgba.len() as u64);
4063 }
4064 self.retained_bytes_total = self.retained_bytes_total.saturating_add(expected as u64);
4065 self.retained.insert(
4066 handle,
4067 RetainedImage {
4068 w,
4069 h,
4070 format,
4071 rgba: rgba[..expected].to_vec(),
4072 last_used_frame: self.frame_index,
4073 },
4074 );
4075 self.evict_retained_excess();
4076
4077 let tex = match self.images.get(&handle) {
4078 Some(ImageTex::Rgba { tex, .. }) => tex,
4079 _ => unreachable!(),
4080 };
4081
4082 self.queue.write_texture(
4083 wgpu::TexelCopyTextureInfo {
4084 texture: tex,
4085 mip_level: 0,
4086 origin: wgpu::Origin3d::ZERO,
4087 aspect: wgpu::TextureAspect::All,
4088 },
4089 &rgba[..expected],
4090 wgpu::TexelCopyBufferLayout {
4091 offset: 0,
4092 bytes_per_row: Some(4 * w),
4093 rows_per_image: Some(h),
4094 },
4095 wgpu::Extent3d {
4096 width: w,
4097 height: h,
4098 depth_or_array_layers: 1,
4099 },
4100 );
4101
4102 self.evict_budget_excess();
4104
4105 Ok(())
4106 }
4107
4108 fn create_image_binds(
4111 &self,
4112 view: &wgpu::TextureView,
4113 label: &str,
4114 sampler: Option<&wgpu::Sampler>,
4115 ) -> ImageBinds {
4116 let make = |name: &str, sampler: &wgpu::Sampler| {
4117 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4118 label: Some(name),
4119 layout: &self.image_bind_layout_rgba,
4120 entries: &[
4121 wgpu::BindGroupEntry {
4122 binding: 0,
4123 resource: wgpu::BindingResource::TextureView(view),
4124 },
4125 wgpu::BindGroupEntry {
4126 binding: 1,
4127 resource: wgpu::BindingResource::Sampler(sampler),
4128 },
4129 ],
4130 })
4131 };
4132 match sampler {
4133 Some(sampler) => {
4134 let bind = make(label, sampler);
4135 ImageBinds {
4136 linear: make(label, sampler),
4137 nearest: bind.clone(),
4138 tile_linear: bind.clone(),
4139 tile_nearest: bind,
4140 }
4141 }
4142 None => ImageBinds {
4143 linear: make(label, &self.image_sampler),
4144 nearest: make(label, &self.image_sampler_nearest),
4145 tile_linear: make(label, &self.image_sampler_repeat),
4146 tile_nearest: make(label, &self.image_sampler_repeat_nearest),
4147 },
4148 }
4149 }
4150
4151 fn create_nv12_binds(
4152 &self,
4153 view_y: &wgpu::TextureView,
4154 view_uv: &wgpu::TextureView,
4155 yuv_buf: &wgpu::Buffer,
4156 label: &str,
4157 ) -> ImageBinds {
4158 let make = |name: &str, sampler: &wgpu::Sampler| {
4159 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4160 label: Some(name),
4161 layout: &self.image_bind_layout_nv12,
4162 entries: &[
4163 wgpu::BindGroupEntry {
4164 binding: 0,
4165 resource: wgpu::BindingResource::TextureView(view_y),
4166 },
4167 wgpu::BindGroupEntry {
4168 binding: 1,
4169 resource: wgpu::BindingResource::TextureView(view_uv),
4170 },
4171 wgpu::BindGroupEntry {
4172 binding: 2,
4173 resource: wgpu::BindingResource::Sampler(sampler),
4174 },
4175 wgpu::BindGroupEntry {
4176 binding: 3,
4177 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
4178 buffer: yuv_buf,
4179 offset: 0,
4180 size: None,
4181 }),
4182 },
4183 ],
4184 })
4185 };
4186 ImageBinds {
4187 linear: make(label, &self.image_sampler),
4188 nearest: make(label, &self.image_sampler_nearest),
4189 tile_linear: make(label, &self.image_sampler_repeat),
4190 tile_nearest: make(label, &self.image_sampler_repeat_nearest),
4191 }
4192 }
4193
4194 fn create_rgba_tex(
4195 &self,
4196 w: u32,
4197 h: u32,
4198 format: wgpu::TextureFormat,
4199 ) -> (wgpu::Texture, ImageBinds) {
4200 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
4201 label: Some("user image rgba"),
4202 size: wgpu::Extent3d {
4203 width: w,
4204 height: h,
4205 depth_or_array_layers: 1,
4206 },
4207 mip_level_count: 1,
4208 sample_count: 1,
4209 dimension: wgpu::TextureDimension::D2,
4210 format,
4211 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4212 view_formats: &[],
4213 });
4214 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4215 let binds = self.create_image_binds(&view, "image bind rgba", None);
4216 (tex, binds)
4217 }
4218
4219 fn validate_native_texture_view(
4221 &self,
4222 view: &wgpu::TextureView,
4223 width: u32,
4224 height: u32,
4225 ) -> anyhow::Result<()> {
4226 if width == 0 || height == 0 {
4227 anyhow::bail!("native texture dimensions must be non-zero");
4228 }
4229 let texture = view.texture();
4230 if texture.sample_count() != 1
4231 || texture.dimension() != wgpu::TextureDimension::D2
4232 || texture.depth_or_array_layers() != 1
4233 || texture.mip_level_count() == 0
4234 {
4235 anyhow::bail!("native texture must be a single-sample 2D texture with mip levels");
4236 }
4237 if width > self.device.limits().max_texture_dimension_2d
4238 || height > self.device.limits().max_texture_dimension_2d
4239 {
4240 anyhow::bail!("native texture dimensions exceed the device limit");
4241 }
4242 if !texture
4243 .usage()
4244 .contains(wgpu::TextureUsages::TEXTURE_BINDING)
4245 {
4246 anyhow::bail!("native texture lacks TEXTURE_BINDING usage");
4247 }
4248 if !native_format_supported(texture.format(), self.device.features()) {
4249 anyhow::bail!(
4250 "native texture format {:?} is not a filterable alpha color format",
4251 texture.format()
4252 );
4253 }
4254 if texture.width() < width || texture.height() < height {
4255 anyhow::bail!(
4256 "native texture is {}x{}, smaller than requested {width}x{height}",
4257 texture.width(),
4258 texture.height()
4259 );
4260 }
4261 Ok(())
4262 }
4263
4264 pub fn register_native_texture(
4265 &mut self,
4266 view: &wgpu::TextureView,
4267 width: u32,
4268 height: u32,
4269 ) -> u64 {
4270 if let Err(error) = self.validate_native_texture_view(view, width, height) {
4271 log::warn!("register_native_texture: {error:#}");
4272 return 0;
4273 }
4274 let handle = self.next_image_handle;
4275 self.next_image_handle += 1;
4276 let binds = self.create_image_binds(view, "user native image", None);
4277 self.images.insert(
4278 handle,
4279 ImageTex::User {
4280 binds,
4281 w: width,
4282 h: height,
4283 last_used_frame: self.frame_index,
4284 bytes: 0,
4285 },
4286 );
4287 handle
4288 }
4289
4290 pub fn register_native_texture_with_sampler(
4292 &mut self,
4293 view: &wgpu::TextureView,
4294 sampler_desc: wgpu::SamplerDescriptor<'_>,
4295 width: u32,
4296 height: u32,
4297 ) -> u64 {
4298 if let Err(error) = self.validate_native_texture_view(view, width, height) {
4299 log::warn!("register_native_texture: {error:#}");
4300 return 0;
4301 }
4302 if let Err(error) = validate_sampler_descriptor(&self.device, &sampler_desc) {
4303 log::warn!("register_native_texture: {error:#}");
4304 return 0;
4305 }
4306 let handle = self.next_image_handle;
4307 self.next_image_handle += 1;
4308 let sampler = self.device.create_sampler(&sampler_desc);
4309 let binds = self.create_image_binds(view, "user native image sampleropts", Some(&sampler));
4310 self.images.insert(
4311 handle,
4312 ImageTex::User {
4313 binds,
4314 w: width,
4315 h: height,
4316 last_used_frame: self.frame_index,
4317 bytes: 0,
4318 },
4319 );
4320 handle
4321 }
4322
4323 pub fn update_native_texture(&mut self, handle: u64, view: &wgpu::TextureView) {
4325 if handle == 0 {
4326 log::warn!("update_native_texture: reserved handle");
4327 return;
4328 }
4329 let Some((w, h)) = self.images.get(&handle).and_then(|entry| match entry {
4330 ImageTex::User { w, h, .. } => Some((*w, *h)),
4331 _ => None,
4332 }) else {
4333 log::warn!("update_native_texture: handle {handle} is not a native image");
4334 return;
4335 };
4336 if let Err(error) = self.validate_native_texture_view(view, w, h) {
4337 log::warn!("update_native_texture: {error:#}");
4338 return;
4339 }
4340 let binds = self.create_image_binds(view, "user native image update", None);
4341 if let Some(entry) = self.images.get_mut(&handle) {
4342 *entry = ImageTex::User {
4343 binds,
4344 w,
4345 h,
4346 last_used_frame: self.frame_index,
4347 bytes: 0,
4348 };
4349 }
4350 }
4351
4352 pub fn set_image_nv12(
4353 &mut self,
4354 handle: u64,
4355 w: u32,
4356 h: u32,
4357 y: &[u8],
4358 uv: &[u8],
4359 color_info: ColorInfo,
4360 ) -> anyhow::Result<()> {
4361 validate_image_handle(handle)?;
4362 validate_texture_dimensions(&self.device, w, h)?;
4363 if self
4364 .images
4365 .get(&handle)
4366 .is_some_and(|image| matches!(image, ImageTex::Nv12 { external: true, .. }))
4367 {
4368 anyhow::bail!("cannot overwrite an external DMA-BUF image");
4369 }
4370 let y_expected = checked_image_bytes_usize(w, h, 1)?;
4371 let uv_w = w.div_ceil(2);
4372 let uv_h = h.div_ceil(2);
4373 let uv_expected = checked_image_bytes_usize(uv_w, uv_h, 2)?;
4374
4375 if y.len() < y_expected {
4376 return Err(anyhow::anyhow!("Y plane too small"));
4377 }
4378 if uv.len() < uv_expected {
4379 return Err(anyhow::anyhow!("UV plane too small"));
4380 }
4381
4382 let needs_recreate = match self.images.get(&handle) {
4383 Some(ImageTex::Nv12 {
4384 w: ww,
4385 h: hh,
4386 pixel_format,
4387 ..
4388 }) => *ww != w || *hh != h || *pixel_format != PixelFormat::Nv12,
4389 _ => true,
4390 };
4391
4392 let yuv_raw = make_yuv_transform_raw(color_info, PixelFormat::Nv12);
4394
4395 if needs_recreate {
4396 self.remove_image(handle);
4397
4398 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
4399 label: Some("nv12 Y"),
4400 size: wgpu::Extent3d {
4401 width: w,
4402 height: h,
4403 depth_or_array_layers: 1,
4404 },
4405 mip_level_count: 1,
4406 sample_count: 1,
4407 dimension: wgpu::TextureDimension::D2,
4408 format: wgpu::TextureFormat::R8Unorm,
4409 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4410 view_formats: &[],
4411 });
4412 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
4413
4414 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
4415 label: Some("nv12 UV"),
4416 size: wgpu::Extent3d {
4417 width: uv_w,
4418 height: uv_h,
4419 depth_or_array_layers: 1,
4420 },
4421 mip_level_count: 1,
4422 sample_count: 1,
4423 dimension: wgpu::TextureDimension::D2,
4424 format: wgpu::TextureFormat::Rg8Unorm,
4425 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4426 view_formats: &[],
4427 });
4428 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
4429
4430 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4432 label: Some("nv12 yuv transform"),
4433 size: std::mem::size_of::<YuvTransformRaw>() as u64,
4434 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4435 mapped_at_creation: false,
4436 });
4437
4438 self.queue
4440 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
4441
4442 let binds = self.create_nv12_binds(&view_y, &view_uv, &yuv_buf, "nv12 bind");
4443
4444 let bytes = checked_image_bytes(w, h, 1)?
4445 .checked_add(checked_image_bytes(uv_w, uv_h, 2)?)
4446 .and_then(|bytes| bytes.checked_add(std::mem::size_of::<YuvTransformRaw>() as u64))
4447 .ok_or_else(|| anyhow::anyhow!("NV12 image byte size overflow"))?;
4448 self.image_bytes_total = self.image_bytes_total.saturating_add(bytes);
4449
4450 self.images.insert(
4451 handle,
4452 ImageTex::Nv12 {
4453 tex_y,
4454 tex_uv,
4455 binds,
4456 yuv_buf,
4457 w,
4458 h,
4459 pixel_format: PixelFormat::Nv12,
4460 fourcc: Some(NV12_FOURCC),
4461 color_info,
4462 external: false,
4463 last_used_frame: self.frame_index,
4464 bytes,
4465 },
4466 );
4467 } else {
4468 if let Some(ImageTex::Nv12 {
4470 yuv_buf,
4471 color_info: stored_color,
4472 last_used_frame,
4473 ..
4474 }) = self.images.get_mut(&handle)
4475 {
4476 *stored_color = color_info;
4477 *last_used_frame = self.frame_index;
4478 self.queue
4479 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
4480 }
4481 }
4482
4483 let (tex_y, tex_uv, _binds) = match self.images.get(&handle) {
4484 Some(ImageTex::Nv12 {
4485 tex_y,
4486 tex_uv,
4487 binds,
4488 ..
4489 }) => (tex_y, tex_uv, binds),
4490 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
4491 };
4492
4493 self.queue.write_texture(
4494 wgpu::TexelCopyTextureInfo {
4495 texture: tex_y,
4496 mip_level: 0,
4497 origin: wgpu::Origin3d::ZERO,
4498 aspect: wgpu::TextureAspect::All,
4499 },
4500 &y[..y_expected],
4501 wgpu::TexelCopyBufferLayout {
4502 offset: 0,
4503 bytes_per_row: Some(w),
4504 rows_per_image: Some(h),
4505 },
4506 wgpu::Extent3d {
4507 width: w,
4508 height: h,
4509 depth_or_array_layers: 1,
4510 },
4511 );
4512
4513 self.queue.write_texture(
4514 wgpu::TexelCopyTextureInfo {
4515 texture: tex_uv,
4516 mip_level: 0,
4517 origin: wgpu::Origin3d::ZERO,
4518 aspect: wgpu::TextureAspect::All,
4519 },
4520 &uv[..uv_expected],
4521 wgpu::TexelCopyBufferLayout {
4522 offset: 0,
4523 bytes_per_row: Some(2 * uv_w),
4524 rows_per_image: Some(uv_h),
4525 },
4526 wgpu::Extent3d {
4527 width: uv_w,
4528 height: uv_h,
4529 depth_or_array_layers: 1,
4530 },
4531 );
4532
4533 self.evict_budget_excess();
4534 Ok(())
4535 }
4536
4537 pub fn set_image_planes(
4538 &mut self,
4539 handle: u64,
4540 w: u32,
4541 h: u32,
4542 pixel_format: PixelFormat,
4543 planes: &[&[u8]],
4544 color_info: ColorInfo,
4545 ) -> anyhow::Result<()> {
4546 validate_image_handle(handle)?;
4547 if planes.len() != pixel_format.num_planes() as usize {
4548 anyhow::bail!(
4549 "{} requires {} planes, got {}",
4550 match pixel_format {
4551 PixelFormat::Nv12 => "NV12",
4552 PixelFormat::P010 => "P010",
4553 PixelFormat::I420 => "I420",
4554 PixelFormat::I444 => "I444",
4555 PixelFormat::Rgba => "RGBA",
4556 },
4557 pixel_format.num_planes(),
4558 planes.len()
4559 );
4560 }
4561 match pixel_format {
4562 PixelFormat::Nv12 => {
4563 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
4564 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
4565 self.set_image_nv12(handle, w, h, y, uv, color_info)
4566 }
4567 PixelFormat::P010 => {
4568 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
4569 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
4570 self.set_image_p010(handle, w, h, y, uv, color_info)
4571 }
4572 PixelFormat::I420 | PixelFormat::I444 => Err(anyhow::anyhow!(
4573 "I420/I444 not implemented and unlikely -> cheap to convert to NV12 (better for the GPU too)"
4574 )),
4575 PixelFormat::Rgba => {
4576 let rgba = planes
4577 .first()
4578 .ok_or(anyhow::anyhow!("missing RGBA plane"))?;
4579 self.set_image_rgba8(handle, w, h, rgba, false)
4580 }
4581 }
4582 }
4583
4584 fn set_image_p010(
4585 &mut self,
4586 handle: u64,
4587 w: u32,
4588 h: u32,
4589 y: &[u8],
4590 uv: &[u8],
4591 color_info: ColorInfo,
4592 ) -> anyhow::Result<()> {
4593 validate_image_handle(handle)?;
4594 validate_texture_dimensions(&self.device, w, h)?;
4595 if self
4596 .images
4597 .get(&handle)
4598 .is_some_and(|image| matches!(image, ImageTex::Nv12 { external: true, .. }))
4599 {
4600 anyhow::bail!("cannot overwrite an external DMA-BUF image");
4601 }
4602 if !self
4603 .device
4604 .features()
4605 .contains(wgpu::Features::TEXTURE_FORMAT_16BIT_NORM)
4606 {
4607 anyhow::bail!("P010 requires TEXTURE_FORMAT_16BIT_NORM");
4608 }
4609 let uv_w = w.div_ceil(2);
4610 let uv_h = h.div_ceil(2);
4611
4612 let y_expected = checked_image_bytes_usize(w, h, 2)?;
4613 let uv_expected = checked_image_bytes_usize(uv_w, uv_h, 4)?;
4614
4615 if y.len() < y_expected {
4616 return Err(anyhow::anyhow!("P010 Y plane too small"));
4617 }
4618 if uv.len() < uv_expected {
4619 return Err(anyhow::anyhow!("P010 UV plane too small"));
4620 }
4621
4622 let needs_recreate = match self.images.get(&handle) {
4626 Some(ImageTex::Nv12 {
4627 w: ww,
4628 h: hh,
4629 pixel_format,
4630 ..
4631 }) => *ww != w || *hh != h || *pixel_format != PixelFormat::P010,
4632 _ => true,
4633 };
4634
4635 let yuv_raw = make_yuv_transform_raw(color_info, PixelFormat::P010);
4636
4637 if needs_recreate {
4638 self.remove_image(handle);
4639
4640 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
4641 label: Some("p010 Y"),
4642 size: wgpu::Extent3d {
4643 width: w,
4644 height: h,
4645 depth_or_array_layers: 1,
4646 },
4647 mip_level_count: 1,
4648 sample_count: 1,
4649 dimension: wgpu::TextureDimension::D2,
4650 format: wgpu::TextureFormat::R16Unorm,
4651 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4652 view_formats: &[],
4653 });
4654 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
4655
4656 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
4657 label: Some("p010 UV"),
4658 size: wgpu::Extent3d {
4659 width: uv_w,
4660 height: uv_h,
4661 depth_or_array_layers: 1,
4662 },
4663 mip_level_count: 1,
4664 sample_count: 1,
4665 dimension: wgpu::TextureDimension::D2,
4666 format: wgpu::TextureFormat::Rg16Unorm,
4667 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4668 view_formats: &[],
4669 });
4670 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
4671
4672 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4673 label: Some("p010 yuv transform"),
4674 size: std::mem::size_of::<YuvTransformRaw>() as u64,
4675 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4676 mapped_at_creation: false,
4677 });
4678 self.queue
4679 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
4680
4681 let binds = self.create_nv12_binds(&view_y, &view_uv, &yuv_buf, "p010 bind");
4682
4683 let bytes = checked_image_bytes(w, h, 2)?
4684 .checked_add(checked_image_bytes(uv_w, uv_h, 4)?)
4685 .and_then(|bytes| bytes.checked_add(std::mem::size_of::<YuvTransformRaw>() as u64))
4686 .ok_or_else(|| anyhow::anyhow!("P010 image byte size overflow"))?;
4687 self.image_bytes_total = self.image_bytes_total.saturating_add(bytes);
4688
4689 self.images.insert(
4690 handle,
4691 ImageTex::Nv12 {
4692 tex_y,
4693 tex_uv,
4694 binds,
4695 yuv_buf,
4696 w,
4697 h,
4698 pixel_format: PixelFormat::P010,
4699 fourcc: Some(P010_FOURCC),
4700 color_info,
4701 external: false,
4702 last_used_frame: self.frame_index,
4703 bytes,
4704 },
4705 );
4706 } else {
4707 if let Some(ImageTex::Nv12 {
4708 yuv_buf,
4709 color_info: stored_color,
4710 last_used_frame,
4711 ..
4712 }) = self.images.get_mut(&handle)
4713 {
4714 *stored_color = color_info;
4715 *last_used_frame = self.frame_index;
4716 self.queue
4717 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
4718 }
4719 }
4720
4721 let (tex_y, tex_uv, _binds) = match self.images.get(&handle) {
4722 Some(ImageTex::Nv12 {
4723 tex_y,
4724 tex_uv,
4725 binds,
4726 ..
4727 }) => (tex_y, tex_uv, binds),
4728 _ => return Err(anyhow::anyhow!("Handle is not P010/NV12")),
4729 };
4730
4731 self.queue.write_texture(
4732 wgpu::TexelCopyTextureInfo {
4733 texture: tex_y,
4734 mip_level: 0,
4735 origin: wgpu::Origin3d::ZERO,
4736 aspect: wgpu::TextureAspect::All,
4737 },
4738 &y[..y_expected],
4739 wgpu::TexelCopyBufferLayout {
4740 offset: 0,
4741 bytes_per_row: Some(
4742 w.checked_mul(2)
4743 .ok_or_else(|| anyhow::anyhow!("P010 row size overflow"))?,
4744 ),
4745 rows_per_image: Some(h),
4746 },
4747 wgpu::Extent3d {
4748 width: w,
4749 height: h,
4750 depth_or_array_layers: 1,
4751 },
4752 );
4753 self.queue.write_texture(
4754 wgpu::TexelCopyTextureInfo {
4755 texture: tex_uv,
4756 mip_level: 0,
4757 origin: wgpu::Origin3d::ZERO,
4758 aspect: wgpu::TextureAspect::All,
4759 },
4760 &uv[..uv_expected],
4761 wgpu::TexelCopyBufferLayout {
4762 offset: 0,
4763 bytes_per_row: Some(
4764 uv_w.checked_mul(4)
4765 .ok_or_else(|| anyhow::anyhow!("P010 UV row size overflow"))?,
4766 ),
4767 rows_per_image: Some(uv_h),
4768 },
4769 wgpu::Extent3d {
4770 width: uv_w,
4771 height: uv_h,
4772 depth_or_array_layers: 1,
4773 },
4774 );
4775
4776 self.evict_budget_excess();
4777 Ok(())
4778 }
4779
4780 #[cfg(target_os = "linux")]
4781 pub fn set_image_dmabuf(
4782 &mut self,
4783 handle: u64,
4784 w: u32,
4785 h: u32,
4786 fds: Vec<std::os::unix::io::OwnedFd>,
4787 modifier: u64,
4788 strides: Vec<u32>,
4789 offsets: Vec<u64>,
4790 color_info: ColorInfo,
4791 ) -> anyhow::Result<()> {
4792 self.set_image_dmabuf_fourcc(
4793 handle,
4794 w,
4795 h,
4796 fds,
4797 NV12_FOURCC,
4798 modifier,
4799 strides,
4800 offsets,
4801 color_info,
4802 )
4803 }
4804
4805 #[cfg(target_os = "linux")]
4806 pub fn set_image_dmabuf_fourcc(
4807 &mut self,
4808 handle: u64,
4809 w: u32,
4810 h: u32,
4811 fds: Vec<std::os::unix::io::OwnedFd>,
4812 fourcc: u32,
4813 modifier: u64,
4814 strides: Vec<u32>,
4815 offsets: Vec<u64>,
4816 color_info: ColorInfo,
4817 ) -> anyhow::Result<()> {
4818 validate_image_handle(handle)?;
4819 checked_image_bytes(w, h, 1)?;
4820 if w == 0
4821 || h == 0
4822 || w > self.device.limits().max_texture_dimension_2d
4823 || h > self.device.limits().max_texture_dimension_2d
4824 {
4825 anyhow::bail!("DMA-BUF dimensions are outside the device texture limit");
4826 }
4827 if modifier == u64::MAX {
4828 anyhow::bail!("DMA-BUF modifier is invalid");
4829 }
4830 if fds.len() != 2 || strides.len() != 2 || offsets.len() != 2 {
4831 anyhow::bail!(
4832 "DMA-BUF import requires two fds, strides, and offsets (got {}, {}, {})",
4833 fds.len(),
4834 strides.len(),
4835 offsets.len()
4836 );
4837 }
4838 let pixel_format = match fourcc {
4839 0x3231_564e | 0x4e56_3132 => PixelFormat::Nv12,
4840 0x3031_3050 | 0x5030_3130 => PixelFormat::P010,
4841 _ => anyhow::bail!("unsupported DMA-BUF fourcc 0x{fourcc:08x}"),
4842 };
4843 if !self
4844 .device
4845 .features()
4846 .contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF)
4847 {
4848 anyhow::bail!("DMA-BUF import requires VULKAN_EXTERNAL_MEMORY_DMA_BUF");
4849 }
4850 if pixel_format == PixelFormat::P010
4851 && !self
4852 .device
4853 .features()
4854 .contains(wgpu::Features::TEXTURE_FORMAT_16BIT_NORM)
4855 {
4856 anyhow::bail!("P010 DMA-BUF requires TEXTURE_FORMAT_16BIT_NORM");
4857 }
4858 let bytes_per_pixel = if pixel_format == PixelFormat::P010 {
4859 2
4860 } else {
4861 1
4862 };
4863 let uv_w = w.div_ceil(2);
4864 let uv_h = h.div_ceil(2);
4865 let y_file_size =
4866 validate_dmabuf_plane(&fds[0], offsets[0], strides[0], w, h, bytes_per_pixel)?;
4867 let uv_file_size = validate_dmabuf_plane(
4868 &fds[1],
4869 offsets[1],
4870 strides[1],
4871 uv_w,
4872 uv_h,
4873 bytes_per_pixel * 2,
4874 )?;
4875 let yuv_raw = make_yuv_transform_raw(color_info, pixel_format);
4876 let (y_format, uv_format) = if pixel_format == PixelFormat::P010 {
4877 (
4878 wgpu::TextureFormat::R16Unorm,
4879 wgpu::TextureFormat::Rg16Unorm,
4880 )
4881 } else {
4882 (wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::Rg8Unorm)
4883 };
4884 let hal_y_desc = wgpu::hal::TextureDescriptor {
4885 label: Some("dmabuf y"),
4886 size: wgpu::Extent3d {
4887 width: w,
4888 height: h,
4889 depth_or_array_layers: 1,
4890 },
4891 mip_level_count: 1,
4892 sample_count: 1,
4893 dimension: wgpu::TextureDimension::D2,
4894 format: y_format,
4895 usage: wgpu::wgt::TextureUses::RESOURCE,
4896 memory_flags: wgpu::hal::MemoryFlags::empty(),
4897 view_formats: vec![],
4898 };
4899 let hal_uv_desc = wgpu::hal::TextureDescriptor {
4900 label: Some("dmabuf uv"),
4901 size: wgpu::Extent3d {
4902 width: uv_w,
4903 height: uv_h,
4904 depth_or_array_layers: 1,
4905 },
4906 mip_level_count: 1,
4907 sample_count: 1,
4908 dimension: wgpu::TextureDimension::D2,
4909 format: uv_format,
4910 usage: wgpu::wgt::TextureUses::RESOURCE,
4911 memory_flags: wgpu::hal::MemoryFlags::empty(),
4912 view_formats: vec![],
4913 };
4914 let wgpu_y_desc = wgpu::TextureDescriptor {
4915 label: Some("dmabuf y"),
4916 size: hal_y_desc.size,
4917 mip_level_count: 1,
4918 sample_count: 1,
4919 dimension: wgpu::TextureDimension::D2,
4920 format: y_format,
4921 usage: wgpu::TextureUsages::TEXTURE_BINDING,
4922 view_formats: &[],
4923 };
4924 let wgpu_uv_desc = wgpu::TextureDescriptor {
4925 label: Some("dmabuf uv"),
4926 size: hal_uv_desc.size,
4927 mip_level_count: 1,
4928 sample_count: 1,
4929 dimension: wgpu::TextureDimension::D2,
4930 format: uv_format,
4931 usage: wgpu::TextureUsages::TEXTURE_BINDING,
4932 view_formats: &[],
4933 };
4934
4935 let (tex_y, view_y, tex_uv, view_uv) = unsafe {
4936 let hal_guard = self
4937 .device
4938 .as_hal::<wgpu::hal::vulkan::Api>()
4939 .ok_or_else(|| anyhow::anyhow!("device is not Vulkan"))?;
4940 let mut fds = fds.into_iter();
4941 let y_fd = fds.next().expect("validated fd count");
4942 let uv_fd = fds.next().expect("validated fd count");
4943 let y_texture = hal_guard.texture_from_dmabuf_fd(
4944 y_fd,
4945 &hal_y_desc,
4946 modifier,
4947 strides[0] as u64,
4948 offsets[0],
4949 );
4950 let y_texture =
4951 y_texture.map_err(|error| anyhow::anyhow!("import Y DMA-BUF: {error:?}"))?;
4952 let uv_texture = hal_guard.texture_from_dmabuf_fd(
4953 uv_fd,
4954 &hal_uv_desc,
4955 modifier,
4956 strides[1] as u64,
4957 offsets[1],
4958 );
4959 let uv_texture =
4960 uv_texture.map_err(|error| anyhow::anyhow!("import UV DMA-BUF: {error:?}"))?;
4961 drop(hal_guard);
4962 let tex_y = self
4963 .device
4964 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
4965 y_texture,
4966 &wgpu_y_desc,
4967 wgpu::wgt::TextureUses::UNINITIALIZED,
4968 );
4969 let tex_uv = self
4970 .device
4971 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
4972 uv_texture,
4973 &wgpu_uv_desc,
4974 wgpu::wgt::TextureUses::UNINITIALIZED,
4975 );
4976 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
4977 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
4978 (tex_y, view_y, tex_uv, view_uv)
4979 };
4980
4981 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4982 label: Some("dmabuf yuv transform"),
4983 size: std::mem::size_of::<YuvTransformRaw>() as u64,
4984 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4985 mapped_at_creation: false,
4986 });
4987 self.queue
4988 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
4989 let binds = self.create_nv12_binds(&view_y, &view_uv, &yuv_buf, "dmabuf yuv bind");
4990 let bytes = y_file_size
4991 .checked_add(uv_file_size)
4992 .and_then(|value| value.checked_add(std::mem::size_of::<YuvTransformRaw>() as u64))
4993 .ok_or_else(|| anyhow::anyhow!("DMA-BUF byte size overflow"))?;
4994 self.evict_budget_excess();
4995 let replaced_bytes = self
4996 .images
4997 .get(&handle)
4998 .map(|image| match image {
4999 ImageTex::Rgba { bytes, .. }
5000 | ImageTex::Nv12 { bytes, .. }
5001 | ImageTex::User { bytes, .. } => *bytes,
5002 })
5003 .unwrap_or(0);
5004 let projected = self
5005 .image_bytes_total
5006 .saturating_sub(replaced_bytes)
5007 .checked_add(bytes)
5008 .ok_or_else(|| anyhow::anyhow!("DMA-BUF image budget overflow"))?;
5009 if projected > self.image_budget_bytes {
5010 anyhow::bail!("DMA-BUF image budget exceeded");
5011 }
5012 self.remove_image(handle);
5013 self.image_bytes_total = self.image_bytes_total.saturating_add(bytes);
5014 self.images.insert(
5015 handle,
5016 ImageTex::Nv12 {
5017 tex_y,
5018 tex_uv,
5019 binds,
5020 yuv_buf,
5021 w,
5022 h,
5023 pixel_format,
5024 fourcc: Some(canonical_fourcc(pixel_format)),
5025 color_info,
5026 external: true,
5027 last_used_frame: self.frame_index,
5028 bytes,
5029 },
5030 );
5031 self.evict_budget_excess();
5032 Ok(())
5033 }
5034
5035 pub fn image_fourcc(&self, handle: u64) -> Option<u32> {
5036 match self.images.get(&handle) {
5037 Some(ImageTex::Nv12 { fourcc, .. }) => *fourcc,
5038 _ => None,
5039 }
5040 }
5041
5042 pub fn remove_image(&mut self, handle: u64) {
5043 if let Some(img) = self.images.remove(&handle) {
5044 let b = match &img {
5045 ImageTex::Rgba { bytes, .. } => *bytes,
5046 ImageTex::Nv12 { bytes, .. } => *bytes,
5047 ImageTex::User { bytes, .. } => *bytes,
5048 };
5049 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
5050 }
5051 if let Some(retained) = self.retained.remove(&handle) {
5052 self.retained_bytes_total = self
5053 .retained_bytes_total
5054 .saturating_sub(retained.rgba.len() as u64);
5055 }
5056 }
5057
5058 fn evict_image_gpu(&mut self, handle: u64) -> u64 {
5059 let Some(img) = self.images.remove(&handle) else {
5060 return 0;
5061 };
5062 let b = match &img {
5063 ImageTex::Rgba { bytes, .. } => *bytes,
5064 ImageTex::Nv12 { bytes, .. } => *bytes,
5065 ImageTex::User { bytes, .. } => *bytes,
5066 };
5067 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
5068 b
5069 }
5070
5071 fn revive_retained_image(&mut self, handle: u64) -> anyhow::Result<bool> {
5072 if self.images.contains_key(&handle) {
5073 return Ok(true);
5074 }
5075 let Some(r) = self.retained.get_mut(&handle) else {
5076 return Ok(false);
5077 };
5078 r.last_used_frame = self.frame_index;
5079 let r = r.clone();
5080 let (tex, binds) = self.create_rgba_tex(r.w, r.h, r.format);
5081
5082 self.queue.write_texture(
5083 wgpu::TexelCopyTextureInfo {
5084 texture: &tex,
5085 mip_level: 0,
5086 origin: wgpu::Origin3d::ZERO,
5087 aspect: wgpu::TextureAspect::All,
5088 },
5089 &r.rgba,
5090 wgpu::TexelCopyBufferLayout {
5091 offset: 0,
5092 bytes_per_row: Some(4 * r.w),
5093 rows_per_image: Some(r.h),
5094 },
5095 wgpu::Extent3d {
5096 width: r.w,
5097 height: r.h,
5098 depth_or_array_layers: 1,
5099 },
5100 );
5101
5102 let bytes = checked_image_bytes(r.w, r.h, 4)?;
5103 self.image_bytes_total = self.image_bytes_total.saturating_add(bytes);
5104 self.images.insert(
5105 handle,
5106 ImageTex::Rgba {
5107 tex,
5108 binds,
5109 w: r.w,
5110 h: r.h,
5111 format: r.format,
5112 last_used_frame: self.frame_index,
5113 bytes,
5114 },
5115 );
5116 Ok(true)
5117 }
5118
5119 fn resolve_image_for_draw(&mut self, handle: u64) -> Option<(u32, u32, bool)> {
5120 if let Some(retained) = self.retained.get_mut(&handle) {
5121 retained.last_used_frame = self.frame_index;
5122 }
5123 if let Some(t) = self.images.get_mut(&handle) {
5124 return match t {
5125 ImageTex::Rgba {
5126 w,
5127 h,
5128 last_used_frame,
5129 ..
5130 } => {
5131 *last_used_frame = self.frame_index;
5132 Some((*w, *h, false))
5133 }
5134 ImageTex::User {
5135 w,
5136 h,
5137 last_used_frame,
5138 ..
5139 } => {
5140 *last_used_frame = self.frame_index;
5141 Some((*w, *h, false))
5142 }
5143 ImageTex::Nv12 {
5144 w,
5145 h,
5146 last_used_frame,
5147 ..
5148 } => {
5149 *last_used_frame = self.frame_index;
5150 Some((*w, *h, true))
5151 }
5152 };
5153 }
5154 if self.revive_retained_image(handle).unwrap_or(false)
5155 && let Some(ImageTex::Rgba {
5156 w,
5157 h,
5158 last_used_frame,
5159 ..
5160 }) = self.images.get_mut(&handle)
5161 {
5162 *last_used_frame = self.frame_index;
5163 return Some((*w, *h, false));
5164 }
5165 None
5166 }
5167
5168 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
5170 let handle = self.next_image_handle;
5171 self.next_image_handle += 1;
5172 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
5173 log::error!("Failed to register image: {e}");
5174 }
5175 handle
5176 }
5177
5178 pub fn register_image_rgba8(&mut self, w: u32, h: u32, rgba: &[u8], srgb: bool) -> u64 {
5183 let handle = self.next_image_handle;
5184 self.next_image_handle += 1;
5185 if let Err(e) = self.set_image_rgba8(handle, w, h, rgba, srgb) {
5186 log::error!("Failed to register image: {e}");
5187 }
5188 handle
5189 }
5190
5191 pub fn register_coverage_a8(&mut self, w: u32, h: u32, coverage: &[u8]) -> u64 {
5197 if validate_texture_dimensions(&self.device, w, h).is_err() {
5198 log::error!("Coverage dimensions are outside the device limit");
5199 return 0;
5200 }
5201 let Ok(expected_bytes) = checked_image_bytes(w, h, 1) else {
5202 log::error!("Coverage dimensions overflow");
5203 return 0;
5204 };
5205 let Ok(expected) = usize::try_from(expected_bytes) else {
5206 log::error!("Coverage dimensions exceed addressable memory");
5207 return 0;
5208 };
5209 if coverage.len() < expected {
5210 log::error!("Coverage buffer too small: {} < {expected}", coverage.len());
5211 return 0;
5212 }
5213 let handle = self.next_coverage_handle;
5214 self.next_coverage_handle += 1;
5215
5216 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
5217 label: Some("coverage tile a8"),
5218 size: wgpu::Extent3d {
5219 width: w,
5220 height: h,
5221 depth_or_array_layers: 1,
5222 },
5223 mip_level_count: 1,
5224 sample_count: 1,
5225 dimension: wgpu::TextureDimension::D2,
5226 format: wgpu::TextureFormat::R8Unorm,
5227 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
5228 view_formats: &[],
5229 });
5230 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
5231 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
5232 label: Some("coverage bind a8"),
5233 layout: &self.image_bind_layout_rgba,
5234 entries: &[
5235 wgpu::BindGroupEntry {
5236 binding: 0,
5237 resource: wgpu::BindingResource::TextureView(&view),
5238 },
5239 wgpu::BindGroupEntry {
5240 binding: 1,
5241 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
5242 },
5243 ],
5244 });
5245 self.queue.write_texture(
5246 wgpu::TexelCopyTextureInfo {
5247 texture: &tex,
5248 mip_level: 0,
5249 origin: wgpu::Origin3d::ZERO,
5250 aspect: wgpu::TextureAspect::All,
5251 },
5252 &coverage[..expected],
5253 wgpu::TexelCopyBufferLayout {
5254 offset: 0,
5255 bytes_per_row: Some(w),
5256 rows_per_image: Some(h),
5257 },
5258 wgpu::Extent3d {
5259 width: w,
5260 height: h,
5261 depth_or_array_layers: 1,
5262 },
5263 );
5264 let bytes = checked_image_bytes(w, h, 1).unwrap_or(u64::MAX);
5265 self.image_bytes_total = self.image_bytes_total.saturating_add(bytes);
5266 self.coverages.insert(
5267 handle,
5268 CoverageTex {
5269 tex,
5270 bind,
5271 w,
5272 h,
5273 last_used_frame: self.frame_index,
5274 bytes,
5275 },
5276 );
5277 self.evict_budget_excess();
5278 handle
5279 }
5280
5281 pub fn remove_coverage(&mut self, handle: u64) {
5283 if let Some(tile) = self.coverages.remove(&handle) {
5284 self.image_bytes_total = self.image_bytes_total.saturating_sub(tile.bytes);
5285 }
5286 }
5287
5288 pub fn coverage_dimensions(&mut self, handle: u64) -> Option<(u32, u32)> {
5291 if let Some(tile) = self.coverages.get_mut(&handle) {
5292 tile.last_used_frame = self.frame_index;
5293 return Some((tile.w, tile.h));
5294 }
5295 None
5296 }
5297
5298 fn evict_retained_excess(&mut self) {
5299 let mut candidates: Vec<(u64, u64, u64)> = self
5300 .retained
5301 .iter()
5302 .map(|(handle, image)| (*handle, image.last_used_frame, image.rgba.len() as u64))
5303 .collect();
5304 candidates.sort_by_key(|candidate| candidate.1);
5305 for (handle, _last_used, bytes) in candidates {
5306 if self.retained.len() <= MAX_RETAINED_IMAGES
5307 && self.retained_bytes_total <= MAX_RETAINED_IMAGE_BYTES
5308 {
5309 break;
5310 }
5311 if let Some(image) = self.retained.remove(&handle) {
5312 self.retained_bytes_total = self
5313 .retained_bytes_total
5314 .saturating_sub(bytes.min(image.rgba.len() as u64));
5315 }
5316 }
5317 }
5318
5319 fn evict_unused_images(&mut self) {
5320 let now = self.frame_index;
5321 let evict_after = self.image_evict_after_frames;
5322
5323 let mut to_evict = Vec::new();
5326 for (h, t) in self.images.iter() {
5327 if !t.evictable() {
5328 continue;
5329 }
5330 let last = match t {
5331 ImageTex::Rgba {
5332 last_used_frame, ..
5333 } => *last_used_frame,
5334 ImageTex::User {
5335 last_used_frame, ..
5336 } => *last_used_frame,
5337 ImageTex::Nv12 {
5338 last_used_frame, ..
5339 } => *last_used_frame,
5340 };
5341 if now.saturating_sub(last) > evict_after {
5342 to_evict.push(*h);
5343 }
5344 }
5345 for h in to_evict {
5346 if self.retained.contains_key(&h) {
5347 self.evict_image_gpu(h);
5348 } else {
5349 self.remove_image(h);
5350 }
5351 }
5352
5353 let mut stale = Vec::new();
5355 for (h, t) in self.coverages.iter() {
5356 if now.saturating_sub(t.last_used_frame) > evict_after {
5357 stale.push(*h);
5358 }
5359 }
5360 for h in stale {
5361 self.remove_coverage(h);
5362 }
5363
5364 self.evict_budget_excess();
5365 }
5366
5367 fn next_cache_touch(&mut self) -> u64 {
5368 let touch = self.cache_touch_clock;
5369 self.cache_touch_clock = self.cache_touch_clock.wrapping_add(1);
5370 touch
5371 }
5372
5373 fn gpu_bytes_total(&self) -> u64 {
5374 self.image_bytes_total
5375 .saturating_add(self.layer_bytes_total)
5376 .saturating_add(self.blend_snapshot_bytes_total)
5377 .saturating_add(self.mesh_cache_bytes_total)
5378 .saturating_add(self.working_space_bytes)
5379 .saturating_add(self.surface_resolve_bytes)
5380 .saturating_add(self.msaa_bytes)
5381 .saturating_add(self.ws_msaa_bytes)
5382 .saturating_add(self.depth_stencil_bytes)
5383 }
5384
5385 fn budget_exceeded(&self) -> bool {
5386 self.image_bytes_total > self.image_budget_bytes
5387 || self.gpu_bytes_total() > self.gpu_budget_bytes
5388 }
5389
5390 fn evict_budget_excess(&mut self) {
5391 let image_over_budget = self.image_bytes_total > self.image_budget_bytes;
5392 let gpu_over_budget = self.gpu_bytes_total() > self.gpu_budget_bytes;
5393 if !image_over_budget && !gpu_over_budget {
5394 return;
5395 }
5396 let mut coverage_candidates: Vec<(u64, u64)> = self
5397 .coverages
5398 .iter()
5399 .map(|(handle, tile)| (*handle, tile.last_used_frame))
5400 .collect();
5401 coverage_candidates.sort_by_key(|candidate| candidate.1);
5402 let now = self.frame_index;
5403 for (handle, last_used) in coverage_candidates {
5404 if !self.budget_exceeded() {
5405 break;
5406 }
5407 if last_used == now {
5408 continue;
5409 }
5410 self.remove_coverage(handle);
5411 }
5412 if !self.budget_exceeded() {
5413 return;
5414 }
5415 let mut candidates: Vec<(u64, u64, u64)> = self
5417 .images
5418 .iter()
5419 .filter_map(|(h, t)| {
5420 if !t.evictable() {
5421 return None;
5422 }
5423 let (last, bytes) = match t {
5424 ImageTex::Rgba {
5425 last_used_frame,
5426 bytes,
5427 ..
5428 } => (*last_used_frame, *bytes),
5429 ImageTex::User {
5430 last_used_frame,
5431 bytes,
5432 ..
5433 } => (*last_used_frame, *bytes),
5434 ImageTex::Nv12 {
5435 last_used_frame,
5436 bytes,
5437 ..
5438 } => (*last_used_frame, *bytes),
5439 };
5440 Some((*h, last, bytes))
5441 })
5442 .collect();
5443
5444 candidates.sort_by_key(|k| k.1);
5446
5447 for (h, last, _bytes) in candidates {
5448 if !self.budget_exceeded() {
5449 break;
5450 }
5451 if last == now {
5452 continue;
5453 }
5454 if self.retained.contains_key(&h) {
5455 self.evict_image_gpu(h);
5456 } else {
5457 self.remove_image(h);
5458 }
5459 }
5460 while self.gpu_bytes_total() > self.gpu_budget_bytes {
5461 if !self.evict_mesh_cache() {
5462 break;
5463 }
5464 }
5465 if self.gpu_bytes_total() > self.gpu_budget_bytes {
5466 log::warn!(
5467 "renderer GPU resource budget exceeded: {} > {}",
5468 self.gpu_bytes_total(),
5469 self.gpu_budget_bytes
5470 );
5471 }
5472 }
5473
5474 pub fn set_pixels_per_point(&mut self, ppp: f32) {
5476 if ppp.is_finite() {
5477 self.pixels_per_point = ppp.clamp(0.5, 8.0);
5478 }
5479 }
5480
5481 pub fn set_working_space(&mut self, enabled: bool) {
5485 if enabled == self.working_space {
5486 return;
5487 }
5488 self.working_space = enabled;
5489 if enabled {
5490 self.ensure_display_pipeline();
5491 self.recreate_msaa_and_depth_stencil();
5492 self.recreate_working_space_texture();
5493 } else {
5494 self.ws_tex = None;
5495 self.ws_view = None;
5496 self.ws_bind = None;
5497 self.ws_msaa_tex = None;
5498 self.ws_msaa_view = None;
5499 self.surface_resolve_tex = None;
5500 self.surface_resolve_view = None;
5501 self.surface_resolve_bytes = 0;
5502 self.working_space_bytes = 0;
5503 self.recreate_msaa_and_depth_stencil();
5504 }
5505 self.evict_budget_excess();
5506 }
5507
5508 fn ensure_display_pipeline(&mut self) {
5509 if self.display_pipeline.is_some() {
5510 return;
5511 }
5512
5513 let layout = self
5514 .device
5515 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
5516 label: Some("display transform layout"),
5517 entries: &[
5518 wgpu::BindGroupLayoutEntry {
5519 binding: 0,
5520 visibility: wgpu::ShaderStages::FRAGMENT,
5521 ty: wgpu::BindingType::Texture {
5522 multisampled: false,
5523 view_dimension: wgpu::TextureViewDimension::D2,
5524 sample_type: wgpu::TextureSampleType::Float { filterable: true },
5525 },
5526 count: None,
5527 },
5528 wgpu::BindGroupLayoutEntry {
5529 binding: 1,
5530 visibility: wgpu::ShaderStages::FRAGMENT,
5531 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
5532 count: None,
5533 },
5534 ],
5535 });
5536 self.display_layout = Some(layout);
5537
5538 let display_source: Cow<'static, str> = if self.output_format.is_srgb() {
5539 Cow::Borrowed(include_str!("shaders/display_transform_passthrough.wgsl"))
5540 } else {
5541 Cow::Borrowed(include_str!("shaders/display_transform.wgsl"))
5542 };
5543 let shader = self
5544 .device
5545 .create_shader_module(wgpu::ShaderModuleDescriptor {
5546 label: Some("display transform"),
5547 source: wgpu::ShaderSource::Wgsl(display_source),
5548 });
5549
5550 let pipeline_layout = self
5551 .device
5552 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
5553 label: Some("display transform pipeline layout"),
5554 bind_group_layouts: &[None, self.display_layout.as_ref()],
5555 immediate_size: 0,
5556 });
5557
5558 let pipeline = self
5559 .device
5560 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
5561 label: Some("display transform pipeline"),
5562 layout: Some(&pipeline_layout),
5563 vertex: wgpu::VertexState {
5564 module: &shader,
5565 entry_point: Some("vs_main"),
5566 buffers: &[],
5567 compilation_options: wgpu::PipelineCompilationOptions::default(),
5568 },
5569 fragment: Some(wgpu::FragmentState {
5570 module: &shader,
5571 entry_point: Some("fs_main"),
5572 targets: &[Some(wgpu::ColorTargetState {
5573 format: self.output_format,
5574 blend: None,
5575 write_mask: wgpu::ColorWrites::ALL,
5576 })],
5577 compilation_options: wgpu::PipelineCompilationOptions::default(),
5578 }),
5579 primitive: wgpu::PrimitiveState::default(),
5580 depth_stencil: None,
5581 multisample: wgpu::MultisampleState::default(),
5582 multiview_mask: None,
5583 cache: None,
5584 });
5585 self.display_pipeline = Some(pipeline);
5586 }
5587
5588 pub fn resize(&mut self, width: u32, height: u32) {
5593 let (width, height) =
5594 clamp_output_size(width, height, self.device.limits().max_texture_dimension_2d);
5595 if self.output_width == width && self.output_height == height {
5596 return;
5597 }
5598 self.output_width = width;
5599 self.output_height = height;
5600 if width == 0 || height == 0 {
5601 self.msaa_tex = None;
5602 self.msaa_view = None;
5603 self.ws_msaa_tex = None;
5604 self.ws_msaa_view = None;
5605 self.surface_resolve_tex = None;
5606 self.surface_resolve_view = None;
5607 self.surface_resolve_bytes = 0;
5608 self.msaa_bytes = 0;
5609 self.ws_msaa_bytes = 0;
5610 self.depth_stencil_bytes = 0;
5611 self.working_space_bytes = 0;
5612 self.ws_tex = None;
5613 self.ws_view = None;
5614 self.ws_bind = None;
5615 self.evict_budget_excess();
5616 return;
5617 }
5618 self.recreate_msaa_and_depth_stencil();
5619 self.recreate_working_space_texture();
5620 self.evict_budget_excess();
5621 }
5622
5623 fn recreate_working_space_texture(&mut self) {
5624 if !self.working_space || self.output_width == 0 || self.output_height == 0 {
5625 return;
5626 }
5627 self.working_space_bytes = 0;
5628 let w = self
5629 .output_width
5630 .clamp(1, self.device.limits().max_texture_dimension_2d);
5631 let h = self
5632 .output_height
5633 .clamp(1, self.device.limits().max_texture_dimension_2d);
5634
5635 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
5636 label: Some("working space"),
5637 size: wgpu::Extent3d {
5638 width: w,
5639 height: h,
5640 depth_or_array_layers: 1,
5641 },
5642 mip_level_count: 1,
5643 sample_count: 1,
5644 dimension: wgpu::TextureDimension::D2,
5645 format: wgpu::TextureFormat::Rgba16Float,
5646 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
5647 | wgpu::TextureUsages::TEXTURE_BINDING
5648 | wgpu::TextureUsages::COPY_SRC
5649 | wgpu::TextureUsages::COPY_DST,
5650 view_formats: &[],
5651 });
5652 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
5653
5654 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
5655 label: Some("working space bind"),
5656 layout: self.display_layout.as_ref().unwrap(),
5657 entries: &[
5658 wgpu::BindGroupEntry {
5659 binding: 0,
5660 resource: wgpu::BindingResource::TextureView(&view),
5661 },
5662 wgpu::BindGroupEntry {
5663 binding: 1,
5664 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
5665 },
5666 ],
5667 });
5668
5669 self.working_space_bytes =
5670 texture_storage_bytes(wgpu::TextureFormat::Rgba16Float, w, h).unwrap_or(0);
5671 self.ws_tex = Some(tex);
5672 self.ws_view = Some(view);
5673 self.ws_bind = Some(bind);
5674 }
5675
5676 fn recreate_msaa_and_depth_stencil(&mut self) {
5677 if self.output_width == 0 || self.output_height == 0 {
5678 self.msaa_tex = None;
5679 self.msaa_view = None;
5680 self.ws_msaa_tex = None;
5681 self.ws_msaa_view = None;
5682 self.surface_resolve_tex = None;
5683 self.surface_resolve_view = None;
5684 self.surface_resolve_bytes = 0;
5685 self.msaa_bytes = 0;
5686 self.ws_msaa_bytes = 0;
5687 self.depth_stencil_bytes = 0;
5688 return;
5689 }
5690 self.msaa_bytes = 0;
5691 self.ws_msaa_bytes = 0;
5692 self.depth_stencil_bytes = 0;
5693 let aliased_surface = self.surface_format != self.output_format;
5694 let view_format_list = [self.output_format];
5695 let surface_view_formats: &[wgpu::TextureFormat] = if aliased_surface {
5696 &view_format_list
5697 } else {
5698 &[]
5699 };
5700 let surface_view = if aliased_surface {
5701 wgpu::TextureViewDescriptor {
5702 format: Some(self.output_format),
5703 ..Default::default()
5704 }
5705 } else {
5706 wgpu::TextureViewDescriptor::default()
5707 };
5708 if self.msaa_samples > 1 && !self.working_space {
5709 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
5710 label: Some("msaa color"),
5711 size: wgpu::Extent3d {
5712 width: self.output_width.max(1),
5713 height: self.output_height.max(1),
5714 depth_or_array_layers: 1,
5715 },
5716 mip_level_count: 1,
5717 sample_count: self.msaa_samples,
5718 dimension: wgpu::TextureDimension::D2,
5719 format: self.surface_format,
5720 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
5721 view_formats: surface_view_formats,
5722 });
5723 let view = tex.create_view(&surface_view);
5724 self.msaa_bytes = texture_storage_bytes_with_samples(
5725 self.surface_format,
5726 self.output_width.max(1),
5727 self.output_height.max(1),
5728 self.msaa_samples,
5729 )
5730 .unwrap_or(0);
5731 self.msaa_tex = Some(tex);
5732 self.msaa_view = Some(view);
5733 } else {
5734 self.msaa_tex = None;
5735 self.msaa_view = None;
5736 }
5737 if self.working_space && self.working_space_msaa_samples > 1 {
5738 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
5739 label: Some("working space msaa color"),
5740 size: wgpu::Extent3d {
5741 width: self.output_width.max(1),
5742 height: self.output_height.max(1),
5743 depth_or_array_layers: 1,
5744 },
5745 mip_level_count: 1,
5746 sample_count: self.working_space_msaa_samples,
5747 dimension: wgpu::TextureDimension::D2,
5748 format: wgpu::TextureFormat::Rgba16Float,
5749 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
5750 view_formats: &[],
5751 });
5752 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
5753 self.ws_msaa_bytes = texture_storage_bytes_with_samples(
5754 wgpu::TextureFormat::Rgba16Float,
5755 self.output_width.max(1),
5756 self.output_height.max(1),
5757 self.working_space_msaa_samples,
5758 )
5759 .unwrap_or(0);
5760 self.ws_msaa_tex = Some(tex);
5761 self.ws_msaa_view = Some(view);
5762 } else {
5763 self.ws_msaa_tex = None;
5764 self.ws_msaa_view = None;
5765 }
5766 self.surface_resolve_tex = None;
5767 self.surface_resolve_view = None;
5768 self.surface_resolve_bytes = 0;
5769 if self.msaa_samples > 1 && !self.working_space {
5770 let width = self.output_width.max(1);
5771 let height = self.output_height.max(1);
5772 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
5773 label: Some("surface resolve"),
5774 size: wgpu::Extent3d {
5775 width,
5776 height,
5777 depth_or_array_layers: 1,
5778 },
5779 mip_level_count: 1,
5780 sample_count: 1,
5781 dimension: wgpu::TextureDimension::D2,
5782 format: self.surface_format,
5783 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
5784 | wgpu::TextureUsages::TEXTURE_BINDING
5785 | wgpu::TextureUsages::COPY_SRC,
5786 view_formats: surface_view_formats,
5787 });
5788 let view = tex.create_view(&surface_view);
5789 self.surface_resolve_bytes =
5790 texture_storage_bytes(self.surface_format, width, height).unwrap_or(0);
5791 self.surface_resolve_tex = Some(tex);
5792 self.surface_resolve_view = Some(view);
5793 }
5794
5795 self.depth_stencil_bytes = texture_storage_bytes_with_samples(
5796 wgpu::TextureFormat::Depth24PlusStencil8,
5797 self.output_width.max(1),
5798 self.output_height.max(1),
5799 self.active_surface_msaa_samples(),
5800 )
5801 .unwrap_or(0);
5802 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
5803 label: Some("depth-stencil (stencil clips)"),
5804 size: wgpu::Extent3d {
5805 width: self.output_width.max(1),
5806 height: self.output_height.max(1),
5807 depth_or_array_layers: 1,
5808 },
5809 mip_level_count: 1,
5810 sample_count: self.active_surface_msaa_samples(),
5811 dimension: wgpu::TextureDimension::D2,
5812 format: wgpu::TextureFormat::Depth24PlusStencil8,
5813 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
5814 view_formats: &[],
5815 });
5816 self.depth_stencil_view = self
5817 .depth_stencil_tex
5818 .create_view(&wgpu::TextureViewDescriptor::default());
5819 }
5820
5821 fn active_surface_msaa_samples(&self) -> u32 {
5822 if self.working_space {
5823 self.working_space_msaa_samples
5824 } else {
5825 self.msaa_samples
5826 }
5827 }
5828
5829 fn layer_target_format(&self) -> wgpu::TextureFormat {
5830 if self.working_space {
5831 wgpu::TextureFormat::Rgba16Float
5832 } else {
5833 self.output_format
5834 }
5835 }
5836
5837 fn get_or_create_layer(
5838 &mut self,
5839 layer_id: u32,
5840 width: u32,
5841 height: u32,
5842 rect: repose_core::Rect,
5843 transient: bool,
5844 ) -> bool {
5845 if width == 0
5846 || height == 0
5847 || width > self.device.limits().max_texture_dimension_2d
5848 || height > self.device.limits().max_texture_dimension_2d
5849 {
5850 return false;
5851 }
5852 let needs_alloc = match self.layer_pool.get(&layer_id) {
5853 Some(lt) => {
5854 lt.width != width || lt.height != height || lt.format != self.layer_target_format()
5855 }
5856 None => true,
5857 };
5858 if !needs_alloc {
5859 if let Some(lt) = self.layer_pool.get_mut(&layer_id) {
5860 lt.rect_px = (rect.x, rect.y, rect.w, rect.h);
5861 }
5862 return true;
5863 }
5864 let color_bytes =
5865 texture_storage_bytes(self.layer_target_format(), width.max(1), height.max(1))
5866 .unwrap_or(MAX_GRAPHICS_LAYER_BYTES + 1);
5867 let depth_bytes = checked_image_bytes(width.max(1), height.max(1), 4)
5868 .unwrap_or(MAX_GRAPHICS_LAYER_BYTES + 1);
5869 let bytes = color_bytes.saturating_add(depth_bytes);
5870 let old = self.layer_pool.get(&layer_id);
5871 let old_bytes = old.map_or(0, |layer| layer.bytes);
5872 let old_transient_bytes =
5873 old.map_or(0, |layer| if layer.transient { layer.bytes } else { 0 });
5874 let transient_count = self
5875 .layer_pool
5876 .values()
5877 .filter(|layer| layer.transient)
5878 .count();
5879 let transient_count = transient_count
5880 .saturating_sub(usize::from(old.is_some_and(|layer| layer.transient)))
5881 .saturating_add(usize::from(transient));
5882 let count = self.layer_pool.len() + usize::from(!self.layer_pool.contains_key(&layer_id));
5883 let total = self
5884 .layer_bytes_total
5885 .saturating_sub(old_bytes)
5886 .saturating_add(bytes);
5887 let projected_gpu = self
5888 .gpu_bytes_total()
5889 .saturating_sub(old_bytes)
5890 .saturating_add(bytes);
5891 let projected_transient = self
5892 .transient_layer_bytes_total
5893 .saturating_sub(old_transient_bytes)
5894 .saturating_add(if transient { bytes } else { 0 });
5895 if count > MAX_GRAPHICS_LAYERS
5896 || (transient
5897 && (transient_count > MAX_TRANSIENT_GRAPHICS_LAYERS
5898 || projected_transient > MAX_TRANSIENT_GRAPHICS_LAYER_BYTES))
5899 || bytes > MAX_GRAPHICS_LAYER_BYTES
5900 || total > MAX_GRAPHICS_LAYER_BYTES
5901 || projected_gpu > self.gpu_budget_bytes
5902 {
5903 log::warn!("graphics layer budget exhausted; layer {layer_id} skipped");
5904 if let Some(old) = self.layer_pool.remove(&layer_id) {
5905 self.layer_bytes_total = self.layer_bytes_total.saturating_sub(old.bytes);
5906 if old.transient {
5907 self.transient_layer_bytes_total =
5908 self.transient_layer_bytes_total.saturating_sub(old.bytes);
5909 }
5910 }
5911 return false;
5912 }
5913 if let Some(old) = self.layer_pool.remove(&layer_id) {
5914 self.layer_bytes_total = self.layer_bytes_total.saturating_sub(old.bytes);
5915 if old.transient {
5916 self.transient_layer_bytes_total =
5917 self.transient_layer_bytes_total.saturating_sub(old.bytes);
5918 }
5919 }
5920 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
5921 label: Some("graphics layer"),
5922 size: wgpu::Extent3d {
5923 width: width.max(1),
5924 height: height.max(1),
5925 depth_or_array_layers: 1,
5926 },
5927 mip_level_count: 1,
5928 sample_count: 1,
5929 dimension: wgpu::TextureDimension::D2,
5930 format: self.layer_target_format(),
5931 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
5932 | wgpu::TextureUsages::TEXTURE_BINDING
5933 | wgpu::TextureUsages::COPY_SRC,
5934 view_formats: &[],
5935 });
5936 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
5937 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
5938 label: Some("layer bind"),
5939 layout: &self.image_bind_layout_rgba,
5940 entries: &[
5941 wgpu::BindGroupEntry {
5942 binding: 0,
5943 resource: wgpu::BindingResource::TextureView(&view),
5944 },
5945 wgpu::BindGroupEntry {
5946 binding: 1,
5947 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
5948 },
5949 ],
5950 });
5951 let bind_linear = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
5952 label: Some("layer bind linear"),
5953 layout: &self.image_bind_layout_rgba,
5954 entries: &[
5955 wgpu::BindGroupEntry {
5956 binding: 0,
5957 resource: wgpu::BindingResource::TextureView(&view),
5958 },
5959 wgpu::BindGroupEntry {
5960 binding: 1,
5961 resource: wgpu::BindingResource::Sampler(&self.layer_sampler_linear),
5962 },
5963 ],
5964 });
5965 let depth_stencil_view = self.create_layer_depth_stencil(width, height);
5966 self.layer_pool.insert(
5967 layer_id,
5968 LayerTarget {
5969 texture: tex,
5970 view,
5971 bind,
5972 bind_linear,
5973 depth_stencil_view,
5974 width,
5975 height,
5976 format: self.layer_target_format(),
5977 bytes,
5978 rect_px: (rect.x, rect.y, rect.w, rect.h),
5979 transient,
5980 },
5981 );
5982 self.layer_bytes_total = self.layer_bytes_total.saturating_add(bytes);
5983 if transient {
5984 self.transient_layer_bytes_total =
5985 self.transient_layer_bytes_total.saturating_add(bytes);
5986 }
5987 true
5988 }
5989
5990 fn acquire_blur_scratch(&mut self, layer_id: u32, slot: u32, pad: (f32, f32)) -> Option<u32> {
6000 let src = self.layer_pool.get(&layer_id)?;
6001 let width_f = (src.width as f32 + pad.0 * 2.0).ceil();
6002 let height_f = (src.height as f32 + pad.1 * 2.0).ceil();
6003 let max_dim = self.device.limits().max_texture_dimension_2d as f32;
6004 if !width_f.is_finite()
6005 || !height_f.is_finite()
6006 || width_f < 1.0
6007 || height_f < 1.0
6008 || width_f > max_dim
6009 || height_f > max_dim
6010 {
6011 log::warn!("blur scratch size {width_f}x{height_f} is invalid");
6012 return None;
6013 }
6014 let next = self.blur_scratch_next;
6015 let scratch_id = match self.blur_scratch_ids.get(&(layer_id, slot)) {
6016 Some(&id) => id,
6017 None => {
6018 let id = BLUR_SCRATCH_ID_BASE.wrapping_add(next);
6019 self.blur_scratch_next = next.wrapping_add(1);
6020 self.blur_scratch_ids.insert((layer_id, slot), id);
6021 id
6022 }
6023 };
6024 let allocated = self.get_or_create_layer(
6025 scratch_id,
6026 width_f as u32,
6027 height_f as u32,
6028 repose_core::Rect {
6029 x: 0.0,
6030 y: 0.0,
6031 w: width_f,
6032 h: height_f,
6033 },
6034 true,
6035 );
6036 allocated.then_some(scratch_id)
6037 }
6038
6039 #[allow(clippy::too_many_arguments)]
6058 fn emit_separable_blur(
6059 &mut self,
6060 source_layer: u32,
6061 slot: u32,
6062 sigma: [f32; 2],
6063 pad: (f32, f32),
6064 edge_mode: u32,
6065 alpha_only: bool,
6066 shape: [f32; 4],
6067 passes: &mut Vec<Pass>,
6068 current_pass: &mut Pass,
6069 next_pass_id: &mut u64,
6070 encoder: &mut wgpu::CommandEncoder,
6071 ) -> Option<u32> {
6072 let scratch_id = self.acquire_blur_scratch(source_layer, slot, pad)?;
6073 let (scratch_w, scratch_h, source_w, source_h) = {
6074 let scratch = self.layer_pool.get(&scratch_id)?;
6075 let source = self.layer_pool.get(&source_layer)?;
6076 (
6077 scratch.width,
6078 scratch.height,
6079 source.width as f32,
6080 source.height as f32,
6081 )
6082 };
6083 let uv = [
6089 -pad.0 / source_w,
6090 -pad.1 / source_h,
6091 1.0 + pad.0 / source_w,
6092 1.0 + pad.1 / source_h,
6093 ];
6094 let ndc = [-1.0, -1.0, 2.0, 2.0];
6095 let inst = BlurInstance {
6096 xywh: [ndc[0] + ndc[2] * 0.5, ndc[1] + ndc[3] * 0.5, ndc[2], ndc[3]],
6097 uv,
6098 color: [1.0, 1.0, 1.0, 1.0],
6099 sigma_px: sigma,
6100 fwd_mat: [1.0, 0.0, 0.0, 1.0],
6101 axis: 0,
6102 edge_mode,
6103 shape,
6104 };
6105 let cmd = match self.upload_blur_instance(&inst, encoder) {
6106 Some(off) if alpha_only => Cmd::CompositeShadow {
6107 off,
6108 cnt: 1,
6109 layer_id: source_layer,
6110 },
6111 Some(off) => Cmd::CompositeBlur {
6112 off,
6113 cnt: 1,
6114 layer_id: source_layer,
6115 },
6116 None => return None,
6117 };
6118 let target = current_pass.target;
6119 let scissor = current_pass.active_scissor;
6120 let resumed_pass_id = *next_pass_id;
6121 *next_pass_id += 1;
6122 let saved = std::mem::replace(
6123 current_pass,
6124 Pass {
6125 id: resumed_pass_id,
6126 target,
6127 initial_scissor: scissor.unwrap_or((0, 0, 1, 1)),
6128 active_scissor: scissor,
6129 clear_color: None,
6130 cmds: Vec::new(),
6131 },
6132 );
6133 passes.push(saved);
6134 let scratch_pass_id = *next_pass_id;
6135 *next_pass_id += 1;
6136 passes.push(Pass {
6137 id: scratch_pass_id,
6138 target: PassTarget::Layer(scratch_id),
6139 initial_scissor: (0, 0, scratch_w, scratch_h),
6140 active_scissor: Some((0, 0, scratch_w, scratch_h)),
6141 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
6142 cmds: vec![cmd],
6143 });
6144 Some(scratch_id)
6145 }
6146
6147 fn upload_blur_instance(
6148 &mut self,
6149 inst: &BlurInstance,
6150 encoder: &mut wgpu::CommandEncoder,
6151 ) -> Option<u64> {
6152 self.blur_ring
6153 .grow_to_fit(
6154 &self.device,
6155 encoder,
6156 std::mem::size_of::<BlurInstance>() as u64,
6157 )
6158 .ok()?;
6159 self.blur_ring
6160 .alloc_write(&self.queue, bytemuck::bytes_of(inst))
6161 .ok()
6162 }
6163 fn create_layer_depth_stencil(&self, width: u32, height: u32) -> wgpu::TextureView {
6164 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
6165 label: Some("layer depth-stencil"),
6166 size: wgpu::Extent3d {
6167 width: width.max(1),
6168 height: height.max(1),
6169 depth_or_array_layers: 1,
6170 },
6171 mip_level_count: 1,
6172 sample_count: 1,
6173 dimension: wgpu::TextureDimension::D2,
6174 format: wgpu::TextureFormat::Depth24PlusStencil8,
6175 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
6176 view_formats: &[],
6177 });
6178 tex.create_view(&wgpu::TextureViewDescriptor::default())
6179 }
6180
6181 fn ensure_globals_staging(&mut self, pass_count: usize) -> bool {
6182 let size = std::mem::size_of::<Globals>() as u64;
6183 let needed = match (pass_count.max(1) as u64).checked_mul(size) {
6184 Some(needed) => needed,
6185 None => return false,
6186 };
6187 if needed <= self.globals_staging_cap {
6188 return true;
6189 }
6190 let cap = needed
6191 .next_power_of_two()
6192 .min(self.device.limits().max_buffer_size);
6193 if cap < needed {
6194 return false;
6195 }
6196 self.globals_staging = self.device.create_buffer(&wgpu::BufferDescriptor {
6197 label: Some("globals staging"),
6198 size: cap,
6199 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
6200 mapped_at_creation: false,
6201 });
6202 self.globals_staging_cap = cap;
6203 true
6204 }
6205
6206 fn reset_atlas_storage(&mut self) {
6207 self.atlas_mask = init_atlas_mask(&self.device);
6208 self.atlas_color = init_atlas_color(&self.device);
6209 self.atlas_mask_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
6210 label: Some("atlas mask bind"),
6211 layout: &self.text_bind_layout,
6212 entries: &[
6213 wgpu::BindGroupEntry {
6214 binding: 0,
6215 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
6216 },
6217 wgpu::BindGroupEntry {
6218 binding: 1,
6219 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
6220 },
6221 ],
6222 });
6223 self.atlas_color_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
6224 label: Some("atlas color bind"),
6225 layout: &self.text_bind_layout,
6226 entries: &[
6227 wgpu::BindGroupEntry {
6228 binding: 0,
6229 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
6230 },
6231 wgpu::BindGroupEntry {
6232 binding: 1,
6233 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
6234 },
6235 ],
6236 });
6237 self.atlas_mask_pending = AtlasPending::default();
6238 self.atlas_color_pending = AtlasPending::default();
6239 self.atlas_mask_deferred.clear();
6240 self.atlas_color_deferred.clear();
6241 }
6242
6243 fn apply_atlas_pending(&mut self) {
6248 if self.atlas_mask_pending.grow {
6249 self.atlas_mask_pending.grow = false;
6250 let size = (self.atlas_mask.size * 2).min(MAX_GLYPH_ATLAS_SIZE);
6251 self.repack_mask(size);
6252 }
6253 if self.atlas_color_pending.grow {
6254 self.atlas_color_pending.grow = false;
6255 let size = (self.atlas_color.size * 2).min(MAX_GLYPH_ATLAS_SIZE);
6256 self.repack_color(size);
6257 }
6258 if self.frame_index.wrapping_sub(self.atlas_last_compact_frame)
6259 < ATLAS_COMPACT_MIN_FRAME_INTERVAL
6260 {
6261 return;
6262 }
6263 if self.atlas_mask_pending.compact && self.compact_atlas_mask() {
6266 self.atlas_mask_pending.compact = false;
6267 }
6268 if self.atlas_color_pending.compact && self.compact_atlas_color() {
6269 self.atlas_color_pending.compact = false;
6270 }
6271 }
6272
6273 fn compact_atlas_mask(&mut self) -> bool {
6278 let target = self.atlas_mask.used_area / ATLAS_COMPACT_KEEP_DEN * ATLAS_COMPACT_KEEP_NUM;
6279 if !evict_atlas_lru(
6280 &mut self.atlas_mask.map,
6281 &mut self.atlas_mask.used_area,
6282 self.frame_index,
6283 target,
6284 ) {
6285 return false;
6286 }
6287 let size = self.atlas_mask.size;
6288 self.repack_mask(size);
6289 self.atlas_last_compact_frame = self.frame_index;
6290 true
6291 }
6292
6293 fn compact_atlas_color(&mut self) -> bool {
6294 let target = self.atlas_color.used_area / ATLAS_COMPACT_KEEP_DEN * ATLAS_COMPACT_KEEP_NUM;
6295 if !evict_atlas_lru(
6296 &mut self.atlas_color.map,
6297 &mut self.atlas_color.used_area,
6298 self.frame_index,
6299 target,
6300 ) {
6301 return false;
6302 }
6303 let size = self.atlas_color.size;
6304 self.repack_color(size);
6305 self.atlas_last_compact_frame = self.frame_index;
6306 true
6307 }
6308
6309 fn repack_mask(&mut self, new_size: u32) {
6313 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
6314 label: Some("glyph atlas A8 (repacked)"),
6315 size: wgpu::Extent3d {
6316 width: new_size,
6317 height: new_size,
6318 depth_or_array_layers: 1,
6319 },
6320 mip_level_count: 1,
6321 sample_count: 1,
6322 dimension: wgpu::TextureDimension::D2,
6323 format: wgpu::TextureFormat::R8Unorm,
6324 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
6325 view_formats: &[],
6326 });
6327 self.atlas_mask.tex = tex;
6328 self.atlas_mask.view = self
6329 .atlas_mask
6330 .tex
6331 .create_view(&wgpu::TextureViewDescriptor::default());
6332 self.atlas_mask.size = new_size;
6333 self.atlas_mask.next_x = 1;
6334 self.atlas_mask.next_y = 1;
6335 self.atlas_mask.row_h = 0;
6336 self.atlas_mask.used_area = 0;
6337 self.atlas_mask_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
6338 label: Some("atlas mask bind"),
6339 layout: &self.text_bind_layout,
6340 entries: &[
6341 wgpu::BindGroupEntry {
6342 binding: 0,
6343 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
6344 },
6345 wgpu::BindGroupEntry {
6346 binding: 1,
6347 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
6348 },
6349 ],
6350 });
6351 let survivors: Vec<_> = self.atlas_mask.map.drain().collect();
6352 self.atlas_mask_deferred.clear();
6353 for (key, previous) in survivors {
6354 let _ = self.upload_glyph_mask(key.clone(), f32::from_bits(key.font_size_bits));
6355 if let Some(info) = self.atlas_mask.map.get_mut(&key) {
6356 info.last_touch = previous.last_touch;
6357 info.last_used_frame = previous.last_used_frame;
6358 }
6359 }
6360 }
6361
6362 fn repack_color(&mut self, new_size: u32) {
6364 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
6365 label: Some("glyph atlas RGBA (repacked)"),
6366 size: wgpu::Extent3d {
6367 width: new_size,
6368 height: new_size,
6369 depth_or_array_layers: 1,
6370 },
6371 mip_level_count: 1,
6372 sample_count: 1,
6373 dimension: wgpu::TextureDimension::D2,
6374 format: wgpu::TextureFormat::Rgba8UnormSrgb,
6375 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
6376 view_formats: &[],
6377 });
6378 self.atlas_color.tex = tex;
6379 self.atlas_color.view = self
6380 .atlas_color
6381 .tex
6382 .create_view(&wgpu::TextureViewDescriptor::default());
6383 self.atlas_color.size = new_size;
6384 self.atlas_color.next_x = 1;
6385 self.atlas_color.next_y = 1;
6386 self.atlas_color.row_h = 0;
6387 self.atlas_color.used_area = 0;
6388 self.atlas_color_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
6389 label: Some("atlas color bind"),
6390 layout: &self.text_bind_layout,
6391 entries: &[
6392 wgpu::BindGroupEntry {
6393 binding: 0,
6394 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
6395 },
6396 wgpu::BindGroupEntry {
6397 binding: 1,
6398 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
6399 },
6400 ],
6401 });
6402 let survivors: Vec<_> = self.atlas_color.map.drain().collect();
6403 self.atlas_color_deferred.clear();
6404 for (key, previous) in survivors {
6405 let _ = self.upload_glyph_color(key.clone(), f32::from_bits(key.font_size_bits));
6406 if let Some(info) = self.atlas_color.map.get_mut(&key) {
6407 info.last_touch = previous.last_touch;
6408 info.last_used_frame = previous.last_used_frame;
6409 }
6410 }
6411 }
6412
6413 fn sync_text_caches(&mut self) {
6414 let generation = repose_text::font_generation();
6415 if generation == self.atlas_generation {
6416 return;
6417 }
6418 self.atlas_generation = generation;
6419 self.reset_atlas_storage();
6420 self.atlas_mask_failures.clear();
6421 self.atlas_color_failures.clear();
6422 self.glyph_outline_cache.clear();
6423 self.legacy_shaped_text_cache.clear();
6424 self.slug_draw_cache.clear();
6425 self.slug_draw_cache_bytes_total = 0;
6426 self.slug_cache = slug::GlyphSlugCache::new();
6427 }
6428
6429 fn shape_text_cached(
6430 &mut self,
6431 text: &Arc<str>,
6432 px: f32,
6433 line_height_ratio: f32,
6434 font_family: Option<&'static str>,
6435 font_weight: u16,
6436 font_style: u8,
6437 letter_spacing: f32,
6438 variation: Option<&Arc<str>>,
6439 ) -> Arc<repose_text::SharedShapedText> {
6440 repose_text::shape_line_cached(
6441 text.as_ref(),
6442 px,
6443 line_height_ratio,
6444 font_family,
6445 font_weight,
6446 font_style,
6447 letter_spacing,
6448 variation.map(AsRef::as_ref),
6449 )
6450 }
6451
6452 fn legacy_shape_text_cached(
6453 &mut self,
6454 text: &Arc<str>,
6455 px: f32,
6456 line_height_ratio: f32,
6457 font_family: Option<&'static str>,
6458 font_weight: u16,
6459 font_style: u8,
6460 letter_spacing: f32,
6461 variation: Option<&Arc<str>>,
6462 ) -> Arc<[repose_text::ShapedGlyph]> {
6463 let key = TextShapeKey {
6464 text: text.clone(),
6465 px_bits: px.to_bits(),
6466 line_height_bits: line_height_ratio.to_bits(),
6467 font_family,
6468 font_weight,
6469 font_style,
6470 letter_spacing_bits: letter_spacing.to_bits(),
6471 variation: variation.cloned(),
6472 font_generation: self.atlas_generation,
6473 };
6474 if let Some(entry) = self.legacy_shaped_text_cache.get_mut(&key) {
6475 entry.last_used_frame = self.frame_index;
6476 return entry.glyphs.clone();
6477 }
6478 let glyphs: Arc<[repose_text::ShapedGlyph]> = Arc::from(repose_text::shape_line(
6479 text.as_ref(),
6480 px,
6481 line_height_ratio,
6482 font_family,
6483 font_weight,
6484 font_style,
6485 letter_spacing,
6486 variation.map(AsRef::as_ref),
6487 ));
6488 self.legacy_shaped_text_cache.insert(
6489 key,
6490 TextShapeCacheEntry {
6491 glyphs: glyphs.clone(),
6492 last_used_frame: self.frame_index,
6493 },
6494 );
6495 while self.legacy_shaped_text_cache.len() > MAX_LEGACY_SHAPED_TEXT_CACHE_ENTRIES {
6496 let Some(victim) = self
6497 .legacy_shaped_text_cache
6498 .iter()
6499 .min_by_key(|(_, entry)| entry.last_used_frame)
6500 .map(|(key, _)| key.clone())
6501 else {
6502 break;
6503 };
6504 self.legacy_shaped_text_cache.remove(&victim);
6505 }
6506 glyphs
6507 }
6508
6509 fn slug_draw_key(
6510 cache_key: &repose_text::CacheKey,
6511 fill: bool,
6512 stroke_key: Option<&slug::StrokeTessKey>,
6513 transform: &Transform,
6514 origin: (f32, f32),
6515 px: f32,
6516 target: (f32, f32),
6517 color: [f32; 4],
6518 ) -> SlugDrawKey {
6519 let linear = transform.linear();
6520 SlugDrawKey {
6521 cache_key: cache_key.clone(),
6522 fill,
6523 stroke: stroke_key.cloned(),
6524 transform: [
6525 linear[0].to_bits(),
6526 linear[1].to_bits(),
6527 linear[2].to_bits(),
6528 linear[3].to_bits(),
6529 transform.scale_x.to_bits(),
6530 transform.scale_y.to_bits(),
6531 transform.translate_x.to_bits(),
6532 transform.translate_y.to_bits(),
6533 transform.rotate.to_bits(),
6534 transform.shear_x.to_bits(),
6535 transform.shear_y.to_bits(),
6536 ],
6537 origin: [origin.0.to_bits(), origin.1.to_bits()],
6538 px_bits: px.to_bits(),
6539 target: [target.0.to_bits(), target.1.to_bits()],
6540 color: color.map(f32::to_bits),
6541 }
6542 }
6543
6544 fn evict_slug_draw_cache(&mut self) -> bool {
6545 let victim = self
6546 .slug_draw_cache
6547 .iter()
6548 .filter(|(_, entry)| entry.last_used_frame != self.frame_index)
6549 .min_by_key(|(_, entry)| entry.last_touch_frame)
6550 .map(|(key, _)| key.clone());
6551 let Some(victim) = victim else {
6552 return false;
6553 };
6554 let Some(entry) = self.slug_draw_cache.remove(&victim) else {
6555 return false;
6556 };
6557 self.slug_draw_cache_bytes_total =
6558 self.slug_draw_cache_bytes_total.saturating_sub(entry.bytes);
6559 true
6560 }
6561
6562 fn reserve_slug_draw_cache(&mut self, bytes: u64) -> bool {
6563 while self.slug_draw_cache.len() >= MAX_SLUG_DRAW_CACHE_ENTRIES
6564 || self.slug_draw_cache_bytes_total.saturating_add(bytes) > MAX_SLUG_DRAW_CACHE_BYTES
6565 {
6566 if !self.evict_slug_draw_cache() {
6567 return false;
6568 }
6569 }
6570 true
6571 }
6572
6573 fn get_cached_slug_vertices(&mut self, key: &SlugDrawKey) -> Option<Arc<[slug::TessVertex]>> {
6574 let touch = self.next_cache_touch();
6575 self.slug_draw_cache.get_mut(key).map(|entry| {
6576 entry.last_used_frame = self.frame_index;
6577 entry.last_touch_frame = touch;
6578 entry.vertices.clone()
6579 })
6580 }
6581
6582 fn cached_slug_vertices(
6583 &mut self,
6584 key: SlugDrawKey,
6585 vertices: Vec<slug::TessVertex>,
6586 ) -> Arc<[slug::TessVertex]> {
6587 let touch = self.next_cache_touch();
6588 if let Some(entry) = self.slug_draw_cache.get_mut(&key) {
6589 entry.last_used_frame = self.frame_index;
6590 entry.last_touch_frame = touch;
6591 return entry.vertices.clone();
6592 }
6593 let bytes = slug_draw_cache_entry_bytes(vertices.len());
6594 if bytes > MAX_SLUG_DRAW_CACHE_BYTES || !self.reserve_slug_draw_cache(bytes) {
6595 return Arc::from(vertices);
6596 }
6597 let touch = self.next_cache_touch();
6598 let vertices: Arc<[slug::TessVertex]> = Arc::from(vertices);
6599 self.slug_draw_cache.insert(
6600 key,
6601 SlugDrawCacheEntry {
6602 vertices: vertices.clone(),
6603 bytes,
6604 last_used_frame: self.frame_index,
6605 last_touch_frame: touch,
6606 },
6607 );
6608 self.slug_draw_cache_bytes_total = self.slug_draw_cache_bytes_total.saturating_add(bytes);
6609 vertices
6610 }
6611
6612 fn cached_glyph_outline(
6613 &mut self,
6614 cache_key: &repose_text::CacheKey,
6615 ) -> Option<Arc<[repose_text::Command]>> {
6616 let touch = self.next_cache_touch();
6617 if let Some(entry) = self.glyph_outline_cache.get_mut(cache_key) {
6618 entry.last_touch = touch;
6619 return entry.commands.clone();
6620 }
6621 let commands = repose_text::extract_outline_commands(cache_key).map(Arc::from);
6622 if self.glyph_outline_cache.len() >= MAX_GLYPH_OUTLINE_CACHE_ENTRIES {
6623 self.trim_glyph_outline_cache();
6624 }
6625 self.glyph_outline_cache.insert(
6626 cache_key.clone(),
6627 GlyphOutlineEntry {
6628 commands: commands.clone(),
6629 last_touch: touch,
6630 },
6631 );
6632 commands
6633 }
6634
6635 fn trim_glyph_outline_cache(&mut self) {
6638 let target = (MAX_GLYPH_OUTLINE_CACHE_ENTRIES / ATLAS_COMPACT_KEEP_DEN as usize)
6639 * ATLAS_COMPACT_KEEP_NUM as usize;
6640 let mut victims: Vec<(u64, repose_text::CacheKey)> = self
6641 .glyph_outline_cache
6642 .iter()
6643 .map(|(key, entry)| (entry.last_touch, key.clone()))
6644 .collect();
6645 victims.sort_unstable_by_key(|victim| victim.0);
6646 for (_, key) in victims {
6647 if self.glyph_outline_cache.len() <= target {
6648 break;
6649 }
6650 self.glyph_outline_cache.remove(&key);
6651 }
6652 }
6653
6654 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
6655 self.atlas_mask_bind.clone()
6656 }
6657
6658 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
6659 self.atlas_color_bind.clone()
6660 }
6661
6662 fn mark_atlas_mask_failure(&mut self, key: repose_text::CacheKey) {
6663 if self.atlas_mask_failures.len() >= MAX_GLYPH_ATLAS_ENTRIES {
6664 self.atlas_mask_failures.clear();
6665 }
6666 self.atlas_mask_failures.insert(key);
6667 }
6668
6669 fn mark_atlas_color_failure(&mut self, key: repose_text::CacheKey) {
6670 if self.atlas_color_failures.len() >= MAX_GLYPH_ATLAS_ENTRIES {
6671 self.atlas_color_failures.clear();
6672 }
6673 self.atlas_color_failures.insert(key);
6674 }
6675
6676 fn upload_glyph_mask(
6677 &mut self,
6678 cache_key: repose_text::CacheKey,
6679 px: f32,
6680 ) -> Option<GlyphInfo> {
6681 let px = quantize_glyph_px(px);
6682 let keyp = repose_text::CacheKey {
6683 font_size_bits: px.to_bits(),
6684 ..cache_key
6685 };
6686 let touch = self.next_cache_touch();
6687 if let Some(info) = self.atlas_mask.map.get_mut(&keyp) {
6688 info.last_touch = touch;
6689 info.last_used_frame = self.frame_index;
6690 return Some(*info);
6691 }
6692 if self.atlas_mask_failures.contains(&keyp) || self.atlas_mask_deferred.contains(&keyp) {
6693 return None;
6694 }
6695
6696 let Some(gb) = repose_text::rasterize_cache_key(&keyp) else {
6697 self.mark_atlas_mask_failure(keyp);
6698 return None;
6699 };
6700 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
6701 self.mark_atlas_mask_failure(keyp);
6702 return None;
6703 }
6704 let Some(coverage) = swash_to_a8_coverage(gb.content, &gb.data) else {
6705 self.mark_atlas_mask_failure(keyp);
6706 return None;
6707 };
6708
6709 let w = gb.w.max(1);
6710 let h = gb.h.max(1);
6711 let expected = checked_image_bytes_usize(w, h, 1).ok()?;
6712 if coverage.len() < expected {
6713 self.mark_atlas_mask_failure(keyp);
6714 return None;
6715 }
6716 if w >= MAX_GLYPH_ATLAS_SIZE || h >= MAX_GLYPH_ATLAS_SIZE {
6717 self.mark_atlas_mask_failure(keyp);
6718 return None;
6719 }
6720 if !self.alloc_or_schedule_mask(w, h) {
6721 defer_atlas_glyph(&mut self.atlas_mask_deferred, keyp);
6722 return None;
6723 }
6724 let x = self.atlas_mask.next_x;
6725 let y = self.atlas_mask.next_y;
6726 self.atlas_mask.next_x += w + 1;
6727 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
6728
6729 let layout = wgpu::TexelCopyBufferLayout {
6730 offset: 0,
6731 bytes_per_row: Some(w),
6732 rows_per_image: Some(h),
6733 };
6734 let size = wgpu::Extent3d {
6735 width: w,
6736 height: h,
6737 depth_or_array_layers: 1,
6738 };
6739 self.queue.write_texture(
6740 wgpu::TexelCopyTextureInfoBase {
6741 texture: &self.atlas_mask.tex,
6742 mip_level: 0,
6743 origin: wgpu::Origin3d { x, y, z: 0 },
6744 aspect: wgpu::TextureAspect::All,
6745 },
6746 &coverage,
6747 layout,
6748 size,
6749 );
6750
6751 let info = GlyphInfo {
6752 u0: x as f32 / self.atlas_mask.size as f32,
6753 v0: y as f32 / self.atlas_mask.size as f32,
6754 u1: (x + w) as f32 / self.atlas_mask.size as f32,
6755 v1: (y + h) as f32 / self.atlas_mask.size as f32,
6756 w: w as f32,
6757 h: h as f32,
6758 last_touch: touch,
6759 last_used_frame: self.frame_index,
6760 };
6761 self.atlas_mask.used_area = self
6762 .atlas_mask
6763 .used_area
6764 .saturating_add(u64::from(w).saturating_mul(u64::from(h)));
6765 self.atlas_mask.map.insert(keyp, info);
6766 Some(info)
6767 }
6768
6769 fn upload_glyph_color(
6770 &mut self,
6771 cache_key: repose_text::CacheKey,
6772 px: f32,
6773 ) -> Option<GlyphInfo> {
6774 let px = quantize_glyph_px(px);
6775 let keyp = repose_text::CacheKey {
6776 font_size_bits: px.to_bits(),
6777 ..cache_key
6778 };
6779 let touch = self.next_cache_touch();
6780 if let Some(info) = self.atlas_color.map.get_mut(&keyp) {
6781 info.last_touch = touch;
6782 info.last_used_frame = self.frame_index;
6783 return Some(*info);
6784 }
6785 if self.atlas_color_failures.contains(&keyp) || self.atlas_color_deferred.contains(&keyp) {
6786 return None;
6787 }
6788 let Some(gb) = repose_text::rasterize_cache_key(&keyp) else {
6789 self.mark_atlas_color_failure(keyp);
6790 return None;
6791 };
6792 if !matches!(gb.content, repose_text::SwashContent::Color) {
6793 self.mark_atlas_color_failure(keyp);
6794 return None;
6795 }
6796 let w = gb.w.max(1);
6797 let h = gb.h.max(1);
6798 let expected = checked_image_bytes_usize(w, h, 4).ok()?;
6799 if gb.data.len() < expected {
6800 self.mark_atlas_color_failure(keyp);
6801 return None;
6802 }
6803 if w >= MAX_GLYPH_ATLAS_SIZE || h >= MAX_GLYPH_ATLAS_SIZE {
6804 self.mark_atlas_color_failure(keyp);
6805 return None;
6806 }
6807 if !self.alloc_or_schedule_color(w, h) {
6808 defer_atlas_glyph(&mut self.atlas_color_deferred, keyp);
6809 return None;
6810 }
6811 let x = self.atlas_color.next_x;
6812 let y = self.atlas_color.next_y;
6813 self.atlas_color.next_x += w + 1;
6814 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
6815
6816 let layout = wgpu::TexelCopyBufferLayout {
6817 offset: 0,
6818 bytes_per_row: Some(w * 4),
6819 rows_per_image: Some(h),
6820 };
6821 let size = wgpu::Extent3d {
6822 width: w,
6823 height: h,
6824 depth_or_array_layers: 1,
6825 };
6826 self.queue.write_texture(
6827 wgpu::TexelCopyTextureInfoBase {
6828 texture: &self.atlas_color.tex,
6829 mip_level: 0,
6830 origin: wgpu::Origin3d { x, y, z: 0 },
6831 aspect: wgpu::TextureAspect::All,
6832 },
6833 &gb.data,
6834 layout,
6835 size,
6836 );
6837 let info = GlyphInfo {
6838 u0: x as f32 / self.atlas_color.size as f32,
6839 v0: y as f32 / self.atlas_color.size as f32,
6840 u1: (x + w) as f32 / self.atlas_color.size as f32,
6841 v1: (y + h) as f32 / self.atlas_color.size as f32,
6842 w: w as f32,
6843 h: h as f32,
6844 last_touch: touch,
6845 last_used_frame: self.frame_index,
6846 };
6847 self.atlas_color.used_area = self
6848 .atlas_color
6849 .used_area
6850 .saturating_add(u64::from(w).saturating_mul(u64::from(h)));
6851 self.atlas_color.map.insert(keyp, info);
6852 Some(info)
6853 }
6854
6855 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
6856 let Some(x_end) = self
6857 .atlas_mask
6858 .next_x
6859 .checked_add(w)
6860 .and_then(|v| v.checked_add(1))
6861 else {
6862 return false;
6863 };
6864 if x_end >= self.atlas_mask.size {
6865 self.atlas_mask.next_x = 1;
6866 let Some(next_y) = self
6867 .atlas_mask
6868 .next_y
6869 .checked_add(self.atlas_mask.row_h)
6870 .and_then(|v| v.checked_add(1))
6871 else {
6872 return false;
6873 };
6874 self.atlas_mask.next_y = next_y;
6875 self.atlas_mask.row_h = 0;
6876 }
6877 let Some(y_end) = self
6878 .atlas_mask
6879 .next_y
6880 .checked_add(h)
6881 .and_then(|v| v.checked_add(1))
6882 else {
6883 return false;
6884 };
6885 y_end < self.atlas_mask.size
6886 }
6887
6888 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
6889 let Some(x_end) = self
6890 .atlas_color
6891 .next_x
6892 .checked_add(w)
6893 .and_then(|v| v.checked_add(1))
6894 else {
6895 return false;
6896 };
6897 if x_end >= self.atlas_color.size {
6898 self.atlas_color.next_x = 1;
6899 let Some(next_y) = self
6900 .atlas_color
6901 .next_y
6902 .checked_add(self.atlas_color.row_h)
6903 .and_then(|v| v.checked_add(1))
6904 else {
6905 return false;
6906 };
6907 self.atlas_color.next_y = next_y;
6908 self.atlas_color.row_h = 0;
6909 }
6910 let Some(y_end) = self
6911 .atlas_color
6912 .next_y
6913 .checked_add(h)
6914 .and_then(|v| v.checked_add(1))
6915 else {
6916 return false;
6917 };
6918 y_end < self.atlas_color.size
6919 }
6920
6921 fn alloc_or_schedule_mask(&mut self, w: u32, h: u32) -> bool {
6926 if self.atlas_mask.map.len() >= MAX_GLYPH_ATLAS_ENTRIES
6927 || self
6928 .atlas_mask
6929 .used_area
6930 .saturating_add(u64::from(w) * u64::from(h))
6931 >= self.atlas_mask.size as u64 * self.atlas_mask.size as u64
6932 {
6933 self.atlas_mask_pending.compact = true;
6934 return false;
6935 }
6936 if self.alloc_space_mask(w, h) {
6937 return true;
6938 }
6939 if self.atlas_mask.size < MAX_GLYPH_ATLAS_SIZE {
6940 self.atlas_mask_pending.grow = true;
6941 } else {
6942 self.atlas_mask_pending.compact = true;
6943 }
6944 false
6945 }
6946
6947 fn alloc_or_schedule_color(&mut self, w: u32, h: u32) -> bool {
6949 if self.atlas_color.map.len() >= MAX_GLYPH_ATLAS_ENTRIES
6950 || self
6951 .atlas_color
6952 .used_area
6953 .saturating_add(u64::from(w) * u64::from(h))
6954 >= self.atlas_color.size as u64 * self.atlas_color.size as u64
6955 {
6956 self.atlas_color_pending.compact = true;
6957 return false;
6958 }
6959 if self.alloc_space_color(w, h) {
6960 return true;
6961 }
6962 if self.atlas_color.size < MAX_GLYPH_ATLAS_SIZE {
6963 self.atlas_color_pending.grow = true;
6964 } else {
6965 self.atlas_color_pending.compact = true;
6966 }
6967 false
6968 }
6969}
6970
6971fn pack_shape_bits(brush_type: u32, grad_kind: u32, tile_mode: u32, cap: u32) -> u32 {
6974 debug_assert!(brush_type < 4 && grad_kind < 4 && tile_mode < 4 && cap < 4);
6975 (brush_type & 3) | ((grad_kind & 3) << 2) | ((tile_mode & 3) << 4) | ((cap & 3) << 6)
6976}
6977
6978fn brush_to_shape_fields(
6992 brush: &Brush,
6993 rect: &repose_core::Rect,
6994 transform: &Transform,
6995) -> (u32, u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2], u32) {
6996 let to_local = |p: Vec2| {
6997 let m = transform.linear();
6998 let det = m[0] * m[3] - m[1] * m[2];
6999 if det.abs() < 1e-12 {
7000 return [p.x, p.y];
7001 }
7002 [
7003 (m[3] * p.x - m[1] * p.y) / det,
7004 (-m[2] * p.x + m[0] * p.y) / det,
7005 ]
7006 };
7007 match brush {
7008 Brush::Solid(c) => (
7009 0u32,
7010 0u32,
7011 c.to_linear(),
7012 [0.0; 4],
7013 [0.0; 2],
7014 [0.0; 2],
7015 0u32,
7016 ),
7017 Brush::Linear {
7018 start,
7019 end,
7020 start_color,
7021 end_color,
7022 } => (
7023 1u32,
7024 0u32,
7025 start_color.to_linear(),
7026 end_color.to_linear(),
7027 to_local(*start),
7028 to_local(*end),
7029 0u32,
7030 ),
7031 Brush::LinearNormalized {
7032 start,
7033 end,
7034 start_color,
7035 end_color,
7036 } => (
7037 1u32,
7038 0u32,
7039 start_color.to_linear(),
7040 end_color.to_linear(),
7041 to_local(Vec2 {
7042 x: start.x * rect.w,
7043 y: start.y * rect.h,
7044 }),
7045 to_local(Vec2 {
7046 x: end.x * rect.w,
7047 y: end.y * rect.h,
7048 }),
7049 0u32,
7050 ),
7051 Brush::Radial {
7052 center,
7053 radius,
7054 start_color,
7055 end_color,
7056 } => (
7057 1u32,
7058 1u32,
7059 start_color.to_linear(),
7060 end_color.to_linear(),
7061 to_local(*center),
7062 [radius.max(0.0), 0.0],
7063 0u32,
7064 ),
7065 Brush::Sweep {
7066 center,
7067 start_color,
7068 end_color,
7069 } => (
7070 1u32,
7071 2u32,
7072 start_color.to_linear(),
7073 end_color.to_linear(),
7074 to_local(*center),
7075 [0.0, 0.0],
7076 0u32,
7077 ),
7078 _ => (0u32, 0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2], 0u32),
7079 }
7080}
7081
7082fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
7083 match brush {
7084 Brush::Solid(c) => (
7085 0u32,
7086 c.to_linear(),
7087 [0.0, 0.0, 0.0, 0.0],
7088 [0.0, 0.0],
7089 [0.0, 1.0],
7090 ),
7091 Brush::Linear {
7092 start,
7093 end,
7094 start_color,
7095 end_color,
7096 } => (
7097 1u32,
7098 start_color.to_linear(),
7099 end_color.to_linear(),
7100 [start.x, start.y],
7101 [end.x, end.y],
7102 ),
7103 Brush::LinearNormalized {
7104 start,
7105 end,
7106 start_color,
7107 end_color,
7108 } => (
7109 1u32,
7110 start_color.to_linear(),
7111 end_color.to_linear(),
7112 [start.x, start.y],
7113 [end.x, end.y],
7114 ),
7115 Brush::Radial { start_color, .. } => (
7116 0u32,
7117 start_color.to_linear(),
7118 [0.0, 0.0, 0.0, 0.0],
7119 [0.0, 0.0],
7120 [0.0, 1.0],
7121 ),
7122 Brush::Sweep { start_color, .. } => (
7123 0u32,
7124 start_color.to_linear(),
7125 [0.0, 0.0, 0.0, 0.0],
7126 [0.0, 0.0],
7127 [0.0, 1.0],
7128 ),
7129 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
7130 }
7131}
7132
7133#[allow(dead_code)]
7136fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
7137 match brush {
7138 Brush::Solid(c) => c.to_linear(),
7139 Brush::Linear { start_color, .. } => start_color.to_linear(),
7140 Brush::LinearNormalized { start_color, .. } => start_color.to_linear(),
7141 Brush::Radial { start_color, .. } => start_color.to_linear(),
7142 Brush::Sweep { start_color, .. } => start_color.to_linear(),
7143 _ => [0.0; 4],
7144 }
7145}
7146
7147fn init_atlas_mask(device: &wgpu::Device) -> AtlasA8 {
7148 let size = 1024u32;
7149 let tex = device.create_texture(&wgpu::TextureDescriptor {
7150 label: Some("glyph atlas A8"),
7151 size: wgpu::Extent3d {
7152 width: size,
7153 height: size,
7154 depth_or_array_layers: 1,
7155 },
7156 mip_level_count: 1,
7157 sample_count: 1,
7158 dimension: wgpu::TextureDimension::D2,
7159 format: wgpu::TextureFormat::R8Unorm,
7160 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
7161 view_formats: &[],
7162 });
7163 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
7164 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
7165 label: Some("glyph atlas sampler A8"),
7166 address_mode_u: wgpu::AddressMode::ClampToEdge,
7167 address_mode_v: wgpu::AddressMode::ClampToEdge,
7168 address_mode_w: wgpu::AddressMode::ClampToEdge,
7169 mag_filter: wgpu::FilterMode::Linear,
7170 min_filter: wgpu::FilterMode::Linear,
7171 mipmap_filter: wgpu::MipmapFilterMode::Linear,
7172 ..Default::default()
7173 });
7174
7175 AtlasA8 {
7176 tex,
7177 view,
7178 sampler,
7179 size,
7180 next_x: 1,
7181 next_y: 1,
7182 row_h: 0,
7183 map: HashMap::new(),
7184 used_area: 0,
7185 }
7186}
7187
7188fn init_atlas_color(device: &wgpu::Device) -> AtlasRGBA {
7189 let size = 1024u32;
7190 let tex = device.create_texture(&wgpu::TextureDescriptor {
7191 label: Some("glyph atlas RGBA"),
7192 size: wgpu::Extent3d {
7193 width: size,
7194 height: size,
7195 depth_or_array_layers: 1,
7196 },
7197 mip_level_count: 1,
7198 sample_count: 1,
7199 dimension: wgpu::TextureDimension::D2,
7200 format: wgpu::TextureFormat::Rgba8UnormSrgb,
7201 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
7202 view_formats: &[],
7203 });
7204 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
7205 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
7206 label: Some("glyph atlas sampler RGBA"),
7207 address_mode_u: wgpu::AddressMode::ClampToEdge,
7208 address_mode_v: wgpu::AddressMode::ClampToEdge,
7209 address_mode_w: wgpu::AddressMode::ClampToEdge,
7210 mag_filter: wgpu::FilterMode::Linear,
7211 min_filter: wgpu::FilterMode::Linear,
7212 mipmap_filter: wgpu::MipmapFilterMode::Linear,
7213 ..Default::default()
7214 });
7215 AtlasRGBA {
7216 tex,
7217 view,
7218 sampler,
7219 size,
7220 next_x: 1,
7221 next_y: 1,
7222 row_h: 0,
7223 map: HashMap::new(),
7224 used_area: 0,
7225 }
7226}
7227
7228#[cfg(feature = "winit-surface")]
7229impl WgpuSurfaceBackend {
7230 pub fn take_surface(&mut self) -> Option<wgpu::Surface<'static>> {
7233 self.surface.take()
7234 }
7235
7236 pub fn recreate_surface(&mut self, window: &Arc<winit::window::Window>) -> anyhow::Result<()> {
7240 self.window = Some(window.clone());
7241 let size = window.inner_size();
7242 if size.width == 0 || size.height == 0 {
7243 self.surface = None;
7244 self.pending_reconfigure = false;
7245 self.renderer.resize(0, 0);
7246 anyhow::bail!("window has zero size; surface recreation deferred");
7247 }
7248 let Some(instance) = self.instance.as_ref() else {
7249 anyhow::bail!("no wgpu instance retained; cannot recreate surface")
7250 };
7251 let Some(config) = self.surface_config.as_mut() else {
7252 anyhow::bail!("no surface config retained; cannot recreate surface")
7253 };
7254 let (width, height) = clamp_output_size(
7255 size.width,
7256 size.height,
7257 self.renderer.device.limits().max_texture_dimension_2d,
7258 );
7259 config.width = width;
7260 config.height = height;
7261 let surface = instance.create_surface(window.clone())?;
7262 surface.configure(&self.renderer.device, config);
7263 self.surface = Some(surface);
7264 self.pending_reconfigure = false;
7265 self.renderer.resize(width, height);
7266 Ok(())
7267 }
7268}
7269
7270#[cfg(feature = "winit-surface")]
7271impl RenderBackend for WgpuSurfaceBackend {
7272 fn configure_surface(&mut self, width: u32, height: u32) -> (u32, u32) {
7273 if width == 0 || height == 0 {
7274 self.renderer.resize(0, 0);
7275 self.pending_reconfigure = true;
7276 return (0, 0);
7277 }
7278 let (width, height) = clamp_output_size(
7279 width,
7280 height,
7281 self.renderer.device.limits().max_texture_dimension_2d,
7282 );
7283 if self.renderer.output_width == width && self.renderer.output_height == height {
7284 if let Some(ref mut config) = self.surface_config {
7285 config.width = width;
7286 config.height = height;
7287 }
7288 return (width, height);
7289 }
7290 if let Some(ref mut config) = self.surface_config {
7291 config.width = width;
7292 config.height = height;
7293 }
7294 self.renderer.resize(width, height);
7295 if let (Some(surface), Some(config)) = (self.surface.as_ref(), self.surface_config.as_ref())
7296 {
7297 surface.configure(&self.renderer.device, config);
7298 }
7299 (width, height)
7300 }
7301
7302 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) -> bool {
7303 if self.pending_reconfigure {
7304 if let (Some(surface), Some(config)) =
7305 (self.surface.as_ref(), self.surface_config.as_ref())
7306 {
7307 surface.configure(&self.renderer.device, config);
7308 }
7309 self.pending_reconfigure = false;
7310 }
7311
7312 if self.renderer.output_width == 0 || self.renderer.output_height == 0 {
7313 request_frame();
7314 return false;
7315 }
7316 if let Err(error) = self.renderer.begin_frame() {
7317 log::warn!("begin renderer frame: {error:#}");
7318 request_frame();
7319 return false;
7320 }
7321
7322 let Some(surface) = self.surface.as_ref() else {
7323 self.renderer.end_frame();
7324 request_frame();
7325 return false;
7326 };
7327 let frame = match surface.get_current_texture() {
7328 wgpu::CurrentSurfaceTexture::Success(f) => f,
7329 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
7330 self.pending_reconfigure = true;
7331 f
7332 }
7333 other @ (wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost) => {
7334 self.surface = None;
7335 let result = self
7336 .window
7337 .clone()
7338 .ok_or_else(|| anyhow::anyhow!("no window retained for surface recreation"))
7339 .and_then(|window| self.recreate_surface(&window));
7340 if let Err(error) = result {
7341 log::warn!("surface {other:?}; recreation deferred: {error:#}");
7342 }
7343 self.renderer.end_frame();
7344 request_frame();
7345 return false;
7346 }
7347 other => {
7348 match other {
7349 wgpu::CurrentSurfaceTexture::Validation => {
7350 log::warn!("surface {other:?}; reconfiguring next frame");
7351 self.pending_reconfigure = true;
7352 }
7353 wgpu::CurrentSurfaceTexture::Timeout
7354 | wgpu::CurrentSurfaceTexture::Occluded => {
7355 log::debug!("surface {other:?}; retrying next frame");
7356 }
7357 _ => {}
7358 }
7359 self.renderer.end_frame();
7360 request_frame();
7361 return false;
7362 }
7363 };
7364
7365 let (swap_w, swap_h) = (frame.texture.width(), frame.texture.height());
7374 if swap_w != self.renderer.output_width || swap_h != self.renderer.output_height {
7375 log::debug!(
7376 "swapchain {swap_w}x{swap_h} != renderer {}x{}; resyncing",
7377 self.renderer.output_width,
7378 self.renderer.output_height
7379 );
7380 drop(frame);
7381 if let Some(config) = self.surface_config.as_mut() {
7382 config.width = swap_w;
7383 config.height = swap_h;
7384 }
7385 self.renderer.resize(swap_w, swap_h);
7386 self.pending_reconfigure = true;
7387 self.renderer.end_frame();
7388 request_frame();
7389 return false;
7390 }
7391
7392 let swap_view = if let Some(view_format) = self
7393 .surface_config
7394 .as_ref()
7395 .and_then(|c| c.view_formats.iter().find(|f| f.is_srgb()).copied())
7396 {
7397 frame.texture.create_view(&wgpu::TextureViewDescriptor {
7398 format: Some(view_format),
7399 ..Default::default()
7400 })
7401 } else {
7402 frame
7403 .texture
7404 .create_view(&wgpu::TextureViewDescriptor::default())
7405 };
7406 let mut encoder =
7407 self.renderer
7408 .device
7409 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
7410 label: Some("frame encoder"),
7411 });
7412
7413 self.renderer.render_scene_to_encoder_with_texture(
7414 scene,
7415 &mut encoder,
7416 &swap_view,
7417 Some(&frame.texture),
7418 None,
7419 );
7420
7421 {
7424 let _reset = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
7425 label: Some("webgl color_mask reset before present"),
7426 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
7427 view: &swap_view,
7428 resolve_target: None,
7429 ops: wgpu::Operations {
7430 load: wgpu::LoadOp::Load,
7431 store: wgpu::StoreOp::Store,
7432 },
7433 depth_slice: None,
7434 })],
7435 depth_stencil_attachment: None,
7436 timestamp_writes: None,
7437 occlusion_query_set: None,
7438 multiview_mask: None,
7439 });
7440 }
7441
7442 self.renderer
7443 .queue
7444 .submit(std::iter::once(encoder.finish()));
7445 if let Err(e) = catch_unwind(AssertUnwindSafe(|| self.renderer.queue.present(frame))) {
7446 log::warn!("queue.present panicked: {:?}", e);
7447 self.renderer.end_frame();
7448 request_frame();
7449 return false;
7450 }
7451 self.renderer.end_frame();
7452 true
7453 }
7454}
7455
7456impl WgpuSceneRenderer {
7457 #[allow(clippy::too_many_arguments)]
7469 fn replay_active_clips(
7470 &mut self,
7471 clips: &[ActiveClip],
7472 parent_transform: &Transform,
7473 origin: (f32, f32),
7474 target_size: (f32, f32),
7475 pass: &mut Pass,
7476 encoder: &mut wgpu::CommandEncoder,
7477 ) -> Vec<ActiveClip> {
7478 let mut current = repose_core::Rect {
7479 x: 0.0,
7480 y: 0.0,
7481 w: target_size.0,
7482 h: target_size.1,
7483 };
7484 let mut replayed = Vec::with_capacity(clips.len());
7485 for (index, clip) in clips.iter().enumerate() {
7486 let has_inverse = clips[..index].iter().any(ActiveClip::difference);
7487 let mut blocked = clip.blocked();
7488 if has_inverse && !blocked {
7489 log::error!(
7490 "unsupported clip nesting: a Difference clip cannot contain another clip"
7491 );
7492 blocked = true;
7493 }
7494 let difference = clip.difference();
7495 let mut command = None;
7496 match clip {
7497 ActiveClip::Rect {
7498 off: _,
7499 cnt: _,
7500 rect,
7501 radii,
7502 ..
7503 } => {
7504 let local_rect = parent_rect_to_layer_rect(*rect, parent_transform, origin)
7505 .unwrap_or(repose_core::Rect {
7506 x: 0.0,
7507 y: 0.0,
7508 w: 0.0,
7509 h: 0.0,
7510 });
7511 let local_radii =
7512 parent_radii_to_layer_radii(*radii, parent_transform).unwrap_or([0.0; 4]);
7513 let next = if difference {
7514 current
7515 } else {
7516 intersect(current, local_rect)
7517 };
7518 let scissor = rect_to_scissor(
7519 next,
7520 target_size.0.max(1.0) as u32,
7521 target_size.1.max(1.0) as u32,
7522 );
7523 let has_area = local_rect.x.is_finite()
7524 && local_rect.y.is_finite()
7525 && local_rect.w.is_finite()
7526 && local_rect.h.is_finite()
7527 && local_rect.w > 0.0
7528 && local_rect.h > 0.0;
7529 if blocked || !has_area || (!difference && scissor.2 == 0) {
7530 blocked = blocked || !difference;
7531 } else {
7532 let ndc = rect_to_ndc(local_rect, target_size.0, target_size.1);
7533 let instance = ClipInstance {
7534 xywh: [ndc[0] + ndc[2] * 0.5, ndc[1] + ndc[3] * 0.5, ndc[2], ndc[3]],
7535 radii: local_radii,
7536 fwd_mat: [1.0, 0.0, 0.0, 1.0],
7537 };
7538 let bytes = bytemuck::bytes_of(&instance);
7539 if self
7540 .clip_ring
7541 .grow_to_fit(
7542 &self.device,
7543 encoder,
7544 u64::try_from(bytes.len()).unwrap_or(u64::MAX),
7545 )
7546 .is_ok()
7547 && let Ok(new_off) = self.clip_ring.alloc_write(&self.queue, bytes)
7548 {
7549 command = Some(Cmd::ClipPush {
7550 off: new_off,
7551 cnt: 1,
7552 scissor,
7553 difference,
7554 applied: true,
7555 });
7556 replayed.push(ActiveClip::Rect {
7557 off: new_off,
7558 cnt: 1,
7559 rect: local_rect,
7560 radii: local_radii,
7561 scissor,
7562 difference,
7563 applied: true,
7564 blocked: false,
7565 });
7566 } else {
7567 blocked = blocked || !difference;
7568 }
7569 }
7570 current = next;
7571 }
7572 ActiveClip::Vector {
7573 mesh,
7574 source,
7575 affine,
7576 ..
7577 } => {
7578 let local_affine =
7579 parent_affine_to_layer_affine(*affine, parent_transform, origin)
7580 .unwrap_or([0.0; 6]);
7581 let aabb = mesh_aabb(source, local_affine);
7582 let next = if difference {
7583 current
7584 } else {
7585 intersect(current, aabb)
7586 };
7587 let scissor = rect_to_scissor(
7588 next,
7589 target_size.0.max(1.0) as u32,
7590 target_size.1.max(1.0) as u32,
7591 );
7592 let has_area = aabb.x.is_finite()
7593 && aabb.y.is_finite()
7594 && aabb.w.is_finite()
7595 && aabb.h.is_finite()
7596 && aabb.w > 0.0
7597 && aabb.h > 0.0
7598 && mesh
7599 .as_ref()
7600 .is_some_and(|mesh| mesh.vertex_count > 0 && mesh.index_count > 0);
7601 if blocked || !has_area || (!difference && scissor.2 == 0) {
7602 blocked = blocked || !difference;
7603 } else if let Some(mesh_gpu) = mesh.clone()
7604 && let Some(uoff) = self.alloc_mesh_uniform(mesh_uniform_from_paint(
7605 local_affine,
7606 &repose_core::PaintDesc::Solid,
7607 ))
7608 {
7609 command = Some(Cmd::VectorClipPush {
7610 mesh: mesh_gpu.clone(),
7611 uoff,
7612 scissor,
7613 difference,
7614 applied: true,
7615 });
7616 replayed.push(ActiveClip::Vector {
7617 mesh: Some(mesh_gpu),
7618 source: source.clone(),
7619 uoff,
7620 affine: local_affine,
7621 scissor,
7622 difference,
7623 applied: true,
7624 blocked: false,
7625 });
7626 } else {
7627 blocked = blocked || !difference;
7628 }
7629 current = next;
7630 }
7631 }
7632 if blocked {
7633 current = repose_core::Rect {
7634 x: 0.0,
7635 y: 0.0,
7636 w: 0.0,
7637 h: 0.0,
7638 };
7639 }
7640 if command.is_none() {
7641 replayed.push(match clip {
7642 ActiveClip::Rect {
7643 rect,
7644 radii,
7645 scissor,
7646 difference,
7647 ..
7648 } => ActiveClip::Rect {
7649 off: 0,
7650 cnt: 0,
7651 rect: *rect,
7652 radii: *radii,
7653 scissor: *scissor,
7654 difference: *difference,
7655 applied: false,
7656 blocked,
7657 },
7658 ActiveClip::Vector {
7659 mesh,
7660 source,
7661 affine,
7662 scissor,
7663 difference,
7664 ..
7665 } => ActiveClip::Vector {
7666 mesh: mesh.clone(),
7667 source: source.clone(),
7668 uoff: 0,
7669 affine: parent_affine_to_layer_affine(*affine, parent_transform, origin)
7670 .unwrap_or([0.0; 6]),
7671 scissor: *scissor,
7672 difference: *difference,
7673 applied: false,
7674 blocked,
7675 },
7676 });
7677 }
7678 if let Some(command) = command {
7679 pass.cmds.push(command);
7680 }
7681 pass.active_scissor = if blocked {
7682 None
7683 } else {
7684 let scissor = rect_to_scissor(
7685 current,
7686 target_size.0.max(1.0) as u32,
7687 target_size.1.max(1.0) as u32,
7688 );
7689 (scissor.2 > 0 && scissor.3 > 0).then_some(scissor)
7690 };
7691 }
7692 replayed
7693 }
7694
7695 #[allow(clippy::too_many_arguments)]
7696 fn push_perspective_layer(
7697 &mut self,
7698 node: Transform,
7699 top: Transform,
7700 transform_stack: &mut Vec<Transform>,
7701 scissor_stack: &mut Vec<repose_core::Rect>,
7702 root_clip_rect: &mut repose_core::Rect,
7703 current_target_size: &mut (f32, f32),
7704 current_pass: &mut Pass,
7705 active_clips: &mut Vec<ActiveClip>,
7706 passes: &mut Vec<Pass>,
7707 target_stack: &mut Vec<PassTarget>,
7708 flatten_stack: &mut Vec<FlattenRecord>,
7709 id_head: &mut u32,
7710 pass_id_head: &mut u64,
7711 ids_used: &mut Vec<u32>,
7712 encoder: &mut wgpu::CommandEncoder,
7713 ) {
7714 let map =
7718 Transform::compose_projective(&top.projective_matrix(), &node.projective_matrix());
7719 let scr = scissor_stack.last().copied().unwrap_or(*root_clip_rect);
7720 let max_dimension = self.device.limits().max_texture_dimension_2d as f32;
7721 if !scr.w.is_finite()
7722 || !scr.h.is_finite()
7723 || scr.w < 1.0
7724 || scr.h < 1.0
7725 || scr.w > max_dimension
7726 || scr.h > max_dimension
7727 {
7728 return;
7729 }
7730 let w = scr.w.ceil();
7731 let h = scr.h.ceil();
7732 let layer_rect = repose_core::Rect {
7733 x: scr.x,
7734 y: scr.y,
7735 w,
7736 h,
7737 };
7738 if ids_used.len() >= MAX_TRANSIENT_GRAPHICS_LAYERS {
7739 return;
7740 }
7741 let layer_id = *id_head;
7742 *id_head = id_head.wrapping_add(1);
7743 ids_used.push(layer_id);
7744
7745 let stack_len = transform_stack.len();
7746 transform_stack.push(top);
7750 transform_stack.push(Transform::translate(-layer_rect.x, -layer_rect.y));
7751
7752 let saved_scissor_stack = std::mem::replace(
7753 scissor_stack,
7754 vec![repose_core::Rect {
7755 x: 0.0,
7756 y: 0.0,
7757 w,
7758 h,
7759 }],
7760 );
7761 let saved_root = std::mem::replace(
7762 root_clip_rect,
7763 repose_core::Rect {
7764 x: 0.0,
7765 y: 0.0,
7766 w,
7767 h,
7768 },
7769 );
7770 let saved_size = std::mem::replace(current_target_size, (w, h));
7771 let prev_target = current_pass.target;
7772 let saved_active_scissor = current_pass.active_scissor;
7773 let saved_clips = std::mem::take(active_clips);
7774 let layer_pass_id = *pass_id_head;
7775 *pass_id_head += 1;
7776 let mut layer_pass = Pass {
7777 id: layer_pass_id,
7778 target: PassTarget::Layer(layer_id),
7779 initial_scissor: (0, 0, w as u32, h as u32),
7780 active_scissor: Some((0, 0, w as u32, h as u32)),
7781 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
7782 cmds: Vec::new(),
7783 };
7784 let layer_clips = self.replay_active_clips(
7785 &saved_clips,
7786 &top,
7787 (layer_rect.x, layer_rect.y),
7788 (w, h),
7789 &mut layer_pass,
7790 encoder,
7791 );
7792 *active_clips = layer_clips;
7793 let saved = std::mem::replace(current_pass, layer_pass);
7794 passes.push(saved);
7795 target_stack.push(prev_target);
7796 self.get_or_create_layer(layer_id, w as u32, h as u32, layer_rect, true);
7797 *current_target_size = (w, h);
7798 flatten_stack.push(FlattenRecord {
7799 stack_len,
7800 layer_id,
7801 map,
7802 layer_rect,
7803 saved_scissor_stack,
7804 saved_root,
7805 saved_size,
7806 saved_active_scissor,
7807 saved_clips,
7808 });
7809 }
7810
7811 #[allow(clippy::too_many_arguments)]
7814 fn pop_perspective_layer(
7815 &mut self,
7816 rec: FlattenRecord,
7817 scissor_stack: &mut Vec<repose_core::Rect>,
7818 root_clip_rect: &mut repose_core::Rect,
7819 current_target_size: &mut (f32, f32),
7820 current_pass: &mut Pass,
7821 active_clips: &mut Vec<ActiveClip>,
7822 passes: &mut Vec<Pass>,
7823 target_stack: &mut Vec<PassTarget>,
7824 pass_id_head: &mut u64,
7825 encoder: &mut wgpu::CommandEncoder,
7826 ) {
7827 *scissor_stack = rec.saved_scissor_stack;
7828 *root_clip_rect = rec.saved_root;
7829 *current_target_size = rec.saved_size;
7830 *active_clips = rec.saved_clips;
7831 let resumed_pass_id = *pass_id_head;
7832 *pass_id_head += 1;
7833 let saved = std::mem::replace(
7834 current_pass,
7835 Pass {
7836 id: resumed_pass_id,
7837 target: target_stack.pop().unwrap_or(PassTarget::Surface),
7838 initial_scissor: rec.saved_active_scissor.unwrap_or((0, 0, 1, 1)),
7839 active_scissor: rec.saved_active_scissor,
7840 clear_color: None,
7841 cmds: Vec::new(),
7842 },
7843 );
7844 passes.push(saved);
7845
7846 let (tw, th) = rec.saved_size;
7850 let r = rec.layer_rect;
7851 let corners = [
7852 (r.x, r.y),
7853 (r.x + r.w, r.y),
7854 (r.x + r.w, r.y + r.h),
7855 (r.x, r.y + r.h),
7856 ];
7857 let mut ndc = [[0.0f32; 2]; 4];
7858 let mut ws = [1.0f32; 4];
7859 let mut all_behind = true;
7860 for (i, (x, y)) in corners.iter().enumerate() {
7861 let w_raw = rec.map[6] * x + rec.map[7] * y + rec.map[8];
7862 let w = if w_raw.abs() < 1e-6 {
7863 if w_raw < 0.0 { -1e-6 } else { 1e-6 }
7864 } else {
7865 w_raw
7866 };
7867 if w > 0.0 {
7868 all_behind = false;
7869 }
7870 let px = (rec.map[0] * x + rec.map[1] * y + rec.map[2]) / w;
7871 let py = (rec.map[3] * x + rec.map[4] * y + rec.map[5]) / w;
7872 ndc[i] = [px / tw * 2.0 - 1.0, 1.0 - py / th * 2.0];
7873 ws[i] = w;
7874 }
7875 if all_behind {
7876 return;
7879 }
7880 let Some(layer) = self.layer_pool.get(&rec.layer_id) else {
7881 return;
7882 };
7883 let uv_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
7884 let uv_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
7885 let inst = ProjectiveInstance {
7886 c0: ndc[0],
7887 c1: ndc[1],
7888 c2: ndc[2],
7889 c3: ndc[3],
7890 uv: [0.0, 0.0, uv_u1, uv_v1],
7891 w: ws,
7892 alpha: 1.0,
7893 _pad: [0.0; 3],
7894 };
7895 if self
7896 .projective_ring
7897 .grow_to_fit(
7898 &self.device,
7899 encoder,
7900 std::mem::size_of::<ProjectiveInstance>() as u64,
7901 )
7902 .is_err()
7903 {
7904 return;
7905 }
7906 let bytes = bytemuck::bytes_of(&inst);
7907 let Ok(off) = self.projective_ring.alloc_write(&self.queue, bytes) else {
7908 return;
7909 };
7910 current_pass.cmds.push(Cmd::CompositeProjective {
7911 off,
7912 cnt: 1,
7913 layer_id: rec.layer_id,
7914 });
7915 }
7916
7917 fn mesh_cache_key(mesh: &Arc<repose_core::VectorMeshData>) -> MeshCacheKey {
7918 MeshCacheKey {
7919 source: Arc::as_ptr(mesh) as usize,
7920 index: 0,
7921 kind: 1,
7922 vertex_len: mesh.vertices.len(),
7923 index_len: mesh.indices.len(),
7924 }
7925 }
7926
7927 fn overlay_mesh_cache_key(
7928 meshes: &Arc<[repose_core::VectorMeshData]>,
7929 index: usize,
7930 ) -> MeshCacheKey {
7931 let mesh = &meshes[index];
7932 MeshCacheKey {
7933 source: meshes.as_ptr() as usize,
7934 index,
7935 kind: 2,
7936 vertex_len: mesh.vertices.len(),
7937 index_len: mesh.indices.len(),
7938 }
7939 }
7940
7941 fn mesh_owner_matches(owner: &MeshOwner, other: &MeshOwner) -> bool {
7942 match (owner, other) {
7943 (MeshOwner::Object(a), MeshOwner::Object(b)) => Arc::ptr_eq(a, b),
7944 (MeshOwner::Overlay(a), MeshOwner::Overlay(b)) => Arc::ptr_eq(a, b),
7945 _ => false,
7946 }
7947 }
7948
7949 fn evict_mesh_cache(&mut self) -> bool {
7950 let victim = self
7951 .mesh_cache
7952 .iter()
7953 .filter(|(_, entry)| entry.last_used_frame != self.frame_index)
7954 .min_by_key(|(_, entry)| entry.last_touch_frame)
7955 .map(|(key, _)| *key);
7956 let Some(victim) = victim else {
7957 return false;
7958 };
7959 let Some(entry) = self.mesh_cache.remove(&victim) else {
7960 return false;
7961 };
7962 self.mesh_cache_bytes_total = self.mesh_cache_bytes_total.saturating_sub(entry.gpu.bytes);
7963 true
7964 }
7965
7966 fn reserve_mesh_cache(&mut self, bytes: u64) -> bool {
7967 while self.mesh_cache.len() >= MAX_MESH_CACHE_ENTRIES
7968 || self.mesh_cache_bytes_total.saturating_add(bytes) > MAX_MESH_CACHE_BYTES
7969 {
7970 if !self.evict_mesh_cache() {
7971 return false;
7972 }
7973 }
7974 true
7975 }
7976
7977 fn reserve_mesh_gpu_budget(&mut self, bytes: u64) -> bool {
7978 while self.gpu_bytes_total().saturating_add(bytes) > self.gpu_budget_bytes {
7979 if !self.evict_mesh_cache() {
7980 return false;
7981 }
7982 }
7983 true
7984 }
7985
7986 fn upload_mesh_geometry_with_owner(
7987 &mut self,
7988 mesh: &repose_core::VectorMeshData,
7989 key: MeshCacheKey,
7990 owner: MeshOwner,
7991 ) -> Option<MeshGpu> {
7992 let touch = self.next_cache_touch();
7993 let cached = self
7994 .mesh_cache
7995 .get(&key)
7996 .filter(|entry| Self::mesh_owner_matches(&entry.owner, &owner))
7997 .map(|entry| entry.gpu.clone());
7998 if let Some(gpu) = cached {
7999 if let Some(entry) = self.mesh_cache.get_mut(&key) {
8000 entry.last_used_frame = self.frame_index;
8001 entry.last_touch_frame = touch;
8002 }
8003 return Some(gpu);
8004 }
8005 if let Some(entry) = self.mesh_cache.remove(&key) {
8006 self.mesh_cache_bytes_total =
8007 self.mesh_cache_bytes_total.saturating_sub(entry.gpu.bytes);
8008 }
8009 if mesh.vertices.is_empty() || mesh.indices.is_empty() {
8010 log::warn!("vector mesh has no geometry");
8011 return None;
8012 }
8013 if !mesh.indices.len().is_multiple_of(3) {
8014 log::warn!("vector mesh index count is not a triangle list");
8015 return None;
8016 }
8017 let vertex_count = u32::try_from(mesh.vertices.len()).ok()?;
8018 let index_count = u32::try_from(mesh.indices.len()).ok()?;
8019 if mesh.indices.iter().any(|&index| index >= vertex_count) {
8020 log::warn!("vector mesh index is outside its vertex buffer");
8021 return None;
8022 }
8023 if mesh.vertices.iter().any(|vertex| {
8024 vertex.pos.iter().any(|value| !value.is_finite())
8025 || vertex.color.iter().any(|value| !value.is_finite())
8026 || vertex.uv.iter().any(|value| !value.is_finite())
8027 }) {
8028 log::warn!("vector mesh contains non-finite vertex data");
8029 return None;
8030 }
8031 let verts: Vec<MeshVertex> = mesh
8032 .vertices
8033 .iter()
8034 .map(|v| MeshVertex {
8035 pos: v.pos,
8036 color: v.color,
8037 uv: v.uv,
8038 })
8039 .collect();
8040 let vbytes = bytemuck::cast_slice(&verts);
8041 let ibytes = bytemuck::cast_slice(&mesh.indices[..]);
8042 let bytes = u64::try_from(vbytes.len())
8043 .ok()?
8044 .checked_add(u64::try_from(ibytes.len()).ok()?)?;
8045 let max_buffer_size = self.device.limits().max_buffer_size;
8046 if vbytes.len() as u64 > max_buffer_size || ibytes.len() as u64 > max_buffer_size {
8047 return None;
8048 }
8049 let cacheable = bytes <= MAX_MESH_CACHE_BYTES
8050 && self.reserve_mesh_cache(bytes)
8051 && self.reserve_mesh_gpu_budget(bytes);
8052 let vertices = self.device.create_buffer(&wgpu::BufferDescriptor {
8053 label: Some("vector mesh vertices"),
8054 size: vbytes.len().max(4) as u64,
8055 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
8056 mapped_at_creation: false,
8057 });
8058 let indices = self.device.create_buffer(&wgpu::BufferDescriptor {
8059 label: Some("vector mesh indices"),
8060 size: ibytes.len().max(4) as u64,
8061 usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
8062 mapped_at_creation: false,
8063 });
8064 self.queue.write_buffer(&vertices, 0, vbytes);
8065 self.queue.write_buffer(&indices, 0, ibytes);
8066 let gpu = MeshGpu {
8067 vertices,
8068 indices,
8069 vertex_count,
8070 index_count,
8071 bytes,
8072 };
8073 if cacheable {
8074 let touch = self.next_cache_touch();
8075 self.mesh_cache.insert(
8076 key,
8077 MeshCacheEntry {
8078 owner,
8079 gpu: gpu.clone(),
8080 last_used_frame: self.frame_index,
8081 last_touch_frame: touch,
8082 },
8083 );
8084 self.mesh_cache_bytes_total = self.mesh_cache_bytes_total.saturating_add(bytes);
8085 }
8086 Some(gpu)
8087 }
8088
8089 fn upload_mesh_geometry(&mut self, mesh: &Arc<repose_core::VectorMeshData>) -> Option<MeshGpu> {
8090 self.upload_mesh_geometry_with_owner(
8091 mesh,
8092 Self::mesh_cache_key(mesh),
8093 MeshOwner::Object(mesh.clone()),
8094 )
8095 }
8096
8097 fn upload_overlay_mesh(
8098 &mut self,
8099 meshes: &Arc<[repose_core::VectorMeshData]>,
8100 index: usize,
8101 ) -> Option<MeshGpu> {
8102 self.upload_mesh_geometry_with_owner(
8103 &meshes[index],
8104 Self::overlay_mesh_cache_key(meshes, index),
8105 MeshOwner::Overlay(meshes.clone()),
8106 )
8107 }
8108
8109 fn alloc_mesh_uniform(&mut self, u: MeshUniform) -> Option<u64> {
8110 let size = std::mem::size_of::<MeshUniform>() as u64;
8111 let next = self
8112 .mesh_uniform_head
8113 .checked_add(self.mesh_uniform_alignment)?;
8114 if next > self.mesh_uniform_cap || next > u64::from(u32::MAX) {
8115 log::warn!("mesh uniform buffer exhausted; remaining meshes skipped");
8116 return None;
8117 }
8118 let slot = self.mesh_uniform_head;
8119 if slot.checked_add(size)? > self.mesh_uniform_cap {
8120 return None;
8121 }
8122 self.queue
8123 .write_buffer(&self.mesh_uniform_buf, slot, bytemuck::bytes_of(&u));
8124 self.mesh_uniform_head = next;
8125 Some(slot)
8126 }
8127
8128 #[allow(clippy::too_many_arguments)]
8140 fn emit_isolated_blend(
8141 &mut self,
8142 mesh: std::sync::Arc<repose_core::VectorMeshData>,
8143 transform: [f32; 6],
8144 paint: repose_core::PaintDesc,
8145 blend: repose_core::BlendMode,
8146 current_transform: &repose_core::Transform,
8147 current_pass: &mut Pass,
8148 active_clips: &[ActiveClip],
8149 passes: &mut Vec<Pass>,
8150 id_head: &mut u32,
8151 pass_id_head: &mut u64,
8152 ids_used: &mut Vec<u32>,
8153 scissor: (u32, u32, u32, u32),
8154 encoder: &mut wgpu::CommandEncoder,
8155 fb_w: f32,
8156 fb_h: f32,
8157 ) {
8158 if scissor.2 == 0 || scissor.3 == 0 || active_clips.iter().any(ActiveClip::blocked) {
8159 return;
8160 }
8161 let parent_target = current_pass.target;
8162 let (parent_w, parent_h) = match parent_target {
8163 PassTarget::Surface => (fb_w, fb_h),
8164 PassTarget::Layer(id) => match self.layer_pool.get(&id) {
8165 Some(layer) => (layer.width as f32, layer.height as f32),
8166 None => return,
8167 },
8168 };
8169 let affine = combine_mesh_affine(current_transform, transform);
8170 let local_rect = intersect(
8171 mesh_aabb(&mesh, affine),
8172 repose_core::Rect {
8173 x: 0.0,
8174 y: 0.0,
8175 w: parent_w,
8176 h: parent_h,
8177 },
8178 );
8179 if !local_rect.x.is_finite()
8180 || !local_rect.y.is_finite()
8181 || !local_rect.w.is_finite()
8182 || !local_rect.h.is_finite()
8183 || local_rect.w <= 0.0
8184 || local_rect.h <= 0.0
8185 || local_rect.w > self.device.limits().max_texture_dimension_2d as f32
8186 || local_rect.h > self.device.limits().max_texture_dimension_2d as f32
8187 {
8188 return;
8189 }
8190 let layer_rect = repose_core::Rect {
8191 x: local_rect.x.floor(),
8192 y: local_rect.y.floor(),
8193 w: (local_rect.x + local_rect.w).ceil() - local_rect.x.floor(),
8194 h: (local_rect.y + local_rect.h).ceil() - local_rect.y.floor(),
8195 };
8196 let w = layer_rect.w;
8197 let h = layer_rect.h;
8198 let local_rect = layer_rect;
8199 if ids_used.len() >= MAX_TRANSIENT_GRAPHICS_LAYERS {
8200 return;
8201 }
8202 let layer_id = *id_head;
8203 *id_head = id_head.wrapping_add(1);
8204 ids_used.push(layer_id);
8205
8206 let snapshot_format = match parent_target {
8210 PassTarget::Surface if self.working_space => wgpu::TextureFormat::Rgba16Float,
8211 PassTarget::Surface => self.output_format,
8212 PassTarget::Layer(_) => self.layer_target_format(),
8213 };
8214 if !self.alloc_blend_snapshot(layer_id, w as u32, h as u32, snapshot_format) {
8215 log::warn!("blend snapshot budget exhausted; blend skipped");
8216 return;
8217 }
8218 let source_pass_id = *pass_id_head;
8219 *pass_id_head += 1;
8220 let resumed_pass_id = *pass_id_head;
8221 *pass_id_head += 1;
8222 self.blend_copies.push(BlendCopy {
8223 pass_id: resumed_pass_id,
8224 blend_id: layer_id,
8225 target: parent_target,
8226 region: layer_rect,
8227 });
8228
8229 let mut layer_pass = Pass {
8233 id: source_pass_id,
8234 target: PassTarget::Layer(layer_id),
8235 initial_scissor: (0, 0, w as u32, h as u32),
8236 active_scissor: Some((0, 0, w as u32, h as u32)),
8237 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
8238 cmds: Vec::new(),
8239 };
8240 self.replay_active_clips(
8241 active_clips,
8242 current_transform,
8243 (local_rect.x, local_rect.y),
8244 (w, h),
8245 &mut layer_pass,
8246 encoder,
8247 );
8248 self.get_or_create_layer(layer_id, w as u32, h as u32, layer_rect, true);
8249 let shift = repose_core::Transform::translate(-local_rect.x, -local_rect.y);
8250 let local = current_transform.combine(&shift);
8251 self.emit_vector_mesh(
8252 &local,
8253 &mesh,
8254 transform,
8255 &paint,
8256 repose_core::BlendMode::Alpha,
8257 &mut layer_pass.cmds,
8258 );
8259 let saved = std::mem::replace(
8260 current_pass,
8261 Pass {
8262 id: resumed_pass_id,
8263 target: parent_target,
8264 initial_scissor: scissor,
8265 active_scissor: Some(scissor),
8266 clear_color: None,
8267 cmds: Vec::new(),
8268 },
8269 );
8270 passes.push(layer_pass);
8271 passes.push(saved);
8272
8273 let ndc = {
8277 let cx = local_rect.x + local_rect.w * 0.5;
8278 let cy = local_rect.y + local_rect.h * 0.5;
8279 let ndc_cx = (cx / parent_w) * 2.0 - 1.0;
8280 let ndc_cy = 1.0 - (cy / parent_h) * 2.0;
8281 let ndc_w = (local_rect.w / parent_w) * 2.0;
8282 let ndc_h = (local_rect.h / parent_h) * 2.0;
8283 [ndc_cx, ndc_cy, ndc_w, ndc_h]
8284 };
8285 let inst = BlendInstance {
8286 xywh: ndc,
8287 uv: [0.0, 0.0, 1.0, 1.0],
8288 color: [1.0, 1.0, 1.0, 1.0],
8289 fwd_mat: [1.0, 0.0, 0.0, 1.0],
8290 mode: blend.shader_mode(),
8291 _pad: [0.0; 3],
8292 };
8293 if self
8294 .blend_ring
8295 .grow_to_fit(
8296 &self.device,
8297 encoder,
8298 std::mem::size_of::<BlendInstance>() as u64,
8299 )
8300 .is_err()
8301 {
8302 return;
8303 }
8304 let bytes = bytemuck::bytes_of(&inst);
8305 let Ok(off) = self.blend_ring.alloc_write(&self.queue, bytes) else {
8306 return;
8307 };
8308 current_pass.cmds.push(Cmd::BlendLayer {
8309 off,
8310 cnt: 1,
8311 src_layer: layer_id,
8312 dst_layer: Some(layer_id),
8313 parent: parent_target,
8314 scissor,
8315 });
8316 }
8317
8318 fn recycle_blend_snapshot(&mut self, snapshot: BlendSnapshot) {
8319 let key = snapshot.key;
8320 if let Some(old) = self.blend_snapshot_pool.insert(key, snapshot) {
8321 self.blend_snapshot_bytes_total =
8322 self.blend_snapshot_bytes_total.saturating_sub(old.bytes);
8323 }
8324 while self.blend_snapshot_bytes_total > MAX_BLEND_SNAPSHOT_BYTES
8325 || self.blend_snapshot_pool.len() > MAX_BLEND_SNAPSHOT_POOL_ENTRIES
8326 {
8327 let Some(victim) = self.blend_snapshot_pool.keys().next().copied() else {
8328 break;
8329 };
8330 if let Some(snapshot) = self.blend_snapshot_pool.remove(&victim) {
8331 self.blend_snapshot_bytes_total = self
8332 .blend_snapshot_bytes_total
8333 .saturating_sub(snapshot.bytes);
8334 }
8335 }
8336 }
8337
8338 fn recycle_blend_snapshots(&mut self) {
8339 for (_, snapshot) in std::mem::take(&mut self.blend_snapshots) {
8340 self.recycle_blend_snapshot(snapshot);
8341 }
8342 }
8343
8344 fn alloc_blend_snapshot(
8345 &mut self,
8346 layer_id: u32,
8347 w: u32,
8348 h: u32,
8349 format: wgpu::TextureFormat,
8350 ) -> bool {
8351 let key = BlendSnapshotKey {
8352 format,
8353 width: w,
8354 height: h,
8355 };
8356 if self
8357 .blend_snapshots
8358 .get(&layer_id)
8359 .is_some_and(|snapshot| snapshot.key == key)
8360 {
8361 return true;
8362 }
8363 if w == 0 || h == 0 {
8364 return false;
8365 }
8366 if let Some(old) = self.blend_snapshots.remove(&layer_id) {
8367 self.recycle_blend_snapshot(old);
8368 }
8369 if let Some(snapshot) = self.blend_snapshot_pool.remove(&key) {
8370 self.blend_snapshots.insert(layer_id, snapshot);
8371 return true;
8372 }
8373 let Ok(bytes) = texture_storage_bytes(format, w, h) else {
8374 return false;
8375 };
8376 if bytes > MAX_BLEND_SNAPSHOT_BYTES {
8377 return false;
8378 }
8379 while self.blend_snapshot_bytes_total.saturating_add(bytes) > MAX_BLEND_SNAPSHOT_BYTES
8380 || self.gpu_bytes_total().saturating_add(bytes) > self.gpu_budget_bytes
8381 {
8382 let Some(victim) = self.blend_snapshot_pool.keys().next().copied() else {
8383 return false;
8384 };
8385 let Some(snapshot) = self.blend_snapshot_pool.remove(&victim) else {
8386 return false;
8387 };
8388 self.blend_snapshot_bytes_total = self
8389 .blend_snapshot_bytes_total
8390 .saturating_sub(snapshot.bytes);
8391 }
8392 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
8393 label: Some("blend backdrop snapshot"),
8394 size: wgpu::Extent3d {
8395 width: w,
8396 height: h,
8397 depth_or_array_layers: 1,
8398 },
8399 mip_level_count: 1,
8400 sample_count: 1,
8401 dimension: wgpu::TextureDimension::D2,
8402 format,
8403 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
8404 view_formats: &[],
8405 });
8406 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
8407 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
8408 label: Some("blend snapshot bind"),
8409 layout: &self.image_bind_layout_rgba,
8410 entries: &[
8411 wgpu::BindGroupEntry {
8412 binding: 0,
8413 resource: wgpu::BindingResource::TextureView(&view),
8414 },
8415 wgpu::BindGroupEntry {
8416 binding: 1,
8417 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
8418 },
8419 ],
8420 });
8421 self.blend_snapshot_bytes_total = self.blend_snapshot_bytes_total.saturating_add(bytes);
8422 self.blend_snapshots.insert(
8423 layer_id,
8424 BlendSnapshot {
8425 key,
8426 texture: tex,
8427 bind,
8428 bytes,
8429 },
8430 );
8431 true
8432 }
8433
8434 fn remove_blend_snapshot(&mut self, layer_id: u32) {
8435 if let Some(snapshot) = self.blend_snapshots.remove(&layer_id) {
8436 self.recycle_blend_snapshot(snapshot);
8437 }
8438 }
8439
8440 fn blend_snapshot_texture(&self, layer_id: u32) -> Option<wgpu::Texture> {
8441 self.blend_snapshots
8442 .get(&layer_id)
8443 .map(|s| s.texture.clone())
8444 }
8445
8446 #[allow(clippy::too_many_arguments)]
8447 fn emit_vector_mesh(
8448 &mut self,
8449 current_transform: &Transform,
8450 mesh: &Arc<repose_core::VectorMeshData>,
8451 transform: [f32; 6],
8452 paint: &repose_core::PaintDesc,
8453 blend: repose_core::BlendMode,
8454 cmds: &mut Vec<Cmd>,
8455 ) {
8456 let affine = combine_mesh_affine(current_transform, transform);
8457 let Some(mesh_gpu) = self.upload_mesh_geometry(mesh) else {
8458 return;
8459 };
8460 let Some(uoff) = self.alloc_mesh_uniform(mesh_uniform_from_paint(affine, paint)) else {
8461 return;
8462 };
8463 cmds.push(Cmd::VectorMesh {
8464 mesh: mesh_gpu,
8465 uoff,
8466 blend,
8467 });
8468 }
8469
8470 fn touch_callback_scope(&mut self, scope: CallbackScopeKey) {
8471 self.callback_scope_clock = self.callback_scope_clock.wrapping_add(1);
8472 let tick = self.callback_scope_clock;
8473 self.callback_scope_uses.insert(
8474 scope,
8475 CallbackScopeUse {
8476 tick,
8477 frame: self.frame_index,
8478 },
8479 );
8480 }
8481
8482 fn remove_callback_scope(&mut self, scope: &CallbackScopeKey) {
8483 self.callback_scoped_resources.remove(scope);
8484 self.callback_scope_uses.remove(scope);
8485 self.callback_scope_payloads.remove(scope);
8486 }
8487
8488 fn evict_callback_scope(&mut self, protected: &HashSet<CallbackScopeKey>) -> bool {
8489 let candidate = self
8490 .callback_scoped_resources
8491 .keys()
8492 .filter(|scope| !protected.contains(*scope))
8493 .min_by_key(|scope| {
8494 self.callback_scope_uses
8495 .get(scope)
8496 .map(|usage| usage.tick)
8497 .unwrap_or(0)
8498 })
8499 .cloned();
8500 let Some(scope) = candidate else {
8501 return false;
8502 };
8503 self.remove_callback_scope(&scope);
8504 true
8505 }
8506
8507 fn prune_callback_scopes(&mut self) {
8508 let current_frame = self.frame_index;
8509 let mut dead: Vec<(CallbackScopeKey, u64)> = self
8510 .callback_scope_uses
8511 .iter()
8512 .filter_map(|(scope, usage)| {
8513 let payload_dead = self
8514 .callback_scope_payloads
8515 .get(scope)
8516 .is_none_or(|weak| weak.upgrade().is_none());
8517 (payload_dead && !scope.callback.reusable).then(|| (scope.clone(), usage.tick))
8518 })
8519 .collect();
8520 dead.sort_by_key(|(_, tick)| *tick);
8521 for (scope, _) in dead {
8522 self.remove_callback_scope(&scope);
8523 }
8524
8525 if self.callback_scoped_resources.len() <= MAX_CALLBACK_SCOPES {
8526 return;
8527 }
8528 let mut inactive: Vec<(CallbackScopeKey, u64)> = self
8529 .callback_scope_uses
8530 .iter()
8531 .filter(|(_, usage)| usage.frame != current_frame)
8532 .map(|(scope, usage)| (scope.clone(), usage.tick))
8533 .collect();
8534 inactive.sort_by_key(|(_, tick)| *tick);
8535 for (scope, _) in inactive {
8536 if self.callback_scoped_resources.len() <= MAX_CALLBACK_SCOPES {
8537 break;
8538 }
8539 self.remove_callback_scope(&scope);
8540 }
8541 }
8542
8543 pub fn begin_frame(&mut self) -> anyhow::Result<()> {
8544 if self.frame_active {
8545 self.last_render_error = Some("renderer frame is already active".into());
8546 anyhow::bail!("renderer frame is already active");
8547 }
8548 self.last_render_error = None;
8549 self.frame_active = true;
8550 self.frame_index = self.frame_index.wrapping_add(1);
8551 self.apply_atlas_pending();
8552 self.slug_cache.next_frame();
8553 if let Some(composite) = self.callback_resources.get_mut::<DepthComposite>() {
8554 composite.begin_frame();
8555 }
8556 for resources in self.callback_scoped_resources.values_mut() {
8557 if let Some(composite) = resources.get_mut::<DepthComposite>() {
8558 composite.begin_frame();
8559 }
8560 }
8561 Ok(())
8562 }
8563
8564 pub fn end_frame(&mut self) {
8565 if !self.frame_active {
8566 return;
8567 }
8568 if let Some(composite) = self.callback_resources.get_mut::<DepthComposite>() {
8569 composite.end_frame();
8570 }
8571 for resources in self.callback_scoped_resources.values_mut() {
8572 if let Some(composite) = resources.get_mut::<DepthComposite>() {
8573 composite.end_frame();
8574 }
8575 }
8576 self.prune_callback_scopes();
8577 self.evict_unused_images();
8578 self.frame_active = false;
8579 }
8580
8581 pub fn last_render_error(&self) -> Option<&str> {
8582 self.last_render_error.as_deref()
8583 }
8584
8585 pub fn render_scene_to_encoder(
8586 &mut self,
8587 scene: &Scene,
8588 encoder: &mut wgpu::CommandEncoder,
8589 target_view: &wgpu::TextureView,
8590 clear_color_override: Option<[f64; 4]>,
8591 ) {
8592 self.render_scene_to_encoder_with_texture(
8593 scene,
8594 encoder,
8595 target_view,
8596 None,
8597 clear_color_override,
8598 )
8599 }
8600
8601 pub fn render_scene_to_encoder_with_texture(
8607 &mut self,
8608 scene: &Scene,
8609 encoder: &mut wgpu::CommandEncoder,
8610 target_view: &wgpu::TextureView,
8611 target_texture: Option<&wgpu::Texture>,
8612 clear_color_override: Option<[f64; 4]>,
8613 ) {
8614 if self.output_width == 0 || self.output_height == 0 {
8615 return;
8616 }
8617 self.sync_text_caches();
8618 fn affine_aabb(transform: &Transform, rect: &repose_core::Rect) -> repose_core::Rect {
8621 let m = transform.linear();
8622 let (tx, ty) = (transform.translate_x, transform.translate_y);
8623 let corners = [
8624 (rect.x, rect.y),
8625 (rect.x + rect.w, rect.y),
8626 (rect.x, rect.y + rect.h),
8627 (rect.x + rect.w, rect.y + rect.h),
8628 ];
8629 let mut min_x = f32::INFINITY;
8630 let mut min_y = f32::INFINITY;
8631 let mut max_x = f32::NEG_INFINITY;
8632 let mut max_y = f32::NEG_INFINITY;
8633 for (x, y) in corners {
8634 let wx = m[0] * x + m[1] * y + tx;
8635 let wy = m[2] * x + m[3] * y + ty;
8636 min_x = min_x.min(wx);
8637 min_y = min_y.min(wy);
8638 max_x = max_x.max(wx);
8639 max_y = max_y.max(wy);
8640 }
8641 repose_core::Rect {
8642 x: min_x,
8643 y: min_y,
8644 w: (max_x - min_x).max(0.0),
8645 h: (max_y - min_y).max(0.0),
8646 }
8647 }
8648
8649 fn visible(aabb: repose_core::Rect, clip: repose_core::Rect) -> bool {
8650 if !aabb.x.is_finite()
8651 || !aabb.y.is_finite()
8652 || !aabb.w.is_finite()
8653 || !aabb.h.is_finite()
8654 || aabb.w <= 0.0
8655 || aabb.h <= 0.0
8656 {
8657 return false;
8658 }
8659 let aabb = repose_core::Rect {
8660 x: aabb.x - 1.0,
8661 y: aabb.y - 1.0,
8662 w: aabb.w + 2.0,
8663 h: aabb.h + 2.0,
8664 };
8665 let clipped = intersect(aabb, clip);
8666 clipped.x.is_finite()
8667 && clipped.y.is_finite()
8668 && clipped.w.is_finite()
8669 && clipped.h.is_finite()
8670 && clipped.w > 0.0
8671 && clipped.h > 0.0
8672 }
8673
8674 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
8675 let x0 = (x / fb_w) * 2.0 - 1.0;
8676 let y0 = 1.0 - (y / fb_h) * 2.0;
8677 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
8678 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
8679 let min_x = x0.min(x1);
8680 let min_y = y0.min(y1);
8681 let w_ndc = (x1 - x0).abs();
8682 let h_ndc = (y1 - y0).abs();
8683 [min_x, min_y, w_ndc, h_ndc]
8684 }
8685
8686 fn rect_to_instance_ndc(
8691 rect: repose_core::Rect,
8692 transform: &Transform,
8693 fb_w: f32,
8694 fb_h: f32,
8695 ) -> ([f32; 4], [f32; 4]) {
8696 let cx = rect.x + rect.w * 0.5;
8697 let cy = rect.y + rect.h * 0.5;
8698
8699 let m = transform.linear();
8700 let tx = m[0] * cx + m[1] * cy + transform.translate_x;
8701 let ty = m[2] * cx + m[3] * cy + transform.translate_y;
8702
8703 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
8704 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
8705 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
8707 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
8708
8709 ([ndc_cx, ndc_cy, ndc_w, ndc_h], forward_rs_mat(transform))
8710 }
8711
8712 fn forward_rs_mat(transform: &Transform) -> [f32; 4] {
8716 let c = transform.rotate.cos();
8717 let s = transform.rotate.sin();
8718 let (hx, hy) = (transform.shear_x, transform.shear_y);
8719 let m = [c - s * hy, c * hx - s, s + c * hy, s * hx + c];
8720 if (m[0] * m[3] - m[1] * m[2]).abs() < 1e-6 {
8721 return [1.0, 0.0, 0.0, 1.0];
8722 }
8723 m
8724 }
8725
8726 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
8727 if fb_w == 0
8728 || fb_h == 0
8729 || !r.x.is_finite()
8730 || !r.y.is_finite()
8731 || !r.w.is_finite()
8732 || !r.h.is_finite()
8733 || r.w <= 0.0
8734 || r.h <= 0.0
8735 {
8736 return (0, 0, 0, 0);
8737 }
8738 let x0 = r.x.ceil().max(0.0).min(fb_w as f32);
8743 let y0 = r.y.ceil().max(0.0).min(fb_h as f32);
8744 let x1 = (r.x + r.w).floor().max(x0).min(fb_w as f32);
8745 let y1 = (r.y + r.h).floor().max(y0).min(fb_h as f32);
8746 if x1 <= x0 || y1 <= y0 {
8747 return (0, 0, 0, 0);
8748 }
8749 (x0 as u32, y0 as u32, (x1 - x0) as u32, (y1 - y0) as u32)
8750 }
8751
8752 let fb_w = self.output_width as f32;
8753 let fb_h = self.output_height as f32;
8754
8755 let mut passes: Vec<Pass> = Vec::with_capacity(1);
8756 let clear_color = clear_color_override
8757 .map(|color| {
8758 let alpha = color[3];
8759 [color[0] * alpha, color[1] * alpha, color[2] * alpha, alpha]
8760 })
8761 .unwrap_or_else(|| {
8762 let color = scene.clear_color.to_linear();
8763 let alpha = color[3] as f64;
8764 [
8765 color[0] as f64 * alpha,
8766 color[1] as f64 * alpha,
8767 color[2] as f64 * alpha,
8768 alpha,
8769 ]
8770 });
8771 let mut next_pass_id = 0u64;
8772 let mut current_pass: Pass = Pass {
8773 id: next_pass_id,
8774 target: PassTarget::Surface,
8775 initial_scissor: (0, 0, self.output_width, self.output_height),
8776 active_scissor: Some((0, 0, self.output_width, self.output_height)),
8777 clear_color: Some([
8778 clear_color[0] as f32,
8779 clear_color[1] as f32,
8780 clear_color[2] as f32,
8781 clear_color[3] as f32,
8782 ]),
8783 cmds: Vec::with_capacity(scene.nodes.len()),
8784 };
8785 next_pass_id += 1;
8786 let mut target_stack: Vec<PassTarget> = Vec::new();
8787 let mut layer_stack: Vec<LayerState> = Vec::new();
8788 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
8789
8790 struct Batch {
8791 rects: Vec<RectInstance>,
8792 borders: Vec<BorderInstance>,
8793 ellipses: Vec<EllipseInstance>,
8794 e_borders: Vec<EllipseBorderInstance>,
8795 arcs: Vec<ArcInstance>,
8796 masks: Vec<GlyphInstance>,
8797 colors: Vec<GlyphInstance>,
8798 }
8799
8800 impl Batch {
8801 fn new() -> Self {
8802 Self {
8803 rects: vec![],
8804 borders: vec![],
8805 ellipses: vec![],
8806 e_borders: vec![],
8807 arcs: vec![],
8808 masks: vec![],
8809 colors: vec![],
8810 }
8811 }
8812
8813 fn is_empty(&self) -> bool {
8814 self.rects.is_empty()
8815 && self.borders.is_empty()
8816 && self.ellipses.is_empty()
8817 && self.e_borders.is_empty()
8818 && self.arcs.is_empty()
8819 && self.masks.is_empty()
8820 && self.colors.is_empty()
8821 }
8822
8823 fn flush(
8824 &mut self,
8825 pipes: (
8826 &mut InstancedPipe<RectInstance>,
8827 &mut InstancedPipe<BorderInstance>,
8828 &mut InstancedPipe<EllipseInstance>,
8829 &mut InstancedPipe<EllipseBorderInstance>,
8830 &mut InstancedPipe<ArcInstance>,
8831 ),
8832 glyph_pipes: (
8833 &mut InstancedPipe<GlyphInstance>,
8834 &mut InstancedPipe<GlyphInstance>,
8835 ),
8836 device: &wgpu::Device,
8837 queue: &wgpu::Queue,
8838 encoder: &mut wgpu::CommandEncoder,
8839 cmds: &mut Vec<Cmd>,
8840 ) {
8841 let (rects, borders, ellipses, e_borders, arcs) = pipes;
8842 let (masks, colors) = glyph_pipes;
8843
8844 macro_rules! flush_one {
8845 ($buf:ident, $pipe:expr, $variant:ident) => {
8846 if !self.$buf.is_empty() {
8847 if let Some((off, cnt)) =
8848 $pipe.upload(device, queue, encoder, &self.$buf)
8849 {
8850 cmds.push(Cmd::$variant { off, cnt });
8851 }
8852 self.$buf.clear();
8853 }
8854 };
8855 }
8856
8857 flush_one!(rects, rects, Rect);
8858 flush_one!(borders, borders, Border);
8859 flush_one!(ellipses, ellipses, Ellipse);
8860 flush_one!(e_borders, e_borders, EllipseBorder);
8861 flush_one!(arcs, arcs, Arc);
8862 flush_one!(masks, masks, GlyphsMask);
8863 flush_one!(colors, colors, GlyphsColor);
8864 }
8865 }
8866
8867 self.rects.reset();
8868 self.borders.reset();
8869 self.ellipses.reset();
8870 self.ellipse_borders.reset();
8871 self.arcs.reset();
8872 self.glyph_mask.reset();
8873 self.glyph_color.reset();
8874 self.clip_ring.reset();
8875 self.blur_ring.reset();
8876 self.nv12.reset();
8877
8878 self.slug_ring.reset();
8879 self.mesh_uniform_head = 0;
8880 self.projective_ring.reset();
8881 self.blend_ring.reset();
8882 for id in self.flatten_layer_ids.drain(..) {
8883 if let Some(layer) = self.layer_pool.remove(&id) {
8884 self.layer_bytes_total = self.layer_bytes_total.saturating_sub(layer.bytes);
8885 if layer.transient {
8886 self.transient_layer_bytes_total =
8887 self.transient_layer_bytes_total.saturating_sub(layer.bytes);
8888 }
8889 }
8890 }
8891 self.producer_layer_ids.clear();
8892 self.recycle_blend_snapshots();
8893 self.blend_copies.clear();
8894 let mut batch = Batch::new();
8895 let mut image_run: Option<(u64, ImageFilter, bool, Vec<GlyphInstance>)> = None;
8896 let mut nv12_run: Option<(u64, ImageFilter, Vec<Nv12Instance>)> = None;
8897 let mut slug_verts_local: Vec<slug::TessVertex> = Vec::new();
8898 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
8899 let mut flatten_stack: Vec<FlattenRecord> = Vec::new();
8900 let mut flatten_id_head: u32 = FLATTEN_ID_BASE;
8901 let mut flatten_ids_used: Vec<u32> = Vec::new();
8902 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
8903 let mut active_clips: Vec<ActiveClip> = Vec::with_capacity(8);
8904 let mut root_clip_rect = repose_core::Rect {
8905 x: 0.0,
8906 y: 0.0,
8907 w: fb_w,
8908 h: fb_h,
8909 };
8910
8911 let mut current_prim: Option<&'static str> = None;
8912
8913 macro_rules! flush_if_prim_changed {
8914 ($prim:literal, $pipe:expr) => {
8915 if current_prim != Some($prim) {
8916 flush_batch!();
8917 current_prim = Some($prim);
8918 }
8919 };
8920 }
8921
8922 macro_rules! flush_image_runs {
8923 () => {{
8924 if let Some((handle, filter, tile, instances)) = image_run.take()
8925 && let Some((off, cnt)) =
8926 self.glyph_color
8927 .upload(&self.device, &self.queue, encoder, &instances)
8928 {
8929 current_pass.cmds.push(Cmd::ImageRgba {
8930 off,
8931 cnt,
8932 handle,
8933 filter,
8934 tile,
8935 });
8936 }
8937 if let Some((handle, filter, instances)) = nv12_run.take()
8938 && let Some((off, cnt)) =
8939 self.nv12
8940 .upload(&self.device, &self.queue, encoder, &instances)
8941 {
8942 current_pass.cmds.push(Cmd::ImageNv12 {
8943 off,
8944 cnt,
8945 handle,
8946 filter,
8947 });
8948 }
8949 }};
8950 }
8951
8952 macro_rules! flush_primitives {
8953 () => {{
8954 if !batch.is_empty() {
8955 batch.flush(
8956 (
8957 &mut self.rects,
8958 &mut self.borders,
8959 &mut self.ellipses,
8960 &mut self.ellipse_borders,
8961 &mut self.arcs,
8962 ),
8963 (&mut self.glyph_mask, &mut self.glyph_color),
8964 &self.device,
8965 &self.queue,
8966 encoder,
8967 &mut current_pass.cmds,
8968 )
8969 }
8970 }};
8971 }
8972
8973 macro_rules! flush_batch {
8974 () => {{
8975 flush_image_runs!();
8976 flush_primitives!();
8977 }};
8978 }
8979 let mut pending_layer_composite: Option<Cmd> = None;
8980 let shadow_layer_ids: HashSet<u32> = scene
8981 .nodes
8982 .iter()
8983 .filter_map(|node| match node {
8984 SceneNode::CompositeShadow { layer_id, .. } => Some(*layer_id),
8985 _ => None,
8986 })
8987 .collect();
8988 macro_rules! flush_pending_layer {
8989 () => {
8990 if let Some(command) = pending_layer_composite.take() {
8991 current_pass.cmds.push(command);
8992 }
8993 };
8994 }
8995 for node in &scene.nodes {
8996 let t_identity = Transform::identity();
8997 let current_transform = transform_stack.last().unwrap_or(&t_identity);
8998
8999 match node {
9000 SceneNode::Rect {
9001 rect,
9002 brush,
9003 radius,
9004 } => {
9005 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9006 if !visible(affine_aabb(current_transform, rect), clip) {
9007 continue;
9008 }
9009 flush_if_prim_changed!("rect", &self.rects);
9010 let (ndc, fwd_mat) = rect_to_instance_ndc(
9011 *rect,
9012 current_transform,
9013 current_target_size.0,
9014 current_target_size.1,
9015 );
9016 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
9017 brush_to_shape_fields(brush, rect, current_transform);
9018 batch.rects.push(RectInstance {
9019 xywh: ndc,
9020 radii: radius.map(|r| r.0),
9021 color0,
9022 color1,
9023 fwd_mat,
9024 grad: [grad_p0[0], grad_p0[1], grad_p1[0], grad_p1[1]],
9025 bits: pack_shape_bits(brush_type, grad_kind, tile_mode, 0),
9026 _pad: [0.0; 3],
9027 });
9028 }
9029 SceneNode::Border {
9030 rect,
9031 brush,
9032 width,
9033 radius,
9034 } => {
9035 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9036 if !visible(affine_aabb(current_transform, rect), clip) {
9037 continue;
9038 }
9039 flush_if_prim_changed!("border", &self.borders);
9040 let (ndc, fwd_mat) = rect_to_instance_ndc(
9041 *rect,
9042 current_transform,
9043 current_target_size.0,
9044 current_target_size.1,
9045 );
9046 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
9047 brush_to_shape_fields(brush, rect, current_transform);
9048 batch.borders.push(BorderInstance {
9049 xywh: ndc,
9050 radii: radius.map(|r| r.0),
9051 color0,
9052 color1,
9053 fwd_mat,
9054 grad: [grad_p0[0], grad_p0[1], grad_p1[0], grad_p1[1]],
9055 bits: pack_shape_bits(brush_type, grad_kind, tile_mode, 0),
9056 stroke: width.0,
9057 _pad: [0.0; 2],
9058 });
9059 }
9060 SceneNode::Ellipse { rect, brush } => {
9061 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9062 if !visible(affine_aabb(current_transform, rect), clip) {
9063 continue;
9064 }
9065 flush_if_prim_changed!("ellipse", &self.ellipses);
9066 let (ndc, fwd_mat) = rect_to_instance_ndc(
9067 *rect,
9068 current_transform,
9069 current_target_size.0,
9070 current_target_size.1,
9071 );
9072 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
9073 brush_to_shape_fields(brush, rect, current_transform);
9074 batch.ellipses.push(EllipseInstance {
9075 xywh: ndc,
9076 color0,
9077 color1,
9078 fwd_mat,
9079 grad: [grad_p0[0], grad_p0[1], grad_p1[0], grad_p1[1]],
9080 bits: pack_shape_bits(brush_type, grad_kind, tile_mode, 0),
9081 _pad: [0.0; 1],
9082 });
9083 }
9084 SceneNode::EllipseBorder { rect, brush, width } => {
9085 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9086 let cull_pad = width.0.abs() * 0.5 + 2.0;
9087 let cull_rect = repose_core::Rect {
9088 x: rect.x - cull_pad,
9089 y: rect.y - cull_pad,
9090 w: rect.w + cull_pad * 2.0,
9091 h: rect.h + cull_pad * 2.0,
9092 };
9093 if !visible(affine_aabb(current_transform, &cull_rect), clip) {
9094 continue;
9095 }
9096 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
9097 let (ndc, fwd_mat) = rect_to_instance_ndc(
9098 *rect,
9099 current_transform,
9100 current_target_size.0,
9101 current_target_size.1,
9102 );
9103 let pad_px = width.0 * 0.5 + 2.0;
9104 let pad = (pad_px / current_target_size.0.max(1.0)) * 2.0;
9105 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
9106 brush_to_shape_fields(brush, rect, current_transform);
9107 batch.e_borders.push(EllipseBorderInstance {
9108 xywh: ndc,
9109 color0,
9110 color1,
9111 fwd_mat,
9112 grad: [grad_p0[0], grad_p0[1], grad_p1[0], grad_p1[1]],
9113 bits: pack_shape_bits(brush_type, grad_kind, tile_mode, 0),
9114 stroke: width.0,
9115 pad,
9116 _pad: 0.0,
9117 });
9118 }
9119 SceneNode::Arc {
9120 rect,
9121 start_angle,
9122 sweep_angle,
9123 stroke_width,
9124 brush,
9125 cap,
9126 } => {
9127 if !start_angle.is_finite()
9128 || !sweep_angle.is_finite()
9129 || !stroke_width.0.is_finite()
9130 || !rect.x.is_finite()
9131 || !rect.y.is_finite()
9132 || !rect.w.is_finite()
9133 || !rect.h.is_finite()
9134 || rect.w <= 0.0
9135 || rect.h <= 0.0
9136 || stroke_width.0 <= 0.0
9137 || sweep_angle.abs() <= 1e-6
9138 {
9139 continue;
9140 }
9141 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9142 let cull_pad = stroke_width.0.abs() * 0.5 + 2.0;
9143 let cull_rect = repose_core::Rect {
9144 x: rect.x - cull_pad,
9145 y: rect.y - cull_pad,
9146 w: rect.w + cull_pad * 2.0,
9147 h: rect.h + cull_pad * 2.0,
9148 };
9149 if !visible(affine_aabb(current_transform, &cull_rect), clip) {
9150 continue;
9151 }
9152 flush_if_prim_changed!("arc", &self.arcs);
9153 let (ndc, fwd_mat) = rect_to_instance_ndc(
9154 *rect,
9155 current_transform,
9156 current_target_size.0.max(1.0),
9157 current_target_size.1.max(1.0),
9158 );
9159 let pad_px = stroke_width.0 * 0.5 + 2.0;
9160 let pad = (pad_px / current_target_size.0.max(1.0)) * 2.0;
9161 let cap_val = match cap {
9162 StrokeCap::Butt => 0.0,
9163 StrokeCap::Round => 1.0,
9164 StrokeCap::Square => 2.0,
9165 };
9166 let (brush_type, grad_kind, color0, color1, grad_p0, grad_p1, tile_mode) =
9167 brush_to_shape_fields(brush, rect, current_transform);
9168 let (start, sweep) = if *sweep_angle < 0.0 {
9169 (*start_angle + *sweep_angle, -*sweep_angle)
9170 } else {
9171 (*start_angle, *sweep_angle)
9172 };
9173 batch.arcs.push(ArcInstance {
9174 xywh: ndc,
9175 color0,
9176 color1,
9177 fwd_mat,
9178 grad: [grad_p0[0], grad_p0[1], grad_p1[0], grad_p1[1]],
9179 bits: pack_shape_bits(brush_type, grad_kind, tile_mode, cap_val as u32),
9180 start_angle: start,
9181 sweep_angle: sweep,
9182 stroke: stroke_width.0,
9183 pad,
9184 _pad: 0.0,
9185 });
9186 }
9187 SceneNode::Text {
9188 rect,
9189 text,
9190 color,
9191 size,
9192 style,
9193 } => {
9194 let TextPaintStyle {
9195 font_family,
9196 font_weight,
9197 font_style,
9198 text_decoration,
9199 letter_spacing,
9200 font_variation_settings,
9201 baseline_shift,
9202 draw_style,
9203 ..
9204 } = style;
9205 let px = size.0;
9206 if !px.is_finite()
9207 || px <= 0.0
9208 || !rect.x.is_finite()
9209 || !rect.y.is_finite()
9210 || !rect.w.is_finite()
9211 || !rect.h.is_finite()
9212 {
9213 continue;
9214 }
9215 let margin = px * 4.0 + letter_spacing.0.abs() * 2.0;
9216 let text_bounds = repose_core::Rect {
9217 x: rect.x - margin,
9218 y: rect.y - margin,
9219 w: rect.w + margin * 2.0,
9220 h: rect.h + margin * 2.0,
9221 };
9222 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9223 if !visible(affine_aabb(current_transform, &text_bounds), clip) {
9224 continue;
9225 }
9226 flush_batch!(); let lh_ratio = rect.h / px;
9229 let fw = font_weight.0;
9230 let fs = if *font_style == FontStyle::Italic {
9231 1
9232 } else {
9233 0
9234 };
9235 let shaped = self.shape_text_cached(
9236 text,
9237 px,
9238 lh_ratio,
9239 *font_family,
9240 fw,
9241 fs,
9242 letter_spacing.0,
9243 font_variation_settings.as_ref(),
9244 );
9245 let baseline_y = shaped.glyphs.first().map(|g| rect.y + g.y);
9246
9247 let fwd = forward_rs_mat(current_transform);
9248 let has_linear = fwd != [1.0, 0.0, 0.0, 1.0];
9249
9250 let lin = current_transform.linear();
9251 let tr_x = current_transform.translate_x;
9252 let tr_y = current_transform.translate_y;
9253
9254 let make_glyph_instance =
9255 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 4]) {
9256 if has_linear {
9257 let gc_x = gx + gw * 0.5;
9258 let gc_y = gy + gh * 0.5;
9259 let wc_x = lin[0] * gc_x + lin[1] * gc_y + tr_x;
9260 let wc_y = lin[2] * gc_x + lin[3] * gc_y + tr_y;
9261 let ww = gw * current_transform.scale_x;
9262 let wh = gh * current_transform.scale_y;
9263 let ex = fwd[0].abs() * ww * 0.5 + fwd[1].abs() * wh * 0.5;
9264 let ey = fwd[2].abs() * ww * 0.5 + fwd[3].abs() * wh * 0.5;
9265 let ndc_tl = to_ndc(
9266 wc_x - ex,
9267 wc_y - ey,
9268 ex * 2.0,
9269 ey * 2.0,
9270 current_target_size.0,
9271 current_target_size.1,
9272 );
9273 let ndc = [
9274 ndc_tl[0] + ndc_tl[2] * 0.5,
9275 ndc_tl[1] + ndc_tl[3] * 0.5,
9276 ndc_tl[2],
9277 ndc_tl[3],
9278 ];
9279 (ndc, fwd)
9280 } else {
9281 let (sx, sy) = if current_transform.scale_x == 1.0
9282 && current_transform.scale_y == 1.0
9283 {
9284 (gx.round(), gy.round())
9285 } else {
9286 (gx, gy)
9287 };
9288 rect_to_instance_ndc(
9289 repose_core::Rect {
9290 x: sx,
9291 y: sy,
9292 w: gw,
9293 h: gh,
9294 },
9295 current_transform,
9296 current_target_size.0,
9297 current_target_size.1,
9298 )
9299 }
9300 };
9301
9302 let baseline_shift_y: f32 = px * baseline_shift.0;
9303
9304 let (
9305 draws_fill,
9306 is_stroke,
9307 stroke_width,
9308 stroke_cap,
9309 stroke_join,
9310 stroke_miter,
9311 stroke_path_effect,
9312 stroke_color,
9313 ) = match draw_style {
9314 repose_core::DrawStyle::Stroke {
9315 width,
9316 cap,
9317 join,
9318 miter,
9319 path_effect,
9320 } => (
9321 false,
9322 true,
9323 *width,
9324 *cap,
9325 *join,
9326 *miter,
9327 path_effect.clone(),
9328 None,
9329 ),
9330 repose_core::DrawStyle::FillAndStroke {
9331 width,
9332 cap,
9333 join,
9334 miter,
9335 path_effect,
9336 outline_color,
9337 } => (
9338 true,
9339 true,
9340 *width,
9341 *cap,
9342 *join,
9343 *miter,
9344 path_effect.clone(),
9345 outline_color.map(|c| {
9349 c.with_alpha_f32(
9350 f32::from(c.3) * f32::from(color.3) / (255.0 * 255.0),
9351 )
9352 }),
9353 ),
9354 _ => (
9355 true,
9356 false,
9357 0.0,
9358 repose_core::StrokeCap::Butt,
9359 repose_core::StrokeJoin::Miter,
9360 4.0,
9361 None,
9362 None,
9363 ),
9364 };
9365 let outline_under = stroke_color.is_some();
9369 let stroke_tess_key = if is_stroke {
9370 Some(slug::StrokeTessKey::new(
9371 stroke_width,
9372 stroke_cap,
9373 stroke_join,
9374 stroke_miter,
9375 &stroke_path_effect,
9376 ))
9377 } else {
9378 None
9379 };
9380
9381 let mut legacy_shaped = None;
9382 for (glyph_index, sg) in shaped.glyphs.iter().enumerate() {
9383 if self.slug_enabled {
9384 let ck = &sg.cache_key;
9385 let color_linear = color.to_linear();
9386 let stroke_color_linear = stroke_color.unwrap_or(*color).to_linear();
9387 let glyph_pos = (rect.x + sg.x, rect.y + sg.y + baseline_shift_y);
9388 let fill_key = draws_fill.then(|| {
9389 Self::slug_draw_key(
9390 ck,
9391 true,
9392 None,
9393 current_transform,
9394 glyph_pos,
9395 px,
9396 current_target_size,
9397 color_linear,
9398 )
9399 });
9400 let stroke_key = is_stroke.then(|| {
9401 Self::slug_draw_key(
9402 ck,
9403 false,
9404 stroke_tess_key.as_ref(),
9405 current_transform,
9406 glyph_pos,
9407 px,
9408 current_target_size,
9409 stroke_color_linear,
9410 )
9411 });
9412
9413 let fill_cache = fill_key
9416 .as_ref()
9417 .and_then(|k| self.get_cached_slug_vertices(k));
9418 let stroke_cache = stroke_key
9419 .as_ref()
9420 .and_then(|k| self.get_cached_slug_vertices(k));
9421 let fill_miss = fill_key.is_some() && fill_cache.is_none();
9422 let stroke_miss = stroke_key.is_some() && stroke_cache.is_none();
9423 if (fill_key.is_some() || stroke_key.is_some())
9424 && !fill_miss
9425 && !stroke_miss
9426 {
9427 let mut push = |cached: &Option<Arc<[slug::TessVertex]>>| {
9428 if let Some(vertices) = cached {
9429 slug_verts_local.extend_from_slice(vertices);
9430 }
9431 };
9432 if outline_under {
9433 push(&stroke_cache);
9434 push(&fill_cache);
9435 } else {
9436 push(&fill_cache);
9437 push(&stroke_cache);
9438 }
9439 continue;
9440 }
9441
9442 let outline = self.cached_glyph_outline(ck);
9443 if let Some(commands) = outline.as_ref() {
9444 let font_size_px = f32::from_bits(ck.font_size_bits);
9445 if draws_fill {
9446 self.slug_cache.get_or_insert(
9447 ck.clone(),
9448 font_size_px,
9449 commands.as_ref(),
9450 );
9451 }
9452 if is_stroke {
9453 self.slug_cache.get_or_insert_stroke(
9454 ck.clone(),
9455 font_size_px,
9456 commands.as_ref(),
9457 stroke_width,
9458 stroke_cap,
9459 stroke_join,
9460 stroke_miter,
9461 &stroke_path_effect,
9462 );
9463 }
9464 }
9465
9466 if let Some(entry) = self.slug_cache.get(ck) {
9467 let ox = rect.x + sg.x;
9468 let oy = rect.y + sg.y + baseline_shift_y;
9469 let tf = |x: f32, y: f32| -> (f32, f32) {
9470 if has_linear {
9471 (
9472 lin[0] * x + lin[1] * y + current_transform.translate_x,
9473 lin[2] * x + lin[3] * y + current_transform.translate_y,
9474 )
9475 } else {
9476 (
9477 x * current_transform.scale_x
9478 + current_transform.translate_x,
9479 y * current_transform.scale_y
9480 + current_transform.translate_y,
9481 )
9482 }
9483 };
9484 let tw = current_target_size.0;
9485 let th = current_target_size.1;
9486 let fill_vertices = if draws_fill && fill_cache.is_none() {
9487 entry.fill_vertices.clone().unwrap_or_default()
9488 } else {
9489 Vec::new()
9490 };
9491 let stroke_vertices = if stroke_cache.is_none() {
9492 stroke_tess_key
9493 .as_ref()
9494 .and_then(|key| entry.stroke_variants.get(key))
9495 .map(|variant| variant.vertices.clone())
9496 .unwrap_or_default()
9497 } else {
9498 Vec::new()
9499 };
9500 let mut emit =
9501 |verts: &Vec<[f32; 2]>,
9502 key: &Option<SlugDrawKey>,
9503 cached: &Option<Arc<[slug::TessVertex]>>,
9504 vertex_color: [f32; 4]| {
9505 if let Some(vertices) = cached {
9506 slug_verts_local.extend_from_slice(vertices);
9507 return;
9508 }
9509 let Some(key) = key.clone() else {
9510 return;
9511 };
9512 let transformed: Vec<slug::TessVertex> = verts
9513 .iter()
9514 .map(|v| {
9515 let (sx, sy) = tf(ox + v[0] * px, oy - v[1] * px);
9516 slug::TessVertex {
9517 ndc_pos: [
9518 sx / tw * 2.0 - 1.0,
9519 -(sy / th) * 2.0 + 1.0,
9520 ],
9521 color: vertex_color,
9522 }
9523 })
9524 .collect();
9525 let vertices = self.cached_slug_vertices(key, transformed);
9526 slug_verts_local.extend_from_slice(&vertices);
9527 };
9528 if outline_under {
9532 emit(
9533 &stroke_vertices,
9534 &stroke_key,
9535 &stroke_cache,
9536 stroke_color_linear,
9537 );
9538 emit(&fill_vertices, &fill_key, &fill_cache, color_linear);
9539 } else {
9540 emit(&fill_vertices, &fill_key, &fill_cache, color_linear);
9541 emit(
9542 &stroke_vertices,
9543 &stroke_key,
9544 &stroke_cache,
9545 stroke_color_linear,
9546 );
9547 }
9548 continue;
9549 }
9550 }
9551
9552 if !draws_fill {
9553 continue;
9554 }
9555 if legacy_shaped.is_none() {
9556 legacy_shaped = Some(self.legacy_shape_text_cached(
9557 text,
9558 px,
9559 lh_ratio,
9560 *font_family,
9561 fw,
9562 fs,
9563 letter_spacing.0,
9564 font_variation_settings.as_ref(),
9565 ));
9566 }
9567 let Some(legacy_glyph) = legacy_shaped
9568 .as_ref()
9569 .and_then(|glyphs| glyphs.get(glyph_index))
9570 else {
9571 continue;
9572 };
9573 let gx = rect.x + legacy_glyph.x + legacy_glyph.bearing_x;
9574 let gy =
9575 rect.y + legacy_glyph.y - legacy_glyph.bearing_y + baseline_shift_y;
9576 if let Some(info) =
9577 self.upload_glyph_color(legacy_glyph.cache_key.clone(), legacy_glyph.px)
9578 {
9579 let (ndc, fwd_mat) = make_glyph_instance(gx, gy, info.w, info.h);
9580 batch.colors.push(GlyphInstance {
9581 xywh: ndc,
9582 uv: [info.u0, info.v1, info.u1, info.v0],
9583 color: color.to_linear(),
9584 fwd_mat,
9585 });
9586 } else if let Some(info) =
9587 self.upload_glyph_mask(legacy_glyph.cache_key.clone(), legacy_glyph.px)
9588 {
9589 let (ndc, fwd_mat) = make_glyph_instance(gx, gy, info.w, info.h);
9590 batch.masks.push(GlyphInstance {
9591 xywh: ndc,
9592 uv: [info.u0, info.v1, info.u1, info.v0],
9593 color: color.to_linear(),
9594 fwd_mat,
9595 });
9596 }
9597 }
9598
9599 if !slug_verts_local.is_empty() {
9601 let bytes = bytemuck::cast_slice(&slug_verts_local);
9602 let byte_len = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
9603 let Ok(count) = u32::try_from(slug_verts_local.len()) else {
9604 slug_verts_local.clear();
9605 continue;
9606 };
9607 if self
9608 .slug_ring
9609 .grow_to_fit(&self.device, encoder, byte_len)
9610 .is_ok()
9611 && let Ok(off) = self.slug_ring.alloc_write(&self.queue, bytes)
9612 {
9613 current_pass
9614 .cmds
9615 .push(Cmd::GlyphsVector { off, cnt: count });
9616 }
9617 slug_verts_local.clear();
9618 }
9619
9620 if (text_decoration.underline || text_decoration.strikethrough)
9622 && let Some(baseline_y) = baseline_y
9623 {
9624 flush_batch!();
9625 current_prim = Some("rect");
9626 let deco_color = text_decoration.color.unwrap_or(*color);
9627 let thickness = (px * 0.07).max(1.0);
9628
9629 if text_decoration.underline {
9630 let dy = baseline_y + px * 0.1;
9631 let (ndc, fwd_mat) = rect_to_instance_ndc(
9632 repose_core::Rect {
9633 x: rect.x,
9634 y: dy,
9635 w: rect.w,
9636 h: thickness,
9637 },
9638 current_transform,
9639 current_target_size.0,
9640 current_target_size.1,
9641 );
9642 batch.rects.push(RectInstance {
9643 xywh: ndc,
9644 radii: [0.0; 4],
9645 color0: deco_color.to_linear(),
9646 color1: [0.0; 4],
9647 fwd_mat,
9648 grad: [0.0; 4],
9649 bits: 0,
9650 _pad: [0.0; 3],
9651 });
9652 }
9653 if text_decoration.strikethrough {
9654 let sy = baseline_y - px * 0.3;
9655 let (ndc, fwd_mat) = rect_to_instance_ndc(
9656 repose_core::Rect {
9657 x: rect.x,
9658 y: sy,
9659 w: rect.w,
9660 h: thickness,
9661 },
9662 current_transform,
9663 current_target_size.0,
9664 current_target_size.1,
9665 );
9666 batch.rects.push(RectInstance {
9667 xywh: ndc,
9668 radii: [0.0; 4],
9669 color0: deco_color.to_linear(),
9670 color1: [0.0; 4],
9671 fwd_mat,
9672 grad: [0.0; 4],
9673 bits: 0,
9674 _pad: [0.0; 3],
9675 });
9676 }
9677 }
9678 }
9679 SceneNode::Image {
9680 rect,
9681 handle,
9682 tint,
9683 style,
9684 } => {
9685 let ImagePaintStyle {
9686 fit,
9687 filter,
9688 source_rect,
9689 alignment,
9690 } = style;
9691 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9692 if !visible(affine_aabb(current_transform, rect), clip) {
9693 continue;
9694 }
9695 flush_batch!();
9696 let (img_w, img_h, is_nv12) = match self.resolve_image_for_draw(*handle) {
9697 Some(wh) => wh,
9698 None => {
9699 log::warn!("Image handle {} not found", handle);
9700 continue;
9701 }
9702 };
9703 let Some(source) = resolve_image_source(*source_rect, img_w, img_h) else {
9704 log::warn!(
9705 "Image handle {} has invalid source rect {:?}",
9706 handle,
9707 source_rect
9708 );
9709 continue;
9710 };
9711 let Some((draw_rect, uv_rect)) =
9712 image_fit_geometry(*rect, *fit, *alignment, &source)
9713 else {
9714 continue;
9715 };
9716 let (ndc_center, fwd_mat) = rect_to_instance_ndc(
9717 draw_rect,
9718 current_transform,
9719 current_target_size.0,
9720 current_target_size.1,
9721 );
9722 if is_nv12 {
9723 if image_run.is_some() {
9724 flush_image_runs!();
9725 }
9726 let (uv_x_offset, uv_y_offset) =
9727 if let Some(ImageTex::Nv12 {
9728 w, h, color_info, ..
9729 }) = self.images.get(handle)
9730 {
9731 let chroma_w = w.div_ceil(2).max(1) as f32;
9732 let chroma_h = h.div_ceil(2).max(1) as f32;
9733 match color_info.chroma_siting {
9734 ChromaSiting::Center => (0.0, 0.0),
9735 ChromaSiting::Left => (-0.5 / chroma_w, 0.0),
9736 ChromaSiting::TopLeft => (-0.5 / chroma_w, -0.5 / chroma_h),
9737 }
9738 } else {
9739 (0.0, 0.0)
9740 };
9741 let inst = Nv12Instance {
9742 xywh: ndc_center,
9743 uv: uv_rect,
9744 color: tint.to_linear(),
9745 uv_x_offset,
9746 uv_y_offset,
9747 fwd_mat,
9748 };
9749 if nv12_run
9750 .as_ref()
9751 .is_some_and(|(run_handle, run_filter, _)| {
9752 *run_handle != *handle || *run_filter != *filter
9753 })
9754 {
9755 flush_image_runs!();
9756 }
9757 match &mut nv12_run {
9758 Some((run_handle, run_filter, instances))
9759 if *run_handle == *handle && *run_filter == *filter =>
9760 {
9761 instances.push(inst)
9762 }
9763 _ => nv12_run = Some((*handle, *filter, vec![inst])),
9764 }
9765 } else {
9766 if nv12_run.is_some() {
9767 flush_image_runs!();
9768 }
9769 let tile = matches!(fit, ImageFit::Tile);
9770 let inst = GlyphInstance {
9771 xywh: ndc_center,
9772 uv: uv_rect,
9773 color: tint.to_linear(),
9774 fwd_mat,
9775 };
9776 if image_run.as_ref().is_some_and(
9777 |(run_handle, run_filter, run_tile, _)| {
9778 *run_handle != *handle
9779 || *run_filter != *filter
9780 || *run_tile != tile
9781 },
9782 ) {
9783 flush_image_runs!();
9784 }
9785 match &mut image_run {
9786 Some((run_handle, run_filter, run_tile, instances))
9787 if *run_handle == *handle
9788 && *run_filter == *filter
9789 && *run_tile == tile =>
9790 {
9791 instances.push(inst)
9792 }
9793 _ => image_run = Some((*handle, *filter, tile, vec![inst])),
9794 }
9795 }
9796 }
9797 SceneNode::Coverage {
9798 rect,
9799 handle,
9800 color,
9801 } => {
9802 flush_batch!();
9803 let Some((tile_w, tile_h)) = self.coverage_dimensions(*handle) else {
9806 log::warn!("Coverage handle {handle} not found");
9807 continue;
9808 };
9809 let draw_rect = repose_core::Rect {
9812 x: rect.x,
9813 y: rect.y,
9814 w: tile_w as f32,
9815 h: tile_h as f32,
9816 };
9817 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9818 if !visible(affine_aabb(current_transform, &draw_rect), clip) {
9819 continue;
9820 }
9821 let (ndc_center, fwd_mat) = rect_to_instance_ndc(
9822 draw_rect,
9823 current_transform,
9824 current_target_size.0,
9825 current_target_size.1,
9826 );
9827 let inst = GlyphInstance {
9828 xywh: ndc_center,
9829 uv: [0.0, 1.0, 1.0, 0.0],
9830 color: color.to_linear(),
9831 fwd_mat,
9832 };
9833 if let Some((off, _)) =
9834 self.glyph_color
9835 .upload(&self.device, &self.queue, encoder, &[inst])
9836 {
9837 current_pass.cmds.push(Cmd::Coverage {
9838 off,
9839 cnt: 1,
9840 handle: *handle,
9841 });
9842 }
9843 }
9844 SceneNode::PushClip { rect, radius, op } => {
9845 flush_batch!();
9846
9847 let is_diff = matches!(op, repose_core::ClipOp::Difference);
9848 let t_identity = Transform::identity();
9849 let current_transform = transform_stack.last().unwrap_or(&t_identity);
9850 let transformed = affine_aabb(current_transform, rect);
9851 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9852 let next_scissor = if is_diff {
9853 top
9854 } else {
9855 intersect(top, transformed)
9856 };
9857 scissor_stack.push(next_scissor);
9858 let scissor = to_scissor(
9859 &next_scissor,
9860 current_target_size.0 as u32,
9861 current_target_size.1 as u32,
9862 );
9863 let has_inverse = active_clips.iter().any(ActiveClip::difference);
9864 let mut blocked =
9865 current_pass.active_scissor.is_none() || (has_inverse && !is_diff);
9866 if has_inverse && is_diff {
9867 log::error!(
9868 "unsupported clip nesting: Difference cannot contain a Difference clip"
9869 );
9870 blocked = true;
9871 }
9872 let has_area = transformed.x.is_finite()
9873 && transformed.y.is_finite()
9874 && transformed.w.is_finite()
9875 && transformed.h.is_finite()
9876 && transformed.w > 0.0
9877 && transformed.h > 0.0;
9878 let mut applied = false;
9879 let mut off = 0;
9880 if !blocked && has_area && (is_diff || scissor.2 > 0 && scissor.3 > 0) {
9881 let clip_ndc_tl = to_ndc(
9882 transformed.x,
9883 transformed.y,
9884 transformed.w,
9885 transformed.h,
9886 current_target_size.0,
9887 current_target_size.1,
9888 );
9889 let inst = ClipInstance {
9890 xywh: [
9891 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
9892 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
9893 clip_ndc_tl[2],
9894 clip_ndc_tl[3],
9895 ],
9896 radii: radius.map(|r| r.0),
9897 fwd_mat: [1.0, 0.0, 0.0, 1.0],
9898 };
9899 let bytes = bytemuck::bytes_of(&inst);
9900 if self
9901 .clip_ring
9902 .grow_to_fit(
9903 &self.device,
9904 encoder,
9905 u64::try_from(bytes.len()).unwrap_or(u64::MAX),
9906 )
9907 .is_ok()
9908 && let Ok(new_off) = self.clip_ring.alloc_write(&self.queue, bytes)
9909 {
9910 off = new_off;
9911 applied = true;
9912 current_pass.cmds.push(Cmd::ClipPush {
9913 off,
9914 cnt: 1,
9915 scissor,
9916 difference: is_diff,
9917 applied,
9918 });
9919 } else {
9920 blocked = !is_diff;
9921 }
9922 } else if !is_diff {
9923 blocked = true;
9924 }
9925 current_pass.active_scissor = if blocked {
9926 None
9927 } else {
9928 (scissor.2 > 0 && scissor.3 > 0).then_some(scissor)
9929 };
9930 active_clips.push(ActiveClip::Rect {
9931 off,
9932 cnt: u32::from(applied),
9933 rect: transformed,
9934 radii: radius.map(|r| r.0),
9935 scissor,
9936 difference: is_diff,
9937 applied,
9938 blocked,
9939 });
9940 }
9941 SceneNode::PopClip => {
9942 flush_batch!();
9943
9944 if scissor_stack.is_empty() || active_clips.is_empty() {
9945 log::error!("PopClip does not match an active clip");
9946 } else {
9947 scissor_stack.pop();
9948 }
9949 let clip = active_clips.pop();
9950 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9951 let scissor = to_scissor(
9952 &top,
9953 current_target_size.0 as u32,
9954 current_target_size.1 as u32,
9955 );
9956 current_pass.active_scissor =
9957 (scissor.2 > 0 && scissor.3 > 0).then_some(scissor);
9958 match clip {
9959 Some(ActiveClip::Rect {
9960 off,
9961 cnt,
9962 scissor: stencil_scissor,
9963 difference,
9964 applied,
9965 ..
9966 }) if applied && cnt > 0 => {
9967 current_pass.cmds.push(Cmd::ClipPop {
9968 off,
9969 cnt,
9970 scissor,
9971 stencil_scissor,
9972 difference,
9973 applied: true,
9974 });
9975 }
9976 Some(ActiveClip::Vector { .. }) => {
9977 log::error!("PopClip matched a vector clip");
9978 }
9979 None => {}
9980 _ => {}
9981 }
9982 }
9983 SceneNode::Shadow {
9984 rect,
9985 radius,
9986 elevation: _,
9987 color,
9988 } => {
9989 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
9990 if !visible(affine_aabb(current_transform, rect), clip) {
9991 continue;
9992 }
9993 flush_if_prim_changed!("rect", &self.rects);
9994 let (ndc, fwd_mat) = rect_to_instance_ndc(
9995 *rect,
9996 current_transform,
9997 current_target_size.0,
9998 current_target_size.1,
9999 );
10000 let (_brush_type, color0, _color1, _grad_p0, _grad_p1) =
10001 brush_to_instance_fields(&Brush::Solid(*color));
10002 batch.rects.push(RectInstance {
10003 xywh: ndc,
10004 radii: radius.map(|r| r.0),
10005 color0,
10006 color1: [0.0; 4],
10007 fwd_mat,
10008 grad: [0.0; 4],
10009 bits: 0,
10010 _pad: [0.0; 3],
10011 });
10012 }
10013 SceneNode::PushTransform { transform } => {
10014 flush_batch!(); if transform.has_perspective() {
10016 let top = *transform_stack.last().unwrap_or(&t_identity);
10021 self.push_perspective_layer(
10022 *transform,
10023 top,
10024 &mut transform_stack,
10025 &mut scissor_stack,
10026 &mut root_clip_rect,
10027 &mut current_target_size,
10028 &mut current_pass,
10029 &mut active_clips,
10030 &mut passes,
10031 &mut target_stack,
10032 &mut flatten_stack,
10033 &mut flatten_id_head,
10034 &mut next_pass_id,
10035 &mut flatten_ids_used,
10036 encoder,
10037 );
10038 } else {
10039 let combined = current_transform.combine(transform);
10040 transform_stack.push(combined);
10041 }
10042 }
10043 SceneNode::PopTransform => {
10044 flush_batch!(); if let Some(rec) = flatten_stack.last() {
10046 if transform_stack.len() == rec.stack_len + 2 {
10050 let rec = flatten_stack.pop().expect("checked above");
10051 transform_stack.pop();
10052 transform_stack.pop();
10053 self.pop_perspective_layer(
10054 rec,
10055 &mut scissor_stack,
10056 &mut root_clip_rect,
10057 &mut current_target_size,
10058 &mut current_pass,
10059 &mut active_clips,
10060 &mut passes,
10061 &mut target_stack,
10062 &mut next_pass_id,
10063 encoder,
10064 );
10065 continue;
10066 }
10067 }
10068 transform_stack.pop();
10069 }
10070 SceneNode::BeginLayer {
10071 rect,
10072 layer_id,
10073 alpha,
10074 blur_radius_x,
10075 blur_radius_y,
10076 rectangle_edge,
10077 } => {
10078 flush_batch!();
10079 let width_f = rect.w.round();
10080 let height_f = rect.h.round();
10081 let max_dimension = self.device.limits().max_texture_dimension_2d as f32;
10082 if !width_f.is_finite()
10083 || !height_f.is_finite()
10084 || width_f < 1.0
10085 || height_f < 1.0
10086 || width_f > max_dimension
10087 || height_f > max_dimension
10088 {
10089 log::warn!("BeginLayer dimensions are invalid");
10090 continue;
10091 }
10092 let width = width_f as u32;
10093 let height = height_f as u32;
10094 let parent_scissors =
10095 std::mem::replace(&mut scissor_stack, Vec::with_capacity(8));
10096 let parent_root_clip = std::mem::replace(
10097 &mut root_clip_rect,
10098 repose_core::Rect {
10099 x: 0.0,
10100 y: 0.0,
10101 w: width as f32,
10102 h: height as f32,
10103 },
10104 );
10105 scissor_stack.push(root_clip_rect);
10106 let layer_pass_id = next_pass_id;
10107 next_pass_id += 1;
10108 let parent_clips = std::mem::take(&mut active_clips);
10109 let mut layer_pass = Pass {
10110 id: layer_pass_id,
10111 target: PassTarget::Layer(*layer_id),
10112 initial_scissor: (0, 0, width, height),
10113 active_scissor: Some((0, 0, width, height)),
10114 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
10115 cmds: Vec::new(),
10116 };
10117 let parent_transform = *current_transform;
10118 active_clips = self.replay_active_clips(
10119 &parent_clips,
10120 &parent_transform,
10121 (rect.x, rect.y),
10122 (width as f32, height as f32),
10123 &mut layer_pass,
10124 encoder,
10125 );
10126 let saved = std::mem::replace(&mut current_pass, layer_pass);
10127 if let Some(top) = transform_stack.last_mut() {
10128 *top = Transform::identity();
10129 }
10130 layer_stack.push(LayerState {
10131 layer_id: *layer_id,
10132 parent_target: saved.target,
10133 parent_scissors,
10134 parent_root_clip,
10135 parent_size: current_target_size,
10136 parent_transform,
10137 parent_scissor: saved.active_scissor,
10138 parent_clips,
10139 alpha: *alpha,
10140 blur: (blur_radius_x.0, blur_radius_y.0),
10141 rectangle_edge: *rectangle_edge,
10142 });
10143 passes.push(saved);
10144 if self.get_or_create_layer(*layer_id, width, height, *rect, false) {
10145 self.producer_layer_ids.insert(*layer_id);
10146 }
10147 current_target_size = (width as f32, height as f32);
10148 }
10149 SceneNode::EndLayer { layer_id } => {
10150 flush_batch!();
10151 if layer_stack.last().map(|state| state.layer_id) != Some(*layer_id) {
10152 log::warn!("EndLayer {} does not match active layer", layer_id);
10153 continue;
10154 }
10155 let state = layer_stack.pop().expect("checked above");
10156 scissor_stack = state.parent_scissors;
10157 root_clip_rect = state.parent_root_clip;
10158 active_clips = state.parent_clips;
10159 if let Some(top) = transform_stack.last_mut() {
10160 *top = state.parent_transform;
10161 }
10162 current_target_size = state.parent_size;
10163 let resumed_pass_id = next_pass_id;
10164 next_pass_id += 1;
10165 let saved = std::mem::replace(
10166 &mut current_pass,
10167 Pass {
10168 id: resumed_pass_id,
10169 target: state.parent_target,
10170 initial_scissor: state.parent_scissor.unwrap_or((0, 0, 1, 1)),
10171 active_scissor: state.parent_scissor,
10172 clear_color: None,
10173 cmds: Vec::new(),
10174 },
10175 );
10176 passes.push(saved);
10177 let Some(layer) = self.layer_pool.get(layer_id).cloned() else {
10178 continue;
10179 };
10180 let layer_rect = repose_core::Rect {
10181 x: layer.rect_px.0,
10182 y: layer.rect_px.1,
10183 w: layer.rect_px.2,
10184 h: layer.rect_px.3,
10185 };
10186 let local_layer_rect = affine_aabb(&state.parent_transform, &layer_rect);
10187 let (parent_width, parent_height) = state.parent_size;
10188 let parent_clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
10189 let composite_rect = if state.blur.0 > 0.0 || state.blur.1 > 0.0 {
10190 let pad_x = blur_pad(state.blur.0);
10191 let pad_y = blur_pad(state.blur.1);
10192 repose_core::Rect {
10193 x: local_layer_rect.x - pad_x,
10194 y: local_layer_rect.y - pad_y,
10195 w: local_layer_rect.w + pad_x * 2.0,
10196 h: local_layer_rect.h + pad_y * 2.0,
10197 }
10198 } else {
10199 local_layer_rect
10200 };
10201 let shadow_follows = shadow_layer_ids.contains(layer_id);
10202 if !visible(composite_rect, parent_clip) {
10203 continue;
10204 }
10205 let mut composite = None;
10206 if state.blur.0 > 0.0 || state.blur.1 > 0.0 {
10207 flush_batch!();
10208 let pad_x = blur_pad(state.blur.0);
10209 let pad_y = blur_pad(state.blur.1);
10210 let rect = repose_core::Rect {
10211 x: local_layer_rect.x - pad_x,
10212 y: local_layer_rect.y - pad_y,
10213 w: local_layer_rect.w + pad_x * 2.0,
10214 h: local_layer_rect.h + pad_y * 2.0,
10215 };
10216 let ndc =
10217 to_ndc(rect.x, rect.y, rect.w, rect.h, parent_width, parent_height);
10218 let edge_mode = if state.rectangle_edge { 0 } else { 1 };
10219 let sigma = [blur_sigma(state.blur.0), blur_sigma(state.blur.1)];
10220 if let Some(scratch_id) = self.emit_separable_blur(
10221 *layer_id,
10222 BLUR_SLOT_CONTENT,
10223 sigma,
10224 (pad_x, pad_y),
10225 edge_mode,
10226 false,
10227 [0.0; 4],
10228 &mut passes,
10229 &mut current_pass,
10230 &mut next_pass_id,
10231 encoder,
10232 ) {
10233 let inst = BlurInstance {
10234 xywh: [
10235 ndc[0] + ndc[2] * 0.5,
10236 ndc[1] + ndc[3] * 0.5,
10237 ndc[2],
10238 ndc[3],
10239 ],
10240 uv: [0.0, 0.0, 1.0, 1.0],
10241 color: [1.0, 1.0, 1.0, state.alpha],
10242 sigma_px: sigma,
10243 fwd_mat: [1.0, 0.0, 0.0, 1.0],
10244 axis: 1,
10245 edge_mode,
10246 shape: [0.0; 4],
10247 };
10248 if let Some(off) = self.upload_blur_instance(&inst, encoder) {
10249 composite = Some(Cmd::CompositeBlur {
10250 off,
10251 cnt: 1,
10252 layer_id: scratch_id,
10253 });
10254 }
10255 }
10256 }
10257 if composite.is_none() {
10260 let ndc = to_ndc(
10261 local_layer_rect.x,
10262 local_layer_rect.y,
10263 local_layer_rect.w,
10264 local_layer_rect.h,
10265 parent_width,
10266 parent_height,
10267 );
10268 let inst = GlyphInstance {
10269 xywh: [ndc[0] + ndc[2] * 0.5, ndc[1] + ndc[3] * 0.5, ndc[2], ndc[3]],
10270 uv: [0.0, 1.0, 1.0, 0.0],
10271 color: [1.0, 1.0, 1.0, state.alpha],
10272 fwd_mat: [1.0, 0.0, 0.0, 1.0],
10273 };
10274 composite = self
10275 .glyph_color
10276 .upload(&self.device, &self.queue, encoder, &[inst])
10277 .map(|(off, cnt)| Cmd::CompositeLayer {
10278 off,
10279 cnt,
10280 layer_id: *layer_id,
10281 });
10282 }
10283 if let Some(command) = composite {
10284 if shadow_follows {
10285 pending_layer_composite = Some(command);
10286 } else {
10287 current_pass.cmds.push(command);
10288 }
10289 }
10290 }
10291 SceneNode::CompositeShadow {
10292 layer_id,
10293 blur_px,
10294 offset_px,
10295 color,
10296 shape_px,
10297 } => {
10298 let sigma = blur_sigma(blur_px.0);
10299 let pad = blur_pad(blur_px.0);
10300 let layer = self.layer_pool.get(layer_id).cloned();
10301 if let Some(layer) = layer {
10302 let layer_rect = repose_core::Rect {
10303 x: layer.rect_px.0,
10304 y: layer.rect_px.1,
10305 w: layer.rect_px.2,
10306 h: layer.rect_px.3,
10307 };
10308 let local_layer_rect =
10309 affine_aabb(transform_stack.last().unwrap_or(&t_identity), &layer_rect);
10310 let sx = local_layer_rect.x + offset_px.0.0 - pad;
10311 let sy = local_layer_rect.y + offset_px.1.0 - pad;
10312 let sw = local_layer_rect.w + pad * 2.0;
10313 let sh = local_layer_rect.h + pad * 2.0;
10314 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
10315 if !visible(
10316 repose_core::Rect {
10317 x: sx,
10318 y: sy,
10319 w: sw,
10320 h: sh,
10321 },
10322 clip,
10323 ) {
10324 flush_pending_layer!();
10325 continue;
10326 }
10327 flush_batch!();
10328 let shadow_color = color.to_linear();
10329 let max_r = 0.5 * layer.rect_px.2.min(layer.rect_px.3).max(0.0);
10330 let shape = shape_px.map(|r| r.0.clamp(0.0, max_r));
10331 let scratch_id = self.emit_separable_blur(
10335 *layer_id,
10336 BLUR_SLOT_SHADOW,
10337 [sigma, sigma],
10338 (pad, pad),
10339 1,
10340 true,
10341 shape,
10342 &mut passes,
10343 &mut current_pass,
10344 &mut next_pass_id,
10345 encoder,
10346 );
10347 if let Some(scratch_id) = scratch_id {
10348 let ndc_tl = to_ndc(
10349 sx,
10350 sy,
10351 sw,
10352 sh,
10353 current_target_size.0,
10354 current_target_size.1,
10355 );
10356 let inst = BlurInstance {
10357 xywh: [
10358 ndc_tl[0] + ndc_tl[2] * 0.5,
10359 ndc_tl[1] + ndc_tl[3] * 0.5,
10360 ndc_tl[2],
10361 ndc_tl[3],
10362 ],
10363 uv: [0.0, 0.0, 1.0, 1.0],
10364 color: shadow_color,
10365 sigma_px: [sigma, sigma],
10366 fwd_mat: [1.0, 0.0, 0.0, 1.0],
10367 axis: 1,
10368 edge_mode: 1,
10369 shape: [0.0; 4],
10370 };
10371 if let Some(off) = self.upload_blur_instance(&inst, encoder) {
10372 current_pass.cmds.push(Cmd::CompositeShadow {
10373 off,
10374 cnt: 1,
10375 layer_id: scratch_id,
10376 });
10377 }
10378 }
10379 }
10380 flush_pending_layer!();
10381 }
10382 SceneNode::VectorMesh {
10383 mesh,
10384 transform,
10385 paint,
10386 clip: _,
10387 blend,
10388 } => {
10389 let affine = combine_mesh_affine(current_transform, *transform);
10390 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
10391 if !visible(mesh_aabb(mesh, affine), clip) {
10392 continue;
10393 }
10394 flush_batch!();
10395 let paint_alpha = match paint {
10396 repose_core::PaintDesc::Linear {
10397 start_color,
10398 end_color,
10399 ..
10400 }
10401 | repose_core::PaintDesc::Radial {
10402 start_color,
10403 end_color,
10404 ..
10405 }
10406 | repose_core::PaintDesc::Sweep {
10407 start_color,
10408 end_color,
10409 ..
10410 } => Some(start_color.3.min(end_color.3) as f32 / 255.0),
10411 _ => None,
10412 };
10413 let translucent_fixed_blend =
10414 matches!(
10415 blend,
10416 repose_core::BlendMode::Add
10417 | repose_core::BlendMode::Multiply
10418 | repose_core::BlendMode::Screen
10419 | repose_core::BlendMode::Darken
10420 | repose_core::BlendMode::Lighten
10421 ) && (mesh.vertices.iter().any(|vertex| vertex.color[3] < 0.9999)
10422 || paint_alpha.is_some_and(|alpha| alpha < 0.9999));
10423 if blend.needs_isolation() || translucent_fixed_blend {
10424 let blend_scissor = to_scissor(
10425 &scissor_stack.last().copied().unwrap_or(root_clip_rect),
10426 current_target_size.0 as u32,
10427 current_target_size.1 as u32,
10428 );
10429 self.emit_isolated_blend(
10430 mesh.clone(),
10431 *transform,
10432 *paint,
10433 *blend,
10434 current_transform,
10435 &mut current_pass,
10436 &active_clips,
10437 &mut passes,
10438 &mut flatten_id_head,
10439 &mut next_pass_id,
10440 &mut flatten_ids_used,
10441 blend_scissor,
10442 encoder,
10443 fb_w,
10444 fb_h,
10445 );
10446 } else {
10447 let t_identity = Transform::identity();
10448 let current_transform = transform_stack.last().unwrap_or(&t_identity);
10449 self.emit_vector_mesh(
10450 current_transform,
10451 mesh,
10452 *transform,
10453 paint,
10454 *blend,
10455 &mut current_pass.cmds,
10456 );
10457 }
10458 }
10459 SceneNode::VectorOverlay { meshes } => {
10460 let clip = scissor_stack.last().copied().unwrap_or(root_clip_rect);
10461 let has_visible = meshes
10462 .iter()
10463 .any(|mesh| visible(mesh_aabb(mesh, [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]), clip));
10464 if !has_visible {
10465 continue;
10466 }
10467 flush_batch!();
10468 for (index, m) in meshes.iter().enumerate() {
10469 if !visible(mesh_aabb(m, [1.0, 0.0, 0.0, 0.0, 1.0, 0.0]), clip) {
10470 continue;
10471 }
10472 let Some(mesh_gpu) = self.upload_overlay_mesh(meshes, index) else {
10473 continue;
10474 };
10475 let Some(uoff) = self.alloc_mesh_uniform(MeshUniform::identity()) else {
10476 continue;
10477 };
10478 current_pass.cmds.push(Cmd::VectorOverlay {
10479 mesh: mesh_gpu,
10480 uoff,
10481 });
10482 }
10483 }
10484 SceneNode::PushVectorClip { mesh, op } => {
10485 flush_batch!();
10486 let difference = matches!(op, repose_core::ClipOp::Difference);
10487 let t_identity = Transform::identity();
10488 let current_transform = transform_stack.last().unwrap_or(&t_identity);
10489 let affine =
10490 combine_mesh_affine(current_transform, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
10491 let aabb = mesh_aabb(mesh, affine);
10492 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
10493 let next = if difference {
10494 top
10495 } else {
10496 intersect(top, aabb)
10497 };
10498 scissor_stack.push(next);
10499 let scissor = to_scissor(
10500 &next,
10501 current_target_size.0 as u32,
10502 current_target_size.1 as u32,
10503 );
10504 let has_inverse = active_clips.iter().any(ActiveClip::difference);
10505 let mut blocked = current_pass.active_scissor.is_none() || has_inverse;
10506 if has_inverse {
10507 log::error!(
10508 "unsupported clip nesting: a Difference clip cannot contain another vector clip"
10509 );
10510 }
10511 let has_area = aabb.x.is_finite()
10512 && aabb.y.is_finite()
10513 && aabb.w.is_finite()
10514 && aabb.h.is_finite()
10515 && aabb.w > 0.0
10516 && aabb.h > 0.0;
10517 let mut applied = false;
10518 let mut geometry = None;
10519 let mut uoff = 0;
10520 if !blocked
10521 && has_area
10522 && (if difference {
10523 visible(aabb, top)
10524 } else {
10525 scissor.2 > 0 && scissor.3 > 0
10526 })
10527 {
10528 if let Some(mesh_gpu) = self.upload_mesh_geometry(mesh)
10529 && let Some(slot) = self.alloc_mesh_uniform(mesh_uniform_from_paint(
10530 affine,
10531 &repose_core::PaintDesc::Solid,
10532 ))
10533 {
10534 geometry = Some(mesh_gpu.clone());
10535 uoff = slot;
10536 applied = true;
10537 current_pass.cmds.push(Cmd::VectorClipPush {
10538 mesh: mesh_gpu,
10539 uoff,
10540 scissor,
10541 difference,
10542 applied,
10543 });
10544 } else {
10545 blocked = !difference;
10546 }
10547 } else if !difference {
10548 blocked = true;
10549 }
10550 current_pass.active_scissor = if blocked {
10551 None
10552 } else {
10553 (scissor.2 > 0 && scissor.3 > 0).then_some(scissor)
10554 };
10555 active_clips.push(ActiveClip::Vector {
10556 mesh: geometry,
10557 source: mesh.clone(),
10558 uoff,
10559 affine,
10560 scissor,
10561 difference,
10562 applied,
10563 blocked,
10564 });
10565 }
10566 SceneNode::PopVectorClip => {
10567 flush_batch!();
10568 if scissor_stack.is_empty() || active_clips.is_empty() {
10569 log::error!("PopVectorClip does not match an active clip");
10570 } else {
10571 scissor_stack.pop();
10572 }
10573 let clip = active_clips.pop();
10574 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
10575 let scissor = to_scissor(
10576 &top,
10577 current_target_size.0 as u32,
10578 current_target_size.1 as u32,
10579 );
10580 current_pass.active_scissor =
10581 (scissor.2 > 0 && scissor.3 > 0).then_some(scissor);
10582 match clip {
10583 Some(ActiveClip::Vector {
10584 mesh: Some(mesh),
10585 uoff,
10586 scissor: stencil_scissor,
10587 difference,
10588 applied,
10589 ..
10590 }) if applied && mesh.vertex_count > 0 && mesh.index_count > 0 => {
10591 current_pass.cmds.push(Cmd::VectorClipPop {
10592 mesh,
10593 uoff,
10594 scissor,
10595 stencil_scissor,
10596 difference,
10597 applied: true,
10598 });
10599 }
10600 Some(ActiveClip::Rect { .. }) => {
10601 log::error!("PopVectorClip matched a rounded-rect clip");
10602 }
10603 None => {}
10604 _ => {}
10605 }
10606 }
10607 SceneNode::Callback { rect, payload } => {
10608 let t = transform_stack
10609 .last()
10610 .copied()
10611 .unwrap_or(Transform::identity());
10612 let transformed = affine_aabb(&t, rect);
10613 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
10614 let clip_rect = intersect(transformed, top);
10615 let scissor = to_scissor(
10616 &clip_rect,
10617 current_target_size.0 as u32,
10618 current_target_size.1 as u32,
10619 );
10620 let restore_scissor = to_scissor(
10621 &top,
10622 current_target_size.0 as u32,
10623 current_target_size.1 as u32,
10624 );
10625 if current_pass.active_scissor.is_none()
10626 || !visible(transformed, top)
10627 || scissor.2 == 0
10628 || scissor.3 == 0
10629 {
10630 continue;
10631 }
10632 flush_batch!();
10633 current_pass.cmds.push(Cmd::Callback {
10634 rect: transformed,
10635 clip_rect,
10636 scissor,
10637 restore_scissor,
10638 callback_id: Arc::as_ptr(payload) as *const () as usize,
10639 payload: payload.clone(),
10640 });
10641 }
10642 _ => {}
10643 }
10644 }
10645
10646 flush_pending_layer!();
10647 flush_batch!();
10648 passes.push(current_pass);
10649
10650 {
10651 let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
10652 let mut prepare_list: Vec<(usize, Arc<Callback>)> = Vec::new();
10653 for pass in &passes {
10654 for command in &pass.cmds {
10655 if let Cmd::Callback { payload, .. } = command
10656 && payload.downcast_ref::<Callback>().is_some()
10657 {
10658 let ptr = Arc::as_ptr(payload) as *const () as usize;
10659 if seen.insert(ptr)
10660 && let Ok(cb_arc) = payload.clone().downcast::<Callback>()
10661 {
10662 prepare_list.push((ptr, cb_arc));
10663 }
10664 }
10665 }
10666 }
10667 if !prepare_list.is_empty() {
10668 let mut callback_targets: HashMap<usize, Vec<(PassTarget, ScreenDescriptor)>> =
10669 HashMap::new();
10670 for pass in &passes {
10671 let (size, target_format, sample_count) = match pass.target {
10672 PassTarget::Surface => (
10673 [self.output_width, self.output_height],
10674 if self.working_space {
10675 wgpu::TextureFormat::Rgba16Float
10676 } else {
10677 self.output_format
10678 },
10679 self.active_surface_msaa_samples(),
10680 ),
10681 PassTarget::Layer(layer_id) => {
10682 let layer = self.layer_pool.get(&layer_id);
10683 (
10684 layer.map_or([self.output_width, self.output_height], |layer| {
10685 [layer.width, layer.height]
10686 }),
10687 self.layer_target_format(),
10688 1,
10689 )
10690 }
10691 };
10692 for command in &pass.cmds {
10693 if let Cmd::Callback { payload, .. } = command {
10694 let descriptor = ScreenDescriptor {
10695 size_in_pixels: size,
10696 pixels_per_point: self.pixels_per_point,
10697 target_format,
10698 sample_count,
10699 };
10700 let targets = callback_targets
10701 .entry(Arc::as_ptr(payload) as *const () as usize)
10702 .or_default();
10703 if !targets.iter().any(|(target, target_desc)| {
10704 *target == pass.target
10705 && target_desc.target_format == descriptor.target_format
10706 && target_desc.sample_count == descriptor.sample_count
10707 }) {
10708 targets.push((pass.target, descriptor));
10709 }
10710 }
10711 }
10712 }
10713
10714 let default_descriptor = ScreenDescriptor {
10715 size_in_pixels: [self.output_width, self.output_height],
10716 pixels_per_point: self.pixels_per_point,
10717 target_format: if self.working_space {
10718 wgpu::TextureFormat::Rgba16Float
10719 } else {
10720 self.output_format
10721 },
10722 sample_count: self.active_surface_msaa_samples(),
10723 };
10724 let default_descriptors = [(PassTarget::Surface, default_descriptor)];
10725 let mut prepare_encoder =
10726 self.device
10727 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
10728 label: Some("callback prepare"),
10729 });
10730 let mut finish_encoder =
10731 self.device
10732 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
10733 label: Some("callback finish prepare"),
10734 });
10735 let mut active_callback_scopes = HashSet::new();
10736 for (key, cb) in &prepare_list {
10737 let descriptors = callback_targets
10738 .get(key)
10739 .map(Vec::as_slice)
10740 .unwrap_or(&default_descriptors);
10741 for &(target, screen_desc) in descriptors {
10742 let scope = callback_scope_key(cb, *key, target, &screen_desc);
10743 active_callback_scopes.insert(scope.clone());
10744 if !scope.callback.reusable
10745 && self
10746 .callback_scope_payloads
10747 .get(&scope)
10748 .is_some_and(|payload| payload.upgrade().is_none())
10749 {
10750 self.remove_callback_scope(&scope);
10751 }
10752 let payload: Weak<Callback> = Arc::downgrade(cb);
10753 self.callback_scope_payloads.insert(scope, payload);
10754 }
10755 }
10756 let mut prepare_buffers = Vec::new();
10757 let mut finish_buffers = Vec::new();
10758 for (key, cb) in &prepare_list {
10759 let descriptors = callback_targets
10760 .get(key)
10761 .map(Vec::as_slice)
10762 .unwrap_or(&default_descriptors);
10763 for &(target, screen_desc) in descriptors {
10764 let scope = callback_scope_key(cb, *key, target, &screen_desc);
10765 self.touch_callback_scope(scope.clone());
10766 if !self.callback_scoped_resources.contains_key(&scope)
10767 && self.callback_scoped_resources.len() >= MAX_CALLBACK_SCOPES
10768 {
10769 self.evict_callback_scope(&active_callback_scopes);
10770 }
10771 let resources = self.callback_scoped_resources.entry(scope).or_default();
10772 prepare_buffers.extend(cb.0.prepare(
10773 &self.device,
10774 &self.queue,
10775 &mut prepare_encoder,
10776 &screen_desc,
10777 resources,
10778 ));
10779 }
10780 }
10781 for (key, cb) in &prepare_list {
10782 let descriptors = callback_targets
10783 .get(key)
10784 .map(Vec::as_slice)
10785 .unwrap_or(&default_descriptors);
10786 for &(target, screen_desc) in descriptors {
10787 let scope = callback_scope_key(cb, *key, target, &screen_desc);
10788 self.touch_callback_scope(scope.clone());
10789 if let Some(resources) = self.callback_scoped_resources.get_mut(&scope) {
10790 finish_buffers.extend(cb.0.finish_prepare(
10791 &self.device,
10792 &self.queue,
10793 &mut finish_encoder,
10794 &screen_desc,
10795 resources,
10796 ));
10797 }
10798 }
10799 }
10800 let mut callback_buffers =
10801 Vec::with_capacity(2 + prepare_buffers.len() + finish_buffers.len());
10802 callback_buffers.push(prepare_encoder.finish());
10803 callback_buffers.extend(prepare_buffers);
10804 callback_buffers.push(finish_encoder.finish());
10805 callback_buffers.extend(finish_buffers);
10806 self.queue.submit(callback_buffers);
10807 }
10808 }
10809
10810 let globals_bytes = std::mem::size_of::<Globals>() as u64;
10811 if !self.ensure_globals_staging(passes.len()) {
10812 return;
10813 }
10814 for (i, pass) in passes.iter().enumerate() {
10815 let (target_w, target_h) = match pass.target {
10816 PassTarget::Surface => (fb_w, fb_h),
10817 PassTarget::Layer(layer_id) => {
10818 let lt = self.layer_pool.get(&layer_id);
10819 (
10820 lt.map_or(fb_w, |l| l.width as f32),
10821 lt.map_or(fb_h, |l| l.height as f32),
10822 )
10823 }
10824 };
10825 self.queue.write_buffer(
10826 &self.globals_staging,
10827 (i as u64) * globals_bytes,
10828 bytemuck::bytes_of(&make_globals(target_w, target_h)),
10829 );
10830 }
10831
10832 let bind_mask = self.atlas_bind_group_mask();
10833 let bind_color = self.atlas_bind_group_color();
10834 let mut clip_depth: u32 = 0;
10835 let mut clip_depth_stack: Vec<u32> = Vec::new();
10836
10837 let snapshot_source = target_texture.cloned();
10838 let last_surface_pass = passes
10839 .iter()
10840 .rposition(|pass| matches!(pass.target, PassTarget::Surface));
10841 for (pass_index, pass) in std::mem::take(&mut passes).into_iter().enumerate() {
10842 let needs_snapshot = pass
10850 .cmds
10851 .iter()
10852 .any(|c| matches!(c, Cmd::BlendLayer { .. }));
10853 if needs_snapshot {
10854 let copies = std::mem::take(&mut self.blend_copies);
10855 let has_surface_copy = copies.iter().any(|copy| copy.target == PassTarget::Surface);
10856 let mut resolved_surface = None;
10857 if has_surface_copy
10858 && !self.working_space
10859 && let (Some(msaa_view), Some(resolve_view)) =
10860 (self.msaa_view.as_ref(), self.surface_resolve_view.as_ref())
10861 {
10862 let resolve_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
10863 label: Some("surface blend resolve"),
10864 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
10865 view: msaa_view,
10866 resolve_target: Some(resolve_view),
10867 ops: wgpu::Operations {
10868 load: wgpu::LoadOp::Load,
10869 store: wgpu::StoreOp::Store,
10870 },
10871 depth_slice: None,
10872 })],
10873 depth_stencil_attachment: None,
10874 timestamp_writes: None,
10875 occlusion_query_set: None,
10876 multiview_mask: None,
10877 });
10878 drop(resolve_pass);
10879 resolved_surface = self.surface_resolve_tex.clone();
10880 }
10881 let mut remaining = Vec::with_capacity(copies.len());
10882 for copy in copies {
10883 if copy.pass_id != pass.id {
10884 remaining.push(copy);
10885 continue;
10886 }
10887 let BlendCopy {
10888 blend_id,
10889 target,
10890 region,
10891 ..
10892 } = copy;
10893 let (src_tex, tw, th) = match target {
10894 PassTarget::Layer(parent_id) => match self.layer_pool.get(&parent_id) {
10895 Some(lt) => (lt.texture.clone(), lt.width, lt.height),
10896 None => continue,
10897 },
10898 PassTarget::Surface => {
10899 let texture = if self.working_space {
10900 self.ws_tex.clone()
10901 } else if self.msaa_view.is_some() {
10902 resolved_surface.clone()
10903 } else {
10904 snapshot_source.clone()
10905 };
10906 match texture {
10907 Some(texture) => (texture, self.output_width, self.output_height),
10908 None => {
10909 self.remove_blend_snapshot(blend_id);
10910 continue;
10911 }
10912 }
10913 }
10914 };
10915 let Some(dst_tex) = self.blend_snapshot_texture(blend_id) else {
10916 continue;
10917 };
10918 if !src_tex.usage().contains(wgpu::TextureUsages::COPY_SRC)
10919 || !dst_tex.usage().contains(wgpu::TextureUsages::COPY_DST)
10920 {
10921 self.remove_blend_snapshot(blend_id);
10922 continue;
10923 }
10924 let Some((sx, sy, cw, ch)) = checked_copy_region(region, tw, th) else {
10925 self.remove_blend_snapshot(blend_id);
10926 continue;
10927 };
10928 encoder.copy_texture_to_texture(
10929 wgpu::TexelCopyTextureInfo {
10930 texture: &src_tex,
10931 mip_level: 0,
10932 origin: wgpu::Origin3d { x: sx, y: sy, z: 0 },
10933 aspect: wgpu::TextureAspect::All,
10934 },
10935 wgpu::TexelCopyTextureInfo {
10936 texture: &dst_tex,
10937 mip_level: 0,
10938 origin: wgpu::Origin3d::ZERO,
10939 aspect: wgpu::TextureAspect::All,
10940 },
10941 wgpu::Extent3d {
10942 width: cw,
10943 height: ch,
10944 depth_or_array_layers: 1,
10945 },
10946 );
10947 }
10948 self.blend_copies = remaining;
10949 }
10950 let (color_view, resolve_target, depth_stencil_view, is_layer, working_target) =
10951 match pass.target {
10952 PassTarget::Surface => {
10953 let swap_view = target_view.clone();
10954 let use_ws = self.working_space && self.ws_view.is_some();
10955 let (color, resolve) = if use_ws {
10956 let ws_view = self.ws_view.as_ref().unwrap();
10957 if let Some(msaa_view) = &self.ws_msaa_view {
10958 (msaa_view.clone(), Some(ws_view.clone()))
10959 } else {
10960 (ws_view.clone(), None)
10961 }
10962 } else if let Some(msaa_view) = &self.msaa_view {
10963 let resolve =
10968 (Some(pass_index) == last_surface_pass).then(|| swap_view.clone());
10969 (msaa_view.clone(), resolve)
10970 } else {
10971 (swap_view, None)
10972 };
10973 (
10974 color,
10975 resolve,
10976 self.depth_stencil_view.clone(),
10977 false,
10978 use_ws,
10979 )
10980 }
10981 PassTarget::Layer(layer_id) => {
10982 if let Some(lt) = self.layer_pool.get(&layer_id) {
10983 (
10984 lt.view.clone(),
10985 None,
10986 lt.depth_stencil_view.clone(),
10987 true,
10988 false,
10989 )
10990 } else {
10991 log::warn!("missing layer target {layer_id}");
10992 continue;
10993 }
10994 }
10995 };
10996
10997 encoder.copy_buffer_to_buffer(
10998 &self.globals_staging,
10999 (pass_index as u64) * globals_bytes,
11000 &self.globals_buf,
11001 0,
11002 globals_bytes,
11003 );
11004
11005 if is_layer {
11006 clip_depth_stack.push(clip_depth);
11007 clip_depth = 0;
11008 }
11009
11010 let (tw, th) = match pass.target {
11011 PassTarget::Surface => (self.output_width, self.output_height),
11012 PassTarget::Layer(layer_id) => self
11013 .layer_pool
11014 .get(&layer_id)
11015 .map(|l| (l.width, l.height))
11016 .unwrap_or((self.output_width, self.output_height)),
11017 };
11018 let initial_scissor = clamp_scissor(
11019 pass.initial_scissor.0,
11020 pass.initial_scissor.1,
11021 pass.initial_scissor.2,
11022 pass.initial_scissor.3,
11023 tw,
11024 th,
11025 );
11026 let mut active_scissor = pass
11027 .active_scissor
11028 .map(|scissor| clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th));
11029 if active_scissor.is_some_and(|scissor| scissor.2 == 0 || scissor.3 == 0) {
11030 active_scissor = None;
11031 }
11032
11033 let pipes: &Pipelines = if is_layer {
11034 if self.working_space {
11035 &self.working_space_layer_pipes
11036 } else {
11037 &self.layer_pipes
11038 }
11039 } else if working_target {
11040 &self.working_space_pipes
11041 } else {
11042 &self.surface_pipes
11043 };
11044
11045 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
11046 label: Some("pass"),
11047 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
11048 view: &color_view,
11049 resolve_target: resolve_target.as_ref(),
11050 ops: wgpu::Operations {
11051 load: match pass.clear_color {
11052 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
11053 r: c[0] as f64,
11054 g: c[1] as f64,
11055 b: c[2] as f64,
11056 a: c[3] as f64,
11057 }),
11058 None => wgpu::LoadOp::Load,
11059 },
11060 store: wgpu::StoreOp::Store,
11061 },
11062 depth_slice: None,
11063 })],
11064 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
11065 view: &depth_stencil_view,
11066 depth_ops: None,
11067 stencil_ops: Some(wgpu::Operations {
11068 load: if pass.clear_color.is_some() {
11069 wgpu::LoadOp::Clear(STENCIL_BASE)
11070 } else {
11071 wgpu::LoadOp::Load
11072 },
11073 store: wgpu::StoreOp::Store,
11074 }),
11075 }),
11076 timestamp_writes: None,
11077 occlusion_query_set: None,
11078 multiview_mask: None,
11079 });
11080
11081 rpass.set_bind_group(0, &self.globals_bind, &[]);
11082 rpass.set_stencil_reference(STENCIL_BASE + clip_depth);
11083 let initial_draw_scissor = if initial_scissor.2 == 0 || initial_scissor.3 == 0 {
11084 (0, 0, 1, 1)
11085 } else {
11086 initial_scissor
11087 };
11088 rpass.set_scissor_rect(
11089 initial_draw_scissor.0,
11090 initial_draw_scissor.1,
11091 initial_draw_scissor.2,
11092 initial_draw_scissor.3,
11093 );
11094
11095 macro_rules! draw_simple {
11096 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
11097 rpass.set_pipeline($pipeline);
11098 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
11099 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
11100 rpass.draw(0..6, 0..$n);
11101 }};
11102 }
11103
11104 macro_rules! draw_with_bind {
11105 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
11106 rpass.set_pipeline($pipeline);
11107 rpass.set_bind_group(1, $bind, &[]);
11108 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
11109 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
11110 rpass.draw(0..6, 0..$n);
11111 }};
11112 }
11113
11114 macro_rules! draw_indexed_mesh {
11115 ($pipeline:expr, $uoff:ident, $mesh:ident) => {{
11116 rpass.set_pipeline($pipeline);
11117 rpass.set_bind_group(1, &self.mesh_bind, &[$uoff as u32]);
11118 let vbytes =
11119 ($mesh.vertex_count as u64) * std::mem::size_of::<MeshVertex>() as u64;
11120 rpass.set_vertex_buffer(0, $mesh.vertices.slice(0..vbytes));
11121 let ibytes = ($mesh.index_count as u64) * std::mem::size_of::<u32>() as u64;
11122 rpass.set_index_buffer(
11123 $mesh.indices.slice(0..ibytes),
11124 wgpu::IndexFormat::Uint32,
11125 );
11126 rpass.draw_indexed(0..$mesh.index_count, 0, 0..1);
11127 }};
11128 }
11129
11130 for cmd in pass.cmds {
11131 if active_scissor.is_none()
11132 && !matches!(
11133 &cmd,
11134 Cmd::ClipPush { .. }
11135 | Cmd::ClipPop { .. }
11136 | Cmd::VectorClipPush { .. }
11137 | Cmd::VectorClipPop { .. }
11138 )
11139 {
11140 continue;
11141 }
11142 match cmd {
11143 Cmd::ClipPush {
11144 off,
11145 cnt: n,
11146 scissor,
11147 difference,
11148 applied,
11149 } => {
11150 let scissor =
11151 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
11152 let usable = applied && scissor.2 > 0 && scissor.3 > 0;
11153 if usable {
11154 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
11155 active_scissor = Some(scissor);
11156 rpass.set_pipeline(if difference {
11157 &pipes.clip_dec
11158 } else {
11159 &pipes.clip_bin
11160 });
11161
11162 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
11163 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
11164 rpass.draw(0..6, 0..n);
11165 if !difference {
11166 clip_depth = (clip_depth + 1).min(STENCIL_MAX_DEPTH);
11167 }
11168 } else {
11169 active_scissor = None;
11170 }
11171 rpass.set_stencil_reference(STENCIL_BASE + clip_depth);
11172 }
11173
11174 Cmd::ClipPop {
11175 off,
11176 cnt: n,
11177 scissor,
11178 stencil_scissor,
11179 difference,
11180 applied,
11181 } => {
11182 let scissor =
11183 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
11184 let dec = clamp_scissor(
11185 stencil_scissor.0,
11186 stencil_scissor.1,
11187 stencil_scissor.2,
11188 stencil_scissor.3,
11189 tw,
11190 th,
11191 );
11192 if scissor.2 > 0 && scissor.3 > 0 {
11193 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
11194 active_scissor = Some(scissor);
11195 } else {
11196 active_scissor = None;
11197 }
11198 if applied && n > 0 && dec.2 > 0 && dec.3 > 0 {
11199 rpass.set_scissor_rect(dec.0, dec.1, dec.2, dec.3);
11200 rpass.set_pipeline(if difference {
11201 &pipes.clip_bin
11202 } else {
11203 &pipes.clip_dec
11204 });
11205 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
11206 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
11207 rpass.draw(0..6, 0..n);
11208 if scissor.2 > 0 && scissor.3 > 0 {
11209 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
11210 }
11211 if !difference {
11212 clip_depth = clip_depth.saturating_sub(1);
11213 }
11214 }
11215 rpass.set_stencil_reference(STENCIL_BASE + clip_depth);
11216 }
11217
11218 Cmd::Rect { off, cnt: n } => {
11219 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
11220 }
11221
11222 Cmd::Border { off, cnt: n } => {
11223 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
11224 }
11225
11226 Cmd::GlyphsMask { off, cnt: n } => {
11227 draw_with_bind!(
11228 &pipes.text_mask,
11229 self.glyph_mask.ring,
11230 GlyphInstance,
11231 &bind_mask,
11232 off,
11233 n
11234 );
11235 }
11236
11237 Cmd::GlyphsColor { off, cnt: n } => {
11238 draw_with_bind!(
11239 &pipes.text_color,
11240 self.glyph_color.ring,
11241 GlyphInstance,
11242 &bind_color,
11243 off,
11244 n
11245 );
11246 }
11247
11248 Cmd::GlyphsVector { off, cnt: n } => {
11249 if let Some(slug_pipe) = pipes.slug.as_ref() {
11250 rpass.set_pipeline(slug_pipe);
11251 let bytes = (n as u64) * std::mem::size_of::<slug::TessVertex>() as u64;
11252 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
11253 rpass.draw(0..n, 0..1);
11254 }
11255 }
11256
11257 Cmd::ImageRgba {
11258 off,
11259 cnt: n,
11260 handle,
11261 filter,
11262 tile,
11263 } => {
11264 let bind_opt = match self.images.get(&handle) {
11265 Some(ImageTex::Rgba { binds, .. }) => Some(binds.get(filter, tile)),
11266 Some(ImageTex::User { binds, .. }) => Some(binds.get(filter, tile)),
11267 _ => None,
11268 };
11269 if let Some(bind) = bind_opt {
11270 draw_with_bind!(
11271 &pipes.image_rgba,
11272 self.glyph_color.ring,
11273 GlyphInstance,
11274 bind,
11275 off,
11276 n
11277 );
11278 }
11279 }
11280 Cmd::Coverage {
11281 off,
11282 cnt: n,
11283 handle,
11284 } => {
11285 if let Some(tile) = self.coverages.get(&handle) {
11286 draw_with_bind!(
11287 &pipes.coverage,
11288 self.glyph_color.ring,
11289 GlyphInstance,
11290 &tile.bind,
11291 off,
11292 n
11293 );
11294 }
11295 }
11296
11297 Cmd::ImageNv12 {
11298 off,
11299 cnt: n,
11300 handle,
11301 filter,
11302 } => {
11303 if let Some(ImageTex::Nv12 { binds, .. }) = self.images.get(&handle) {
11304 draw_with_bind!(
11305 &pipes.image_nv12,
11306 self.nv12.ring,
11307 Nv12Instance,
11308 binds.get(filter, false),
11309 off,
11310 n
11311 );
11312 }
11313 }
11314
11315 Cmd::Ellipse { off, cnt: n } => {
11316 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
11317 }
11318
11319 Cmd::EllipseBorder { off, cnt: n } => {
11320 draw_simple!(
11321 &pipes.ellipse_borders,
11322 self.ellipse_borders.ring,
11323 EllipseBorderInstance,
11324 off,
11325 n
11326 );
11327 }
11328
11329 Cmd::Arc { off, cnt: n } => {
11330 draw_simple!(&pipes.arcs, self.arcs.ring, ArcInstance, off, n);
11331 }
11332
11333 Cmd::CompositeLayer {
11334 off,
11335 cnt: n,
11336 layer_id,
11337 } => {
11338 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
11339 draw_with_bind!(
11340 &pipes.image_rgba,
11341 self.glyph_color.ring,
11342 GlyphInstance,
11343 <.bind,
11344 off,
11345 n
11346 );
11347 }
11348 }
11349 Cmd::CompositeShadow {
11350 off,
11351 cnt: n,
11352 layer_id,
11353 } => {
11354 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
11355 draw_with_bind!(
11356 &pipes.blur,
11357 self.blur_ring,
11358 BlurInstance,
11359 <.bind_linear,
11360 off,
11361 n
11362 );
11363 }
11364 }
11365 Cmd::CompositeBlur {
11366 off,
11367 cnt: n,
11368 layer_id,
11369 } => {
11370 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
11371 draw_with_bind!(
11372 &pipes.blur_content,
11373 self.blur_ring,
11374 BlurInstance,
11375 <.bind_linear,
11376 off,
11377 n
11378 );
11379 }
11380 }
11381 Cmd::CompositeProjective {
11382 off,
11383 cnt: n,
11384 layer_id,
11385 } => {
11386 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
11387 draw_with_bind!(
11391 &pipes.projective_layer,
11392 self.projective_ring,
11393 ProjectiveInstance,
11394 <.bind,
11395 off,
11396 n
11397 );
11398 }
11399 }
11400
11401 Cmd::BlendLayer {
11402 off,
11403 cnt: n,
11404 src_layer,
11405 dst_layer,
11406 parent,
11407 scissor,
11408 } => {
11409 let src = self.layer_pool.get(&src_layer).cloned();
11410 let dst = dst_layer.and_then(|id| self.blend_snapshots.get(&id).cloned());
11417 let in_parent = match parent {
11418 PassTarget::Surface => !is_layer,
11419 PassTarget::Layer(id) => {
11420 matches!(pass.target, PassTarget::Layer(pid) if pid == id)
11421 }
11422 };
11423 if let (Some(src_lt), Some(dst_snap)) = (src, dst)
11424 && in_parent
11425 {
11426 let bytes = (n as u64) * std::mem::size_of::<BlendInstance>() as u64;
11427 rpass.set_pipeline(&pipes.blend_layer);
11428 let scissor =
11429 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
11430 if scissor.2 > 0 && scissor.3 > 0 {
11431 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
11432 rpass.set_bind_group(0, &self.globals_bind, &[]);
11433 rpass.set_bind_group(1, &src_lt.bind, &[]);
11434 rpass.set_bind_group(2, &dst_snap.bind, &[]);
11435 rpass.set_vertex_buffer(
11436 0,
11437 self.blend_ring.buf.slice(off..off + bytes),
11438 );
11439 rpass.draw(0..6, 0..n);
11440 }
11441 }
11442 }
11443
11444 Cmd::VectorMesh { mesh, uoff, blend } => {
11445 let pipe = match blend {
11446 repose_core::BlendMode::Add => &pipes.mesh_add,
11447 repose_core::BlendMode::Multiply => &pipes.mesh_multiply,
11448 repose_core::BlendMode::Screen => &pipes.mesh_screen,
11449 repose_core::BlendMode::Darken => &pipes.mesh_darken,
11450 repose_core::BlendMode::Lighten => &pipes.mesh_lighten,
11451 _ => &pipes.mesh,
11452 };
11453 draw_indexed_mesh!(pipe, uoff, mesh);
11454 }
11455
11456 Cmd::VectorOverlay { mesh, uoff } => {
11457 rpass.set_stencil_reference(STENCIL_BASE + clip_depth);
11458 draw_indexed_mesh!(&pipes.mesh_overlay, uoff, mesh);
11459 }
11460
11461 Cmd::VectorClipPush {
11462 mesh,
11463 uoff,
11464 scissor,
11465 difference,
11466 applied,
11467 } => {
11468 let scissor =
11469 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
11470 let usable = applied && scissor.2 > 0 && scissor.3 > 0;
11471 if usable {
11472 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
11473 active_scissor = Some(scissor);
11474 let pipe = if difference {
11475 &pipes.mesh_clip_dec
11476 } else {
11477 &pipes.mesh_clip_inc
11478 };
11479 draw_indexed_mesh!(pipe, uoff, mesh);
11480 if !difference {
11481 clip_depth = (clip_depth + 1).min(STENCIL_MAX_DEPTH);
11482 }
11483 } else {
11484 active_scissor = None;
11485 }
11486 rpass.set_stencil_reference(STENCIL_BASE + clip_depth);
11487 }
11488
11489 Cmd::VectorClipPop {
11490 mesh,
11491 uoff,
11492 scissor,
11493 stencil_scissor,
11494 difference,
11495 applied,
11496 } => {
11497 let scissor =
11498 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
11499 let dec = clamp_scissor(
11500 stencil_scissor.0,
11501 stencil_scissor.1,
11502 stencil_scissor.2,
11503 stencil_scissor.3,
11504 tw,
11505 th,
11506 );
11507 if scissor.2 > 0 && scissor.3 > 0 {
11508 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
11509 active_scissor = Some(scissor);
11510 } else {
11511 active_scissor = None;
11512 }
11513 if applied && dec.2 > 0 && dec.3 > 0 {
11514 rpass.set_scissor_rect(dec.0, dec.1, dec.2, dec.3);
11515 let pipe = if difference {
11516 &pipes.mesh_clip_inc
11517 } else {
11518 &pipes.mesh_clip_dec
11519 };
11520 draw_indexed_mesh!(pipe, uoff, mesh);
11521 if scissor.2 > 0 && scissor.3 > 0 {
11522 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
11523 }
11524 if !difference {
11525 clip_depth = clip_depth.saturating_sub(1);
11526 }
11527 }
11528 rpass.set_stencil_reference(STENCIL_BASE + clip_depth);
11529 }
11530
11531 Cmd::Callback {
11532 rect,
11533 clip_rect,
11534 scissor,
11535 restore_scissor,
11536 callback_id,
11537 payload,
11538 } => {
11539 if let Some(cb) = payload.downcast_ref::<Callback>() {
11540 let vp_x = rect.x.ceil().max(0.0);
11546 let vp_y = rect.y.ceil().max(0.0);
11547 let vp_w = ((rect.x + rect.w).floor() - vp_x).max(1.0);
11548 let vp_h = ((rect.y + rect.h).floor() - vp_y).max(1.0);
11549 if vp_w > 0.0 && vp_h > 0.0 && clip_rect.w > 0.0 && clip_rect.h > 0.0 {
11550 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
11551 rpass.set_viewport(vp_x, vp_y, vp_w, vp_h, 0.0, 1.0);
11552 let info = repose_core::PaintCallbackInfo {
11553 viewport: rect,
11554 clip_rect,
11555 pixels_per_point: self.pixels_per_point,
11556 screen_size_px: [tw, th],
11557 };
11558 let descriptor = ScreenDescriptor {
11559 size_in_pixels: [tw, th],
11560 pixels_per_point: self.pixels_per_point,
11561 target_format: if is_layer {
11562 self.layer_target_format()
11563 } else if working_target {
11564 wgpu::TextureFormat::Rgba16Float
11565 } else {
11566 self.output_format
11567 },
11568 sample_count: if is_layer {
11569 1
11570 } else {
11571 self.active_surface_msaa_samples()
11572 },
11573 };
11574 let scope =
11575 callback_scope_key(cb, callback_id, pass.target, &descriptor);
11576 let resources = self
11577 .callback_scoped_resources
11578 .get(&scope)
11579 .unwrap_or(&self.callback_resources);
11580 let outcome = {
11587 let mut callback_pass = CallbackRenderPass::new(
11588 &mut rpass,
11589 descriptor.target_format,
11590 descriptor.sample_count,
11591 );
11592 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
11593 cb.0.paint(info, &mut callback_pass, resources)
11594 }))
11595 };
11596 if outcome.is_err() {
11597 log::warn!("paint callback panicked; state restored");
11598 }
11599 rpass.set_viewport(0.0, 0.0, tw as f32, th as f32, 0.0, 1.0);
11600 let restore = if restore_scissor.2 > 0 && restore_scissor.3 > 0 {
11601 restore_scissor
11602 } else {
11603 (0, 0, 1, 1)
11604 };
11605 rpass.set_scissor_rect(restore.0, restore.1, restore.2, restore.3);
11606 rpass.set_bind_group(0, &self.globals_bind, &[]);
11607 rpass.set_stencil_reference(STENCIL_BASE + clip_depth);
11608 }
11609 } else {
11610 log::warn!("Unknown paint callback payload");
11611 }
11612 }
11613 }
11614 }
11615 if is_layer {
11616 clip_depth = clip_depth_stack.pop().unwrap_or(0);
11617 }
11618 }
11619
11620 let transient_ids: HashSet<u32> = flatten_ids_used.iter().copied().collect();
11621 let producer_ids = std::mem::take(&mut self.producer_layer_ids);
11622 self.blur_scratch_ids
11626 .retain(|&(source, _), _| producer_ids.contains(&source));
11627 let live_scratch: HashSet<u32> = self.blur_scratch_ids.values().copied().collect();
11628 let stale_layers: Vec<u32> = self
11629 .layer_pool
11630 .keys()
11631 .filter_map(|id| {
11632 (!producer_ids.contains(id)
11633 && !transient_ids.contains(id)
11634 && !live_scratch.contains(id))
11635 .then_some(*id)
11636 })
11637 .collect();
11638 for id in stale_layers {
11639 if let Some(layer) = self.layer_pool.remove(&id) {
11640 self.layer_bytes_total = self.layer_bytes_total.saturating_sub(layer.bytes);
11641 if layer.transient {
11642 self.transient_layer_bytes_total =
11643 self.transient_layer_bytes_total.saturating_sub(layer.bytes);
11644 }
11645 }
11646 }
11647 self.flatten_layer_ids = flatten_ids_used;
11648
11649 if self.working_space
11651 && let (Some(_ws_view), Some(ws_bind), Some(display_pipeline)) =
11652 (&self.ws_view, &self.ws_bind, &self.display_pipeline)
11653 {
11654 let swap_view = target_view.clone();
11655 let mut display_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
11656 label: Some("display transform"),
11657 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
11658 view: &swap_view,
11659 resolve_target: None,
11660 ops: wgpu::Operations {
11661 load: wgpu::LoadOp::Load,
11662 store: wgpu::StoreOp::Store,
11663 },
11664 depth_slice: None,
11665 })],
11666 depth_stencil_attachment: None,
11667 timestamp_writes: None,
11668 occlusion_query_set: None,
11669 multiview_mask: None,
11670 });
11671 display_pass.set_pipeline(display_pipeline);
11672 display_pass.set_bind_group(1, ws_bind, &[]);
11673 display_pass.draw(0..3, 0..1);
11674 }
11675 self.recycle_blend_snapshots();
11676 }
11677
11678 pub fn render_to_view(
11682 &mut self,
11683 scene: &Scene,
11684 encoder: &mut wgpu::CommandEncoder,
11685 target_view: &wgpu::TextureView,
11686 width: u32,
11687 height: u32,
11688 clear_color: Option<[f64; 4]>,
11689 ) {
11690 self.resize(width, height);
11691
11692 if let Err(error) = self.begin_frame() {
11693 log::error!("render_to_view: {error:#}");
11694 return;
11695 }
11696
11697 if self.output_width == 0 || self.output_height == 0 {
11698 self.end_frame();
11699 return;
11700 }
11701
11702 self.render_scene_to_encoder(scene, encoder, target_view, clear_color);
11703 self.end_frame();
11704 }
11705}
11706
11707#[cfg(target_os = "linux")]
11708fn validate_dmabuf_plane(
11709 fd: &std::os::unix::io::OwnedFd,
11710 offset: u64,
11711 stride: u32,
11712 width: u32,
11713 height: u32,
11714 bytes_per_pixel: u32,
11715) -> anyhow::Result<u64> {
11716 if width == 0 || height == 0 || bytes_per_pixel == 0 {
11717 anyhow::bail!("DMA-BUF plane dimensions and bytes per pixel must be non-zero");
11718 }
11719 if !offset.is_multiple_of(4) || !stride.is_multiple_of(4) {
11720 anyhow::bail!("DMA-BUF offset and stride must be 4-byte aligned");
11721 }
11722 let row_bytes = (width as u64)
11723 .checked_mul(bytes_per_pixel as u64)
11724 .ok_or_else(|| anyhow::anyhow!("DMA-BUF row size overflow"))?;
11725 if (stride as u64) < row_bytes {
11726 anyhow::bail!(
11727 "DMA-BUF stride {} is smaller than row size {row_bytes}",
11728 stride
11729 );
11730 }
11731 let span = (height as u64 - 1)
11732 .checked_mul(stride as u64)
11733 .and_then(|value| value.checked_add(row_bytes))
11734 .ok_or_else(|| anyhow::anyhow!("DMA-BUF plane size overflow"))?;
11735 let file = std::fs::File::from(
11736 fd.try_clone()
11737 .map_err(|error| anyhow::anyhow!("duplicate DMA-BUF fd: {error}"))?,
11738 );
11739 let file_size = file
11740 .metadata()
11741 .map_err(|error| anyhow::anyhow!("stat DMA-BUF fd: {error}"))?
11742 .len();
11743 if offset > file_size || span > file_size - offset {
11744 anyhow::bail!("DMA-BUF plane offset/size {offset}+{span} exceeds file size {file_size}");
11745 }
11746 Ok(file_size)
11747}
11748
11749fn is_filterable_color_format(format: wgpu::TextureFormat) -> bool {
11750 matches!(
11751 format,
11752 wgpu::TextureFormat::Rgba8Unorm
11753 | wgpu::TextureFormat::Rgba8UnormSrgb
11754 | wgpu::TextureFormat::Bgra8Unorm
11755 | wgpu::TextureFormat::Bgra8UnormSrgb
11756 | wgpu::TextureFormat::Rgba16Unorm
11757 | wgpu::TextureFormat::Rgba16Float
11758 | wgpu::TextureFormat::Rgba32Float
11759 | wgpu::TextureFormat::Rgb10a2Unorm
11760 )
11761}
11762
11763fn native_format_supported(format: wgpu::TextureFormat, features: wgpu::Features) -> bool {
11764 if !is_filterable_color_format(format) {
11765 return false;
11766 }
11767 match format {
11768 wgpu::TextureFormat::Rgba16Unorm => {
11769 features.contains(wgpu::Features::TEXTURE_FORMAT_16BIT_NORM)
11770 }
11771 wgpu::TextureFormat::Rgba32Float => features.contains(wgpu::Features::FLOAT32_FILTERABLE),
11772 _ => true,
11773 }
11774}
11775
11776fn validate_sampler_descriptor(
11777 device: &wgpu::Device,
11778 sampler: &wgpu::SamplerDescriptor<'_>,
11779) -> anyhow::Result<()> {
11780 if sampler.compare.is_some()
11781 || !matches!(
11782 sampler.mag_filter,
11783 wgpu::FilterMode::Nearest | wgpu::FilterMode::Linear
11784 )
11785 || !matches!(
11786 sampler.min_filter,
11787 wgpu::FilterMode::Nearest | wgpu::FilterMode::Linear
11788 )
11789 || sampler.anisotropy_clamp == 0
11790 || !sampler.lod_min_clamp.is_finite()
11791 || !sampler.lod_max_clamp.is_finite()
11792 || sampler.lod_min_clamp < 0.0
11793 || sampler.lod_max_clamp < sampler.lod_min_clamp
11794 {
11795 anyhow::bail!("native sampler must be non-comparison linear filtering with finite LODs");
11796 }
11797 if (matches!(sampler.address_mode_u, wgpu::AddressMode::ClampToBorder)
11798 || matches!(sampler.address_mode_v, wgpu::AddressMode::ClampToBorder)
11799 || matches!(sampler.address_mode_w, wgpu::AddressMode::ClampToBorder))
11800 && !device
11801 .features()
11802 .contains(wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER)
11803 {
11804 anyhow::bail!("native sampler uses unsupported clamp-to-border addressing");
11805 }
11806 Ok(())
11807}
11808
11809fn validate_texture_dimensions(
11810 device: &wgpu::Device,
11811 width: u32,
11812 height: u32,
11813) -> anyhow::Result<()> {
11814 if width == 0
11815 || height == 0
11816 || width > device.limits().max_texture_dimension_2d
11817 || height > device.limits().max_texture_dimension_2d
11818 {
11819 anyhow::bail!("texture dimensions are outside the device limit");
11820 }
11821 Ok(())
11822}
11823
11824fn validate_image_handle(handle: u64) -> anyhow::Result<()> {
11825 if handle == 0 {
11826 anyhow::bail!("image handle 0 is reserved");
11827 }
11828 Ok(())
11829}
11830
11831fn decode_limits(max_dimension: u32) -> image::Limits {
11836 let mut limits = image::Limits::default();
11837 limits.max_image_width = Some(max_dimension);
11838 limits.max_image_height = Some(max_dimension);
11839 limits.max_alloc = Some(MAX_IMAGE_DECODE_BYTES);
11840 limits
11841}
11842
11843fn decode_image_rgba8(data: &[u8], max_dimension: u32) -> anyhow::Result<(u32, u32, Vec<u8>)> {
11847 let format = image::guess_format(data)?;
11848 let (w, h) = {
11849 let mut probe = image::ImageReader::with_format(std::io::Cursor::new(data), format);
11850 probe.limits(decode_limits(max_dimension));
11851 probe.into_dimensions()?
11852 };
11853 validate_image_decode_budget(w, h, max_dimension)?;
11854 let mut reader = image::ImageReader::with_format(std::io::Cursor::new(data), format);
11855 reader.limits(decode_limits(max_dimension));
11856 let rgba = reader.decode()?.into_rgba8();
11857 Ok((w, h, rgba.into_raw()))
11858}
11859
11860fn validate_image_decode_budget(w: u32, h: u32, max_dimension: u32) -> anyhow::Result<()> {
11861 if w == 0 || h == 0 {
11862 anyhow::bail!("encoded image has zero dimensions");
11863 }
11864 if w > max_dimension || h > max_dimension {
11865 anyhow::bail!("encoded image {w}x{h} exceeds the device texture limit {max_dimension}");
11866 }
11867 let bytes = checked_image_bytes(w, h, 4)?;
11868 if bytes > MAX_IMAGE_DECODE_BYTES {
11869 anyhow::bail!(
11870 "encoded image {w}x{h} needs {bytes} bytes to decode, over the {MAX_IMAGE_DECODE_BYTES} byte limit"
11871 );
11872 }
11873 Ok(())
11874}
11875
11876fn checked_image_bytes(w: u32, h: u32, bytes_per_pixel: u32) -> anyhow::Result<u64> {
11877 if w == 0 || h == 0 || bytes_per_pixel == 0 {
11878 anyhow::bail!("image dimensions and bytes per pixel must be non-zero");
11879 }
11880 (w as u64)
11881 .checked_mul(h as u64)
11882 .and_then(|size| size.checked_mul(bytes_per_pixel as u64))
11883 .ok_or_else(|| anyhow::anyhow!("image byte size overflow"))
11884}
11885
11886fn texture_storage_bytes(
11887 format: wgpu::TextureFormat,
11888 width: u32,
11889 height: u32,
11890) -> anyhow::Result<u64> {
11891 if width == 0 || height == 0 {
11892 anyhow::bail!("texture dimensions must be non-zero");
11893 }
11894 let bytes = format.theoretical_memory_footprint(wgpu::Extent3d {
11895 width,
11896 height,
11897 depth_or_array_layers: 1,
11898 });
11899 if bytes == 0 {
11900 anyhow::bail!("texture format has no storage size");
11901 }
11902 Ok(bytes)
11903}
11904
11905fn texture_storage_bytes_with_samples(
11906 format: wgpu::TextureFormat,
11907 width: u32,
11908 height: u32,
11909 sample_count: u32,
11910) -> anyhow::Result<u64> {
11911 texture_storage_bytes(format, width, height)?
11912 .checked_mul(u64::from(sample_count.max(1)))
11913 .ok_or_else(|| anyhow::anyhow!("multisample texture size overflow"))
11914}
11915
11916fn checked_image_bytes_usize(w: u32, h: u32, bytes_per_pixel: u32) -> anyhow::Result<usize> {
11917 usize::try_from(checked_image_bytes(w, h, bytes_per_pixel)?)
11918 .map_err(|_| anyhow::anyhow!("image byte size exceeds addressable memory"))
11919}
11920
11921fn clamp_scissor(x: u32, y: u32, w: u32, h: u32, tw: u32, th: u32) -> (u32, u32, u32, u32) {
11922 if w == 0 || h == 0 || tw == 0 || th == 0 {
11923 return (0, 0, 0, 0);
11924 }
11925 let x = x.min(tw - 1);
11926 let y = y.min(th - 1);
11927 let w = w.min(tw - x);
11928 let h = h.min(th - y);
11929 (x, y, w, h)
11930}
11931
11932fn rect_to_scissor(r: repose_core::Rect, width: u32, height: u32) -> (u32, u32, u32, u32) {
11933 if width == 0
11934 || height == 0
11935 || !r.x.is_finite()
11936 || !r.y.is_finite()
11937 || !r.w.is_finite()
11938 || !r.h.is_finite()
11939 || r.w <= 0.0
11940 || r.h <= 0.0
11941 {
11942 return (0, 0, 0, 0);
11943 }
11944 let x0 = r.x.ceil().max(0.0).min(width as f32);
11947 let y0 = r.y.ceil().max(0.0).min(height as f32);
11948 let x1 = (r.x + r.w).floor().max(x0).min(width as f32);
11949 let y1 = (r.y + r.h).floor().max(y0).min(height as f32);
11950 if x1 <= x0 || y1 <= y0 {
11951 return (0, 0, 0, 0);
11952 }
11953 (x0 as u32, y0 as u32, (x1 - x0) as u32, (y1 - y0) as u32)
11954}
11955
11956fn rect_to_ndc(r: repose_core::Rect, width: f32, height: f32) -> [f32; 4] {
11957 if width <= 0.0 || height <= 0.0 || !r.x.is_finite() || !r.y.is_finite() {
11958 return [0.0, 0.0, 0.0, 0.0];
11959 }
11960 let x0 = r.x / width * 2.0 - 1.0;
11961 let y0 = 1.0 - r.y / height * 2.0;
11962 let x1 = (r.x + r.w) / width * 2.0 - 1.0;
11963 let y1 = 1.0 - (r.y + r.h) / height * 2.0;
11964 [x0.min(x1), y0.min(y1), (x1 - x0).abs(), (y1 - y0).abs()]
11965}
11966
11967fn parent_rect_to_layer_rect(
11968 rect: repose_core::Rect,
11969 parent_transform: &Transform,
11970 origin: (f32, f32),
11971) -> Option<repose_core::Rect> {
11972 let inverse = parent_transform.inverse_linear()?;
11973 let tx = parent_transform.translate_x;
11974 let ty = parent_transform.translate_y;
11975 let corners = [
11976 (rect.x, rect.y),
11977 (rect.x + rect.w, rect.y),
11978 (rect.x, rect.y + rect.h),
11979 (rect.x + rect.w, rect.y + rect.h),
11980 ];
11981 let mut min_x = f32::INFINITY;
11982 let mut min_y = f32::INFINITY;
11983 let mut max_x = f32::NEG_INFINITY;
11984 let mut max_y = f32::NEG_INFINITY;
11985 for (x, y) in corners {
11986 let local_x = x - tx;
11987 let local_y = y - ty;
11988 let transformed_x = inverse[0] * local_x + inverse[1] * local_y;
11989 let transformed_y = inverse[2] * local_x + inverse[3] * local_y;
11990 min_x = min_x.min(transformed_x);
11991 min_y = min_y.min(transformed_y);
11992 max_x = max_x.max(transformed_x);
11993 max_y = max_y.max(transformed_y);
11994 }
11995 let result = repose_core::Rect {
11996 x: min_x - origin.0,
11997 y: min_y - origin.1,
11998 w: (max_x - min_x).max(0.0),
11999 h: (max_y - min_y).max(0.0),
12000 };
12001 (result.x.is_finite() && result.y.is_finite() && result.w.is_finite() && result.h.is_finite())
12002 .then_some(result)
12003}
12004
12005fn parent_radii_to_layer_radii(radii: [f32; 4], parent_transform: &Transform) -> Option<[f32; 4]> {
12006 let m = parent_transform.linear();
12007 let sx = (m[0] * m[0] + m[2] * m[2]).sqrt();
12008 let sy = (m[1] * m[1] + m[3] * m[3]).sqrt();
12009 (sx.is_finite() && sy.is_finite() && sx > 1e-6 && sy > 1e-6)
12010 .then(|| [radii[0] / sx, radii[1] / sy, radii[2] / sx, radii[3] / sy])
12011}
12012
12013fn parent_affine_to_layer_affine(
12014 affine: [f32; 6],
12015 parent_transform: &Transform,
12016 origin: (f32, f32),
12017) -> Option<[f32; 6]> {
12018 let inverse = parent_transform.inverse_linear()?;
12019 let tx = affine[4] - parent_transform.translate_x;
12020 let ty = affine[5] - parent_transform.translate_y;
12021 Some([
12022 inverse[0] * affine[0] + inverse[1] * affine[2],
12023 inverse[0] * affine[1] + inverse[1] * affine[3],
12024 inverse[2] * affine[0] + inverse[3] * affine[2],
12025 inverse[2] * affine[1] + inverse[3] * affine[3],
12026 inverse[0] * tx + inverse[1] * ty - origin.0,
12027 inverse[2] * tx + inverse[3] * ty - origin.1,
12028 ])
12029}
12030
12031fn checked_copy_region(
12032 region: repose_core::Rect,
12033 target_width: u32,
12034 target_height: u32,
12035) -> Option<(u32, u32, u32, u32)> {
12036 if target_width == 0
12037 || target_height == 0
12038 || !region.x.is_finite()
12039 || !region.y.is_finite()
12040 || !region.w.is_finite()
12041 || !region.h.is_finite()
12042 || region.w <= 0.0
12043 || region.h <= 0.0
12044 {
12045 return None;
12046 }
12047 let x0 = region.x.floor().max(0.0).min(target_width as f32) as u32;
12048 let y0 = region.y.floor().max(0.0).min(target_height as f32) as u32;
12049 let x1 = (region.x + region.w)
12050 .ceil()
12051 .max(x0 as f32)
12052 .min(target_width as f32) as u32;
12053 let y1 = (region.y + region.h)
12054 .ceil()
12055 .max(y0 as f32)
12056 .min(target_height as f32) as u32;
12057 (x1 > x0 && y1 > y0).then_some((x0, y0, x1 - x0, y1 - y0))
12058}
12059
12060fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
12061 let x0 = a.x.max(b.x);
12062 let y0 = a.y.max(b.y);
12063 let x1 = (a.x + a.w).min(b.x + b.w);
12064 let y1 = (a.y + a.h).min(b.y + b.h);
12065 repose_core::Rect {
12066 x: x0,
12067 y: y0,
12068 w: (x1 - x0).max(0.0),
12069 h: (y1 - y0).max(0.0),
12070 }
12071}
12072
12073#[cfg(test)]
12074mod tests {
12075 use super::*;
12076
12077 #[test]
12078 fn image_source_rect_uses_texel_edges() {
12079 let source = resolve_image_source(Some(ImageSourceRect::new(2, 1, 3, 2)), 10, 8)
12080 .expect("source should be valid");
12081 assert_eq!(source.uv_min, [0.2, 0.125]);
12082 assert_eq!(source.uv_max, [0.5, 0.375]);
12083 assert_eq!(source.width, 3.0);
12084 assert_eq!(source.height, 2.0);
12085 assert!(resolve_image_source(Some(ImageSourceRect::new(8, 0, 3, 1)), 10, 8).is_none());
12086 assert!(resolve_image_source(Some(ImageSourceRect::new(0, 0, 0, 1)), 10, 8).is_none());
12087 }
12088
12089 #[test]
12090 fn image_fit_stays_inside_selected_source() {
12091 let source = resolve_image_source(Some(ImageSourceRect::new(2, 1, 4, 2)), 10, 8)
12092 .expect("source should be valid");
12093 let rect = repose_core::Rect {
12094 x: 20.0,
12095 y: 30.0,
12096 w: 8.0,
12097 h: 8.0,
12098 };
12099 let (_, uv) = image_fit_geometry(rect, ImageFit::Cover, ImageAlignment::Center, &source)
12100 .expect("cover geometry should be valid");
12101 assert!(uv[0] >= source.uv_min[0] && uv[2] <= source.uv_max[0]);
12102 assert!(uv[1] >= source.uv_min[1] && uv[3] <= source.uv_max[1]);
12103 }
12104
12105 #[test]
12106 fn ring_alignment_rejects_bad_input() {
12107 assert_eq!(align_up(0, 4).unwrap(), 0);
12108 assert_eq!(align_up(1, 4).unwrap(), 4);
12109 assert!(align_up(1, 3).is_err());
12110 assert!(align_up(u64::MAX, 4).is_err());
12111 }
12112
12113 #[test]
12118 fn atlas_eviction_spares_the_frame_in_flight() {
12119 fn key(glyph_id: u32) -> repose_text::CacheKey {
12120 repose_text::CacheKey {
12121 font_id: 1,
12122 glyph_id,
12123 font_size_bits: 16.0f32.to_bits(),
12124 variation: None,
12125 }
12126 }
12127 let mut map: HashMap<repose_text::CacheKey, GlyphInfo> = HashMap::new();
12128 for (glyph_id, last_touch, last_used_frame) in
12129 [(1u32, 10u64, 9u64), (2, 20, 7), (3, 30, 7), (4, 40, 7)]
12130 {
12131 map.insert(
12132 key(glyph_id),
12133 GlyphInfo {
12134 u0: 0.0,
12135 v0: 0.0,
12136 u1: 0.1,
12137 v1: 0.1,
12138 w: 10.0,
12139 h: 10.0,
12140 last_touch,
12141 last_used_frame,
12142 },
12143 );
12144 }
12145 let mut used_area = 400;
12146 assert!(evict_atlas_lru(&mut map, &mut used_area, 9, 250));
12147 assert_eq!(used_area, 200);
12148 assert!(map.contains_key(&key(1)), "in-flight glyph was evicted");
12149 assert!(!map.contains_key(&key(2)) && !map.contains_key(&key(3)));
12150 assert!(map.contains_key(&key(4)));
12151 }
12152
12153 #[test]
12154 fn atlas_eviction_refuses_to_empty_the_atlas() {
12155 let only = repose_text::CacheKey {
12156 font_id: 1,
12157 glyph_id: 1,
12158 font_size_bits: 16.0f32.to_bits(),
12159 variation: None,
12160 };
12161 let mut map: HashMap<repose_text::CacheKey, GlyphInfo> = HashMap::new();
12162 map.insert(
12163 only,
12164 GlyphInfo {
12165 u0: 0.0,
12166 v0: 0.0,
12167 u1: 0.1,
12168 v1: 0.1,
12169 w: 10.0,
12170 h: 10.0,
12171 last_touch: 1,
12172 last_used_frame: 1,
12173 },
12174 );
12175 let mut used_area = 100;
12176 assert!(!evict_atlas_lru(&mut map, &mut used_area, 2, 0));
12177 assert_eq!(map.len(), 1);
12178 assert_eq!(used_area, 100);
12179 }
12180
12181 #[test]
12182 fn empty_and_negative_rects_are_suppressed() {
12183 assert_eq!(
12184 rect_to_scissor(
12185 repose_core::Rect {
12186 x: 0.0,
12187 y: 0.0,
12188 w: 0.0,
12189 h: 4.0,
12190 },
12191 16,
12192 16,
12193 ),
12194 (0, 0, 0, 0)
12195 );
12196 assert_eq!(
12197 rect_to_scissor(
12198 repose_core::Rect {
12199 x: 0.0,
12200 y: 0.0,
12201 w: -1.0,
12202 h: 4.0,
12203 },
12204 16,
12205 16,
12206 ),
12207 (0, 0, 0, 0)
12208 );
12209 }
12210
12211 #[test]
12212 fn copy_region_is_checked() {
12213 assert_eq!(
12214 checked_copy_region(
12215 repose_core::Rect {
12216 x: 2.25,
12217 y: 3.5,
12218 w: 4.0,
12219 h: 5.0,
12220 },
12221 16,
12222 16,
12223 ),
12224 Some((2, 3, 5, 6))
12225 );
12226 assert_eq!(
12227 checked_copy_region(
12228 repose_core::Rect {
12229 x: 20.0,
12230 y: 0.0,
12231 w: 2.0,
12232 h: 2.0,
12233 },
12234 16,
12235 16,
12236 ),
12237 None
12238 );
12239 }
12240
12241 #[test]
12242 fn decode_budget_rejects_oversized_encoded_images() {
12243 assert!(validate_image_decode_budget(64, 64, 8192).is_ok());
12244 assert!(validate_image_decode_budget(0, 64, 8192).is_err());
12245 assert!(validate_image_decode_budget(64, 0, 8192).is_err());
12246 assert!(validate_image_decode_budget(8193, 1, 8192).is_err());
12247 assert!(validate_image_decode_budget(1, 8193, 8192).is_err());
12248 assert!(validate_image_decode_budget(8192, 4096, 8192).is_err());
12250 }
12251
12252 #[test]
12253 fn only_wgpu_supported_sample_counts_are_accepted() {
12254 for samples in [1, 2, 4, 8, 16] {
12255 assert!(validate_sample_count("msaa_samples", samples).is_ok());
12256 }
12257 for samples in [0, 3, 5, 7, 9, 17, 32, u32::MAX] {
12258 assert!(
12259 validate_sample_count("msaa_samples", samples).is_err(),
12260 "{samples} must be rejected"
12261 );
12262 }
12263 }
12264
12265 #[test]
12266 fn p010_uses_ten_bit_sample_range() {
12267 let raw = make_yuv_transform_raw(ColorInfo::default(), PixelFormat::P010);
12268 let expected = 65535.0 / (64.0 * 1020.0);
12269 assert!((raw.b[3] - expected).abs() < 1e-6);
12270 assert_eq!(canonical_fourcc(PixelFormat::P010), P010_FOURCC);
12271 assert_eq!(canonical_fourcc(PixelFormat::Nv12), NV12_FOURCC);
12272 assert_eq!(
12273 make_yuv_transform_raw(
12274 ColorInfo {
12275 transfer: repose_core::color::Transfer::Pq,
12276 ..ColorInfo::default()
12277 },
12278 PixelFormat::Nv12,
12279 )
12280 .transfer[0],
12281 3.0
12282 );
12283 assert_eq!(
12284 make_yuv_transform_raw(
12285 ColorInfo {
12286 transfer: repose_core::color::Transfer::Hlg,
12287 ..ColorInfo::default()
12288 },
12289 PixelFormat::Nv12,
12290 )
12291 .transfer[0],
12292 4.0
12293 );
12294 }
12295}