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