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