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