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