1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use repose_core::request_frame;
6use repose_core::{
7 Brush, FontStyle, GlyphRasterConfig, RenderBackend, Scene, SceneNode, StrokeCap, Transform,
8};
9use std::panic::{AssertUnwindSafe, catch_unwind};
10use wgpu::Instance;
11
12mod slug;
13
14#[derive(Clone)]
15struct UploadRing {
16 buf: wgpu::Buffer,
17 cap: u64,
18 head: u64,
19}
20
21impl UploadRing {
22 fn new(device: &wgpu::Device, label: &str, cap: u64) -> Self {
23 let buf = device.create_buffer(&wgpu::BufferDescriptor {
24 label: Some(label),
25 size: cap,
26 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
27 mapped_at_creation: false,
28 });
29 Self { buf, cap, head: 0 }
30 }
31
32 fn reset(&mut self) {
33 self.head = 0;
34 }
35
36 fn grow_to_fit(&mut self, device: &wgpu::Device, needed: u64) {
37 let start = (self.head + 3) & !3;
38 if start + needed <= self.cap {
39 return;
40 }
41 let new_cap = (start + needed).next_power_of_two();
42 self.buf = device.create_buffer(&wgpu::BufferDescriptor {
43 label: Some("upload ring (grown)"),
44 size: new_cap,
45 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
46 mapped_at_creation: false,
47 });
48 self.cap = new_cap;
49 }
50
51 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> (u64, u64) {
52 let len = bytes.len() as u64;
53 let start = (self.head + 3) & !3; let end = start + len;
55 assert!(end <= self.cap, "ring overflow - call grow_to_fit first");
56 queue.write_buffer(&self.buf, start, bytes);
57 self.head = end;
58 (start, len)
59 }
60}
61
62struct InstancedPipe<I: bytemuck::Pod> {
63 ring: UploadRing,
64 _marker: std::marker::PhantomData<I>,
65}
66
67impl<I: bytemuck::Pod> InstancedPipe<I> {
68 fn new(ring: UploadRing) -> Self {
69 Self {
70 ring,
71 _marker: std::marker::PhantomData,
72 }
73 }
74
75 fn upload(
76 &mut self,
77 device: &wgpu::Device,
78 queue: &wgpu::Queue,
79 data: &[I],
80 ) -> Option<(u64, u32)> {
81 if data.is_empty() {
82 return None;
83 }
84 let bytes = bytemuck::cast_slice(data);
85 self.ring.grow_to_fit(device, bytes.len() as u64);
86 let (off, wrote) = self.ring.alloc_write(queue, bytes);
87 debug_assert_eq!(wrote as usize, bytes.len());
88 Some((off, data.len() as u32))
89 }
90
91 fn reset(&mut self) {
92 self.ring.reset();
93 }
94}
95
96#[repr(C)]
97#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
98struct Globals {
99 ndc_to_px: [f32; 2],
100 _pad: [f32; 2],
101}
102
103pub struct WgpuBackend {
104 surface: wgpu::Surface<'static>,
105 device: wgpu::Device,
106 queue: wgpu::Queue,
107 config: wgpu::SurfaceConfiguration,
108
109 surface_pipes: Pipelines,
112 layer_pipes: Pipelines,
113
114 rects: InstancedPipe<RectInstance>,
116 borders: InstancedPipe<BorderInstance>,
117 ellipses: InstancedPipe<EllipseInstance>,
118 ellipse_borders: InstancedPipe<EllipseBorderInstance>,
119 arcs: InstancedPipe<ArcInstance>,
120 glyph_mask: InstancedPipe<GlyphInstance>,
121 glyph_color: InstancedPipe<GlyphInstance>,
122
123 image_bind_layout_rgba: wgpu::BindGroupLayout,
125 image_bind_layout_nv12: wgpu::BindGroupLayout,
126 image_sampler: wgpu::Sampler,
127
128 blur_ring: UploadRing,
130
131 text_bind_layout: wgpu::BindGroupLayout,
132
133 clip_ring: UploadRing,
135
136 slug_enabled: bool,
138 slug_ring: UploadRing,
139 slug_cache: slug::GlyphSlugCache,
140
141 nv12: InstancedPipe<Nv12Instance>,
143
144 msaa_samples: u32,
145
146 depth_stencil_tex: wgpu::Texture,
148 depth_stencil_view: wgpu::TextureView,
149
150 msaa_tex: Option<wgpu::Texture>,
152 msaa_view: Option<wgpu::TextureView>,
153
154 globals_layout: wgpu::BindGroupLayout,
155 globals_buf: wgpu::Buffer,
156 globals_bind: wgpu::BindGroup,
157
158 atlas_mask: AtlasA8,
160 atlas_color: AtlasRGBA,
161
162 next_image_handle: u64,
164 images: HashMap<u64, ImageTex>,
165
166 frame_index: u64,
168 image_bytes_total: u64,
169 image_evict_after_frames: u64,
170 image_budget_bytes: u64,
171
172 layer_pool: HashMap<u32, LayerTarget>,
175}
176
177impl Drop for WgpuBackend {
178 fn drop(&mut self) {
179 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
180 }
181}
182
183#[derive(Clone)]
184struct LayerTarget {
185 texture: wgpu::Texture,
186 view: wgpu::TextureView,
187 bind: wgpu::BindGroup,
188 depth_stencil_tex: wgpu::Texture,
189 depth_stencil_view: wgpu::TextureView,
190 width: u32,
191 height: u32,
192 rect_px: (f32, f32, f32, f32),
193}
194
195#[derive(Clone, Copy)]
197enum PassTarget {
198 Surface,
199 Layer(u32),
200}
201
202struct Pipelines {
207 rects: wgpu::RenderPipeline,
208 borders: wgpu::RenderPipeline,
209 ellipses: wgpu::RenderPipeline,
210 ellipse_borders: wgpu::RenderPipeline,
211 arcs: wgpu::RenderPipeline,
212 text_mask: wgpu::RenderPipeline,
213 text_color: wgpu::RenderPipeline,
214 image_rgba: wgpu::RenderPipeline,
215 image_nv12: wgpu::RenderPipeline,
216 blur: wgpu::RenderPipeline,
217 clip_a2c: wgpu::RenderPipeline,
218 clip_bin: wgpu::RenderPipeline,
219 slug: Option<wgpu::RenderPipeline>,
220}
221
222impl Pipelines {
223 fn create(
224 device: &wgpu::Device,
225 format: wgpu::TextureFormat,
226 sample_count: u32,
227 globals_layout: &wgpu::BindGroupLayout,
228 text_bind_layout: &wgpu::BindGroupLayout,
229 image_bind_layout_nv12: &wgpu::BindGroupLayout,
230 clip_pipeline_layout: &wgpu::PipelineLayout,
231 stencil_for_content: &wgpu::DepthStencilState,
232 stencil_for_clip_inc: &wgpu::DepthStencilState,
233 clip_color_target: &wgpu::ColorTargetState,
234 clip_vertex_layout: &wgpu::VertexBufferLayout,
235 ) -> Self {
236 let msaa_state = wgpu::MultisampleState {
237 count: sample_count,
238 mask: !0,
239 alpha_to_coverage_enabled: false,
240 };
241
242 macro_rules! make_content_pipeline {
243 ($name:ident, $shader:literal, $inst_type:ty, $attrs:expr) => {
244 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
245 label: Some(concat!($shader, ".wgsl")),
246 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(concat!(
247 "shaders/", $shader, ".wgsl"
248 )))),
249 });
250 let pipeline_layout =
251 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
252 label: Some(concat!($shader, " pipeline layout")),
253 bind_group_layouts: &[Some(globals_layout)],
254 immediate_size: 0,
255 });
256 let $name = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
257 label: Some(concat!($shader, " pipeline")),
258 layout: Some(&pipeline_layout),
259 vertex: wgpu::VertexState {
260 module: &shader_module,
261 entry_point: Some("vs_main"),
262 buffers: &[wgpu::VertexBufferLayout {
263 array_stride: std::mem::size_of::<$inst_type>() as u64,
264 step_mode: wgpu::VertexStepMode::Instance,
265 attributes: $attrs,
266 }],
267 compilation_options: wgpu::PipelineCompilationOptions::default(),
268 },
269 fragment: Some(wgpu::FragmentState {
270 module: &shader_module,
271 entry_point: Some("fs_main"),
272 targets: &[Some(wgpu::ColorTargetState {
273 format,
274 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
275 write_mask: wgpu::ColorWrites::ALL,
276 })],
277 compilation_options: wgpu::PipelineCompilationOptions::default(),
278 }),
279 primitive: wgpu::PrimitiveState::default(),
280 depth_stencil: Some(stencil_for_content.clone()),
281 multisample: msaa_state,
282 multiview_mask: None,
283 cache: None,
284 });
285 };
286 }
287
288 let rect_attrs: &[wgpu::VertexAttribute] = &[
289 wgpu::VertexAttribute {
290 shader_location: 0,
291 offset: 0,
292 format: wgpu::VertexFormat::Float32x4,
293 },
294 wgpu::VertexAttribute {
295 shader_location: 1,
296 offset: 16,
297 format: wgpu::VertexFormat::Float32x4,
298 },
299 wgpu::VertexAttribute {
300 shader_location: 2,
301 offset: 32,
302 format: wgpu::VertexFormat::Uint32,
303 },
304 wgpu::VertexAttribute {
305 shader_location: 3,
306 offset: 48,
307 format: wgpu::VertexFormat::Float32x4,
308 },
309 wgpu::VertexAttribute {
310 shader_location: 4,
311 offset: 64,
312 format: wgpu::VertexFormat::Float32x4,
313 },
314 wgpu::VertexAttribute {
315 shader_location: 5,
316 offset: 80,
317 format: wgpu::VertexFormat::Float32x2,
318 },
319 wgpu::VertexAttribute {
320 shader_location: 6,
321 offset: 88,
322 format: wgpu::VertexFormat::Float32x2,
323 },
324 wgpu::VertexAttribute {
325 shader_location: 7,
326 offset: 96,
327 format: wgpu::VertexFormat::Float32x2,
328 },
329 ];
330 let border_attrs: &[wgpu::VertexAttribute] = &[
331 wgpu::VertexAttribute {
332 shader_location: 0,
333 offset: 0,
334 format: wgpu::VertexFormat::Float32x4,
335 },
336 wgpu::VertexAttribute {
337 shader_location: 1,
338 offset: 16,
339 format: wgpu::VertexFormat::Float32x4,
340 },
341 wgpu::VertexAttribute {
342 shader_location: 2,
343 offset: 32,
344 format: wgpu::VertexFormat::Float32,
345 },
346 wgpu::VertexAttribute {
347 shader_location: 3,
348 offset: 36,
349 format: wgpu::VertexFormat::Float32x4,
350 },
351 wgpu::VertexAttribute {
352 shader_location: 4,
353 offset: 52,
354 format: wgpu::VertexFormat::Float32x2,
355 },
356 ];
357 let ellipse_attrs: &[wgpu::VertexAttribute] = &[
358 wgpu::VertexAttribute {
359 shader_location: 0,
360 offset: 0,
361 format: wgpu::VertexFormat::Float32x4,
362 },
363 wgpu::VertexAttribute {
364 shader_location: 1,
365 offset: 16,
366 format: wgpu::VertexFormat::Float32x4,
367 },
368 wgpu::VertexAttribute {
369 shader_location: 2,
370 offset: 32,
371 format: wgpu::VertexFormat::Float32x2,
372 },
373 ];
374 let ellipse_border_attrs: &[wgpu::VertexAttribute] = &[
375 wgpu::VertexAttribute {
376 shader_location: 0,
377 offset: 0,
378 format: wgpu::VertexFormat::Float32x4,
379 },
380 wgpu::VertexAttribute {
381 shader_location: 1,
382 offset: 16,
383 format: wgpu::VertexFormat::Float32,
384 },
385 wgpu::VertexAttribute {
386 shader_location: 2,
387 offset: 20,
388 format: wgpu::VertexFormat::Float32,
389 },
390 wgpu::VertexAttribute {
391 shader_location: 3,
392 offset: 24,
393 format: wgpu::VertexFormat::Float32x4,
394 },
395 wgpu::VertexAttribute {
396 shader_location: 4,
397 offset: 40,
398 format: wgpu::VertexFormat::Float32x2,
399 },
400 ];
401
402 make_content_pipeline!(rects, "rect", RectInstance, rect_attrs);
403 make_content_pipeline!(borders, "border", BorderInstance, border_attrs);
404 make_content_pipeline!(ellipses, "ellipse", EllipseInstance, ellipse_attrs);
405 make_content_pipeline!(
406 ellipse_borders,
407 "ellipse_border",
408 EllipseBorderInstance,
409 ellipse_border_attrs
410 );
411
412 let arc_attrs: &[wgpu::VertexAttribute] = &[
413 wgpu::VertexAttribute {
414 shader_location: 0,
415 offset: 0,
416 format: wgpu::VertexFormat::Float32x4,
417 },
418 wgpu::VertexAttribute {
419 shader_location: 1,
420 offset: 16,
421 format: wgpu::VertexFormat::Float32,
422 },
423 wgpu::VertexAttribute {
424 shader_location: 2,
425 offset: 20,
426 format: wgpu::VertexFormat::Float32,
427 },
428 wgpu::VertexAttribute {
429 shader_location: 3,
430 offset: 24,
431 format: wgpu::VertexFormat::Float32,
432 },
433 wgpu::VertexAttribute {
434 shader_location: 4,
435 offset: 28,
436 format: wgpu::VertexFormat::Float32,
437 },
438 wgpu::VertexAttribute {
439 shader_location: 5,
440 offset: 32,
441 format: wgpu::VertexFormat::Float32x4,
442 },
443 wgpu::VertexAttribute {
444 shader_location: 6,
445 offset: 48,
446 format: wgpu::VertexFormat::Float32x2,
447 },
448 wgpu::VertexAttribute {
449 shader_location: 7,
450 offset: 56,
451 format: wgpu::VertexFormat::Float32,
452 },
453 ];
454
455 make_content_pipeline!(arcs, "arc", ArcInstance, arc_attrs);
456
457 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
459 label: Some("text.wgsl"),
460 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
461 });
462 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
464 label: Some("text_color.wgsl"),
465 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
466 "shaders/text_color.wgsl"
467 ))),
468 });
469 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
470 label: Some("text pipeline layout"),
471 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
472 immediate_size: 0,
473 });
474 let glyph_vertex = wgpu::VertexBufferLayout {
475 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
476 step_mode: wgpu::VertexStepMode::Instance,
477 attributes: &[
478 wgpu::VertexAttribute {
479 shader_location: 0,
480 offset: 0,
481 format: wgpu::VertexFormat::Float32x4,
482 },
483 wgpu::VertexAttribute {
484 shader_location: 1,
485 offset: 16,
486 format: wgpu::VertexFormat::Float32x4,
487 },
488 wgpu::VertexAttribute {
489 shader_location: 2,
490 offset: 32,
491 format: wgpu::VertexFormat::Float32x4,
492 },
493 wgpu::VertexAttribute {
494 shader_location: 3,
495 offset: 48,
496 format: wgpu::VertexFormat::Float32x2,
497 },
498 ],
499 };
500 let text_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
501 label: Some("text pipeline (mask)"),
502 layout: Some(&text_pipeline_layout),
503 vertex: wgpu::VertexState {
504 module: &text_mask_shader,
505 entry_point: Some("vs_main"),
506 buffers: &[glyph_vertex.clone()],
507 compilation_options: wgpu::PipelineCompilationOptions::default(),
508 },
509 fragment: Some(wgpu::FragmentState {
510 module: &text_mask_shader,
511 entry_point: Some("fs_main"),
512 targets: &[Some(wgpu::ColorTargetState {
513 format,
514 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
515 write_mask: wgpu::ColorWrites::ALL,
516 })],
517 compilation_options: wgpu::PipelineCompilationOptions::default(),
518 }),
519 primitive: wgpu::PrimitiveState::default(),
520 depth_stencil: Some(stencil_for_content.clone()),
521 multisample: msaa_state,
522 multiview_mask: None,
523 cache: None,
524 });
525 let text_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
526 label: Some("text pipeline (color)"),
527 layout: Some(&text_pipeline_layout),
528 vertex: wgpu::VertexState {
529 module: &text_color_shader,
530 entry_point: Some("vs_main"),
531 buffers: &[glyph_vertex],
532 compilation_options: wgpu::PipelineCompilationOptions::default(),
533 },
534 fragment: Some(wgpu::FragmentState {
535 module: &text_color_shader,
536 entry_point: Some("fs_main"),
537 targets: &[Some(wgpu::ColorTargetState {
538 format,
539 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
540 write_mask: wgpu::ColorWrites::ALL,
541 })],
542 compilation_options: wgpu::PipelineCompilationOptions::default(),
543 }),
544 primitive: wgpu::PrimitiveState::default(),
545 depth_stencil: Some(stencil_for_content.clone()),
546 multisample: msaa_state,
547 multiview_mask: None,
548 cache: None,
549 });
550 let image_rgba = text_color.clone();
552
553 let blur_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
555 label: Some("blur_shadow.wgsl"),
556 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
557 "shaders/blur_shadow.wgsl"
558 ))),
559 });
560 let blur_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
561 label: Some("blur pipeline layout"),
562 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
563 immediate_size: 0,
564 });
565 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
566 label: Some("blur pipeline"),
567 layout: Some(&blur_pipeline_layout),
568 vertex: wgpu::VertexState {
569 module: &blur_shader,
570 entry_point: Some("vs_main"),
571 buffers: &[wgpu::VertexBufferLayout {
572 array_stride: std::mem::size_of::<BlurInstance>() as u64,
573 step_mode: wgpu::VertexStepMode::Instance,
574 attributes: &[
575 wgpu::VertexAttribute {
576 shader_location: 0,
577 offset: 0,
578 format: wgpu::VertexFormat::Float32x4,
579 },
580 wgpu::VertexAttribute {
581 shader_location: 1,
582 offset: 16,
583 format: wgpu::VertexFormat::Float32x4,
584 },
585 wgpu::VertexAttribute {
586 shader_location: 2,
587 offset: 32,
588 format: wgpu::VertexFormat::Float32x4,
589 },
590 wgpu::VertexAttribute {
591 shader_location: 3,
592 offset: 48,
593 format: wgpu::VertexFormat::Float32x2,
594 },
595 wgpu::VertexAttribute {
596 shader_location: 4,
597 offset: 56,
598 format: wgpu::VertexFormat::Float32x2,
599 },
600 ],
601 }],
602 compilation_options: wgpu::PipelineCompilationOptions::default(),
603 },
604 fragment: Some(wgpu::FragmentState {
605 module: &blur_shader,
606 entry_point: Some("fs_main"),
607 targets: &[Some(wgpu::ColorTargetState {
608 format,
609 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
610 write_mask: wgpu::ColorWrites::ALL,
611 })],
612 compilation_options: wgpu::PipelineCompilationOptions::default(),
613 }),
614 primitive: wgpu::PrimitiveState::default(),
615 depth_stencil: Some(stencil_for_content.clone()),
616 multisample: msaa_state,
617 multiview_mask: None,
618 cache: None,
619 });
620
621 let image_nv12_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
623 label: Some("image_nv12.wgsl"),
624 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
625 "shaders/image_nv12.wgsl"
626 ))),
627 });
628 let image_nv12_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
629 label: Some("image nv12 pipeline layout"),
630 bind_group_layouts: &[Some(globals_layout), Some(image_bind_layout_nv12)],
631 immediate_size: 0,
632 });
633 let image_nv12 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
634 label: Some("image nv12 pipeline"),
635 layout: Some(&image_nv12_layout),
636 vertex: wgpu::VertexState {
637 module: &image_nv12_shader,
638 entry_point: Some("vs_main"),
639 buffers: &[wgpu::VertexBufferLayout {
640 array_stride: std::mem::size_of::<Nv12Instance>() as u64,
641 step_mode: wgpu::VertexStepMode::Instance,
642 attributes: &[
643 wgpu::VertexAttribute {
644 shader_location: 0,
645 offset: 0,
646 format: wgpu::VertexFormat::Float32x4,
647 },
648 wgpu::VertexAttribute {
649 shader_location: 1,
650 offset: 16,
651 format: wgpu::VertexFormat::Float32x4,
652 },
653 wgpu::VertexAttribute {
654 shader_location: 2,
655 offset: 32,
656 format: wgpu::VertexFormat::Float32x4,
657 },
658 wgpu::VertexAttribute {
659 shader_location: 3,
660 offset: 48,
661 format: wgpu::VertexFormat::Float32,
662 },
663 wgpu::VertexAttribute {
664 shader_location: 4,
665 offset: 52,
666 format: wgpu::VertexFormat::Float32x2,
667 },
668 ],
669 }],
670 compilation_options: wgpu::PipelineCompilationOptions::default(),
671 },
672 fragment: Some(wgpu::FragmentState {
673 module: &image_nv12_shader,
674 entry_point: Some("fs_main"),
675 targets: &[Some(wgpu::ColorTargetState {
676 format,
677 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
678 write_mask: wgpu::ColorWrites::ALL,
679 })],
680 compilation_options: wgpu::PipelineCompilationOptions::default(),
681 }),
682 primitive: wgpu::PrimitiveState::default(),
683 depth_stencil: Some(stencil_for_content.clone()),
684 multisample: msaa_state,
685 multiview_mask: None,
686 cache: None,
687 });
688
689 let clip_shader_a2c = device.create_shader_module(wgpu::ShaderModuleDescriptor {
691 label: Some("clip_round_rect_a2c.wgsl"),
692 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
693 "shaders/clip_round_rect_a2c.wgsl"
694 ))),
695 });
696 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
697 label: Some("clip_round_rect_bin.wgsl"),
698 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
699 "shaders/clip_round_rect_bin.wgsl"
700 ))),
701 });
702 let clip_a2c = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
703 label: Some("clip pipeline (a2c)"),
704 layout: Some(clip_pipeline_layout),
705 vertex: wgpu::VertexState {
706 module: &clip_shader_a2c,
707 entry_point: Some("vs_main"),
708 buffers: &[clip_vertex_layout.clone()],
709 compilation_options: wgpu::PipelineCompilationOptions::default(),
710 },
711 fragment: Some(wgpu::FragmentState {
712 module: &clip_shader_a2c,
713 entry_point: Some("fs_main"),
714 targets: &[Some(clip_color_target.clone())],
715 compilation_options: wgpu::PipelineCompilationOptions::default(),
716 }),
717 primitive: wgpu::PrimitiveState::default(),
718 depth_stencil: Some(stencil_for_clip_inc.clone()),
719 multisample: wgpu::MultisampleState {
720 count: sample_count,
721 mask: !0,
722 alpha_to_coverage_enabled: sample_count > 1,
723 },
724 multiview_mask: None,
725 cache: None,
726 });
727 let clip_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
728 label: Some("clip pipeline (bin)"),
729 layout: Some(clip_pipeline_layout),
730 vertex: wgpu::VertexState {
731 module: &clip_shader_bin,
732 entry_point: Some("vs_main"),
733 buffers: &[clip_vertex_layout.clone()],
734 compilation_options: wgpu::PipelineCompilationOptions::default(),
735 },
736 fragment: Some(wgpu::FragmentState {
737 module: &clip_shader_bin,
738 entry_point: Some("fs_main"),
739 targets: &[Some(clip_color_target.clone())],
740 compilation_options: wgpu::PipelineCompilationOptions::default(),
741 }),
742 primitive: wgpu::PrimitiveState::default(),
743 depth_stencil: Some(stencil_for_clip_inc.clone()),
744 multisample: wgpu::MultisampleState {
745 count: sample_count,
746 mask: !0,
747 alpha_to_coverage_enabled: false,
748 },
749 multiview_mask: None,
750 cache: None,
751 });
752
753 let slug = Some(slug::create_pipeline(
754 device,
755 format,
756 sample_count,
757 stencil_for_content,
758 ));
759
760 Self {
761 rects,
762 borders,
763 ellipses,
764 ellipse_borders,
765 arcs,
766 text_mask,
767 text_color,
768 image_rgba,
769 image_nv12,
770 blur,
771 clip_a2c,
772 clip_bin,
773 slug,
774 }
775 }
776}
777
778struct Pass {
780 target: PassTarget,
781 initial_scissor: (u32, u32, u32, u32),
783 clear_color: Option<[f32; 4]>,
786 cmds: Vec<Cmd>,
787}
788
789#[allow(non_snake_case)]
790enum Cmd {
791 ClipPush {
792 off: u64,
793 cnt: u32,
794 scissor: (u32, u32, u32, u32),
795 },
796 ClipPop {
797 scissor: (u32, u32, u32, u32),
798 },
799 Rect {
800 off: u64,
801 cnt: u32,
802 },
803 Border {
804 off: u64,
805 cnt: u32,
806 },
807 Ellipse {
808 off: u64,
809 cnt: u32,
810 },
811 EllipseBorder {
812 off: u64,
813 cnt: u32,
814 },
815 Arc {
816 off: u64,
817 cnt: u32,
818 },
819 GlyphsMask {
820 off: u64,
821 cnt: u32,
822 },
823 GlyphsColor {
824 off: u64,
825 cnt: u32,
826 },
827 GlyphsVector {
828 off: u64,
829 cnt: u32,
830 },
831 ImageRgba {
832 off: u64,
833 cnt: u32,
834 handle: u64,
835 },
836 ImageNv12 {
837 off: u64,
838 cnt: u32,
839 handle: u64,
840 },
841 PushTransform(Transform),
842 PopTransform,
843 CompositeLayer {
847 off: u64,
848 cnt: u32,
849 layer_id: u32,
850 alpha: f32,
851 },
852 CompositeShadow {
856 off: u64,
857 cnt: u32,
858 layer_id: u32,
859 },
860}
861
862enum ImageTex {
863 Rgba {
864 tex: wgpu::Texture,
865 view: wgpu::TextureView,
866 bind: wgpu::BindGroup,
867 w: u32,
868 h: u32,
869 format: wgpu::TextureFormat,
870 last_used_frame: u64,
871 bytes: u64,
872 },
873 Nv12 {
874 tex_y: wgpu::Texture,
875 view_y: wgpu::TextureView,
876 tex_uv: wgpu::Texture,
877 view_uv: wgpu::TextureView,
878 bind: wgpu::BindGroup,
879 w: u32,
880 h: u32,
881 full_range: bool,
882 last_used_frame: u64,
883 bytes: u64,
884 },
885}
886
887struct AtlasA8 {
888 tex: wgpu::Texture,
889 view: wgpu::TextureView,
890 sampler: wgpu::Sampler,
891 size: u32,
892 next_x: u32,
893 next_y: u32,
894 row_h: u32,
895 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
896}
897
898struct AtlasRGBA {
899 tex: wgpu::Texture,
900 view: wgpu::TextureView,
901 sampler: wgpu::Sampler,
902 size: u32,
903 next_x: u32,
904 next_y: u32,
905 row_h: u32,
906 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
907}
908
909#[derive(Clone, Copy)]
910struct GlyphInfo {
911 u0: f32,
912 v0: f32,
913 u1: f32,
914 v1: f32,
915 w: f32,
916 h: f32,
917 bearing_x: f32,
918 bearing_y: f32,
919 advance: f32,
920}
921
922#[repr(C)]
923#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
924struct RectInstance {
925 xywh: [f32; 4],
926 radii: [f32; 4],
927 brush_type: u32,
928 _pad: [f32; 3],
929 color0: [f32; 4],
930 color1: [f32; 4],
931 grad_start: [f32; 2],
932 grad_end: [f32; 2],
933 sin_cos: [f32; 2],
934}
935
936#[repr(C)]
937#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
938struct BorderInstance {
939 xywh: [f32; 4],
940 radii: [f32; 4],
941 stroke: f32,
942 color: [f32; 4],
943 sin_cos: [f32; 2],
944}
945
946#[repr(C)]
947#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
948struct EllipseInstance {
949 xywh: [f32; 4],
950 color: [f32; 4],
951 sin_cos: [f32; 2],
952}
953
954#[repr(C)]
955#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
956struct EllipseBorderInstance {
957 xywh: [f32; 4],
958 stroke: f32,
959 pad: f32,
960 color: [f32; 4],
961 sin_cos: [f32; 2],
962}
963
964#[repr(C)]
965#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
966struct ArcInstance {
967 xywh: [f32; 4],
968 start_angle: f32,
969 sweep_angle: f32,
970 stroke: f32,
971 pad: f32,
972 color: [f32; 4],
973 sin_cos: [f32; 2],
974 cap: f32, }
976
977#[repr(C)]
978#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
979struct GlyphInstance {
980 xywh: [f32; 4],
981 uv: [f32; 4],
982 color: [f32; 4],
983 sin_cos: [f32; 2],
984}
985
986#[repr(C)]
987#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
988struct BlurInstance {
989 xywh: [f32; 4],
990 uv: [f32; 4],
991 color: [f32; 4],
992 blur_uv: [f32; 2],
993 sin_cos: [f32; 2],
994}
995
996#[repr(C)]
997#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
998struct Nv12Instance {
999 xywh: [f32; 4],
1000 uv: [f32; 4],
1001 color: [f32; 4], full_range: f32,
1003 sin_cos: [f32; 2],
1004 _pad: [f32; 1],
1005}
1006
1007#[repr(C)]
1008#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1009struct ClipInstance {
1010 xywh: [f32; 4],
1011 radii: [f32; 4],
1012 sin_cos: [f32; 2],
1013}
1014
1015fn swash_to_a8_coverage(content: cosmic_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
1016 match content {
1017 cosmic_text::SwashContent::Mask => Some(data.to_vec()),
1018 cosmic_text::SwashContent::SubpixelMask => {
1019 let mut out = Vec::with_capacity(data.len() / 4);
1020 for px in data.chunks_exact(4) {
1021 let r = px[0];
1022 let g = px[1];
1023 let b = px[2];
1024 out.push(r.max(g).max(b));
1025 }
1026 Some(out)
1027 }
1028 cosmic_text::SwashContent::Color => None,
1029 }
1030}
1031
1032impl WgpuBackend {
1033 pub async fn new_async(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1034 let instance: Instance;
1035
1036 if cfg!(target_arch = "wasm32") {
1037 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
1038 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
1039 instance = wgpu::util::new_instance_with_webgpu_detection(desc).await;
1040 } else {
1041 instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
1042 };
1043
1044 let surface = instance.create_surface(window.clone())?;
1045
1046 let adapter = instance
1047 .request_adapter(&wgpu::RequestAdapterOptions {
1048 power_preference: wgpu::PowerPreference::HighPerformance,
1049 compatible_surface: Some(&surface),
1050 force_fallback_adapter: false,
1051 })
1052 .await
1053 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
1054
1055 let limits = adapter.limits();
1056
1057 let (device, queue) = adapter
1058 .request_device(&wgpu::DeviceDescriptor {
1059 label: Some("repose-rs device"),
1060 required_features: wgpu::Features::empty(),
1061 required_limits: limits,
1062 experimental_features: wgpu::ExperimentalFeatures::disabled(),
1063 memory_hints: wgpu::MemoryHints::default(),
1064 trace: wgpu::Trace::Off,
1065 })
1066 .await
1067 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
1068
1069 let size = window.inner_size();
1070
1071 let caps = surface.get_capabilities(&adapter);
1072 let format = caps
1073 .formats
1074 .iter()
1075 .copied()
1076 .find(|f| f.is_srgb())
1077 .unwrap_or(caps.formats[0]);
1078 let present_mode = caps
1079 .present_modes
1080 .iter()
1081 .copied()
1082 .find(|m| *m == wgpu::PresentMode::Mailbox || *m == wgpu::PresentMode::Immediate)
1083 .unwrap_or(wgpu::PresentMode::Fifo);
1084 let alpha_mode = caps.alpha_modes[0];
1085
1086 let config = wgpu::SurfaceConfiguration {
1087 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1088 format,
1089 width: size.width.max(1),
1090 height: size.height.max(1),
1091 present_mode,
1092 alpha_mode,
1093 view_formats: vec![],
1094 desired_maximum_frame_latency: 2,
1095 };
1096 surface.configure(&device, &config);
1097
1098 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1099 label: Some("globals layout"),
1100 entries: &[wgpu::BindGroupLayoutEntry {
1101 binding: 0,
1102 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1103 ty: wgpu::BindingType::Buffer {
1104 ty: wgpu::BufferBindingType::Uniform,
1105 has_dynamic_offset: false,
1106 min_binding_size: None,
1107 },
1108 count: None,
1109 }],
1110 });
1111
1112 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
1113 label: Some("globals buf"),
1114 size: std::mem::size_of::<Globals>() as u64,
1115 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1116 mapped_at_creation: false,
1117 });
1118
1119 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1120 label: Some("globals bind"),
1121 layout: &globals_layout,
1122 entries: &[wgpu::BindGroupEntry {
1123 binding: 0,
1124 resource: globals_buf.as_entire_binding(),
1125 }],
1126 });
1127
1128 let fmt_features = adapter.get_texture_format_features(format);
1130 let msaa_samples = if fmt_features.flags.sample_count_supported(4)
1131 && fmt_features
1132 .flags
1133 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
1134 {
1135 4
1136 } else {
1137 1
1138 };
1139
1140 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
1141
1142 let stencil_for_content = wgpu::DepthStencilState {
1143 format: ds_format,
1144 depth_write_enabled: Some(false),
1145 depth_compare: Some(wgpu::CompareFunction::Always),
1146 stencil: wgpu::StencilState {
1147 front: wgpu::StencilFaceState {
1148 compare: wgpu::CompareFunction::LessEqual,
1149 fail_op: wgpu::StencilOperation::Keep,
1150 depth_fail_op: wgpu::StencilOperation::Keep,
1151 pass_op: wgpu::StencilOperation::Keep,
1152 },
1153 back: wgpu::StencilFaceState {
1154 compare: wgpu::CompareFunction::LessEqual,
1155 fail_op: wgpu::StencilOperation::Keep,
1156 depth_fail_op: wgpu::StencilOperation::Keep,
1157 pass_op: wgpu::StencilOperation::Keep,
1158 },
1159 read_mask: 0xFF,
1160 write_mask: 0x00,
1161 },
1162 bias: wgpu::DepthBiasState::default(),
1163 };
1164
1165 let stencil_for_clip_inc = wgpu::DepthStencilState {
1166 format: ds_format,
1167 depth_write_enabled: Some(false),
1168 depth_compare: Some(wgpu::CompareFunction::Always),
1169 stencil: wgpu::StencilState {
1170 front: wgpu::StencilFaceState {
1171 compare: wgpu::CompareFunction::Equal,
1172 fail_op: wgpu::StencilOperation::Keep,
1173 depth_fail_op: wgpu::StencilOperation::Keep,
1174 pass_op: wgpu::StencilOperation::IncrementClamp,
1175 },
1176 back: wgpu::StencilFaceState {
1177 compare: wgpu::CompareFunction::Equal,
1178 fail_op: wgpu::StencilOperation::Keep,
1179 depth_fail_op: wgpu::StencilOperation::Keep,
1180 pass_op: wgpu::StencilOperation::IncrementClamp,
1181 },
1182 read_mask: 0xFF,
1183 write_mask: 0xFF,
1184 },
1185 bias: wgpu::DepthBiasState::default(),
1186 };
1187
1188 let _multisample_state = wgpu::MultisampleState {
1189 count: msaa_samples,
1190 mask: !0,
1191 alpha_to_coverage_enabled: false,
1192 };
1193
1194 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1198 label: Some("image/text sampler"),
1199 address_mode_u: wgpu::AddressMode::ClampToEdge,
1200 address_mode_v: wgpu::AddressMode::ClampToEdge,
1201 mag_filter: wgpu::FilterMode::Linear,
1202 min_filter: wgpu::FilterMode::Linear,
1203 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1204 ..Default::default()
1205 });
1206
1207 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1209 label: Some("text/rgba bind layout"),
1210 entries: &[
1211 wgpu::BindGroupLayoutEntry {
1212 binding: 0,
1213 visibility: wgpu::ShaderStages::FRAGMENT,
1214 ty: wgpu::BindingType::Texture {
1215 multisampled: false,
1216 view_dimension: wgpu::TextureViewDimension::D2,
1217 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1218 },
1219 count: None,
1220 },
1221 wgpu::BindGroupLayoutEntry {
1222 binding: 1,
1223 visibility: wgpu::ShaderStages::FRAGMENT,
1224 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1225 count: None,
1226 },
1227 ],
1228 });
1229 let image_bind_layout_rgba = text_bind_layout.clone();
1231
1232 let image_bind_layout_nv12 =
1234 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1235 label: Some("image bind layout nv12"),
1236 entries: &[
1237 wgpu::BindGroupLayoutEntry {
1239 binding: 0,
1240 visibility: wgpu::ShaderStages::FRAGMENT,
1241 ty: wgpu::BindingType::Texture {
1242 multisampled: false,
1243 view_dimension: wgpu::TextureViewDimension::D2,
1244 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1245 },
1246 count: None,
1247 },
1248 wgpu::BindGroupLayoutEntry {
1250 binding: 1,
1251 visibility: wgpu::ShaderStages::FRAGMENT,
1252 ty: wgpu::BindingType::Texture {
1253 multisampled: false,
1254 view_dimension: wgpu::TextureViewDimension::D2,
1255 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1256 },
1257 count: None,
1258 },
1259 wgpu::BindGroupLayoutEntry {
1261 binding: 2,
1262 visibility: wgpu::ShaderStages::FRAGMENT,
1263 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1264 count: None,
1265 },
1266 ],
1267 });
1268
1269 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1271 label: Some("clip pipeline layout"),
1272 bind_group_layouts: &[Some(&globals_layout)],
1273 immediate_size: 0,
1274 });
1275 let clip_vertex_layout = wgpu::VertexBufferLayout {
1276 array_stride: std::mem::size_of::<ClipInstance>() as u64,
1277 step_mode: wgpu::VertexStepMode::Instance,
1278 attributes: &[
1279 wgpu::VertexAttribute {
1280 shader_location: 0,
1281 offset: 0,
1282 format: wgpu::VertexFormat::Float32x4,
1283 },
1284 wgpu::VertexAttribute {
1285 shader_location: 1,
1286 offset: 16,
1287 format: wgpu::VertexFormat::Float32x4,
1288 },
1289 wgpu::VertexAttribute {
1290 shader_location: 2,
1291 offset: 32,
1292 format: wgpu::VertexFormat::Float32x2,
1293 },
1294 ],
1295 };
1296 let clip_color_target = wgpu::ColorTargetState {
1297 format: config.format,
1298 blend: None,
1299 write_mask: wgpu::ColorWrites::empty(),
1300 };
1301
1302 let surface_pipes = Pipelines::create(
1305 &device,
1306 config.format,
1307 msaa_samples,
1308 &globals_layout,
1309 &text_bind_layout,
1310 &image_bind_layout_nv12,
1311 &clip_pipeline_layout,
1312 &stencil_for_content,
1313 &stencil_for_clip_inc,
1314 &clip_color_target,
1315 &clip_vertex_layout,
1316 );
1317 let layer_pipes = Pipelines::create(
1318 &device,
1319 config.format,
1320 1,
1321 &globals_layout,
1322 &text_bind_layout,
1323 &image_bind_layout_nv12,
1324 &clip_pipeline_layout,
1325 &stencil_for_content,
1326 &stencil_for_clip_inc,
1327 &clip_color_target,
1328 &clip_vertex_layout,
1329 );
1330
1331 let slug_enabled = true;
1333
1334 let blur_ring = UploadRing::new(&device, "blur ring", 1024 * 1024);
1336
1337 let atlas_mask = Self::init_atlas_mask(&device)?;
1339 let atlas_color = Self::init_atlas_color(&device)?;
1340
1341 let ring_rect = UploadRing::new(&device, "ring rect", 1 << 20);
1343 let ring_border = UploadRing::new(&device, "ring border", 1 << 20);
1344 let ring_ellipse = UploadRing::new(&device, "ring ellipse", 1 << 20);
1345 let ring_ellipse_border = UploadRing::new(&device, "ring ellipse border", 1 << 20);
1346 let ring_arc = UploadRing::new(&device, "ring arc", 1 << 20);
1347 let ring_glyph_mask = UploadRing::new(&device, "ring glyph mask", 1 << 20);
1348 let ring_glyph_color = UploadRing::new(&device, "ring glyph color", 1 << 20);
1349 let ring_slug = UploadRing::new(&device, "ring slug", 1 << 22);
1350 let ring_clip = UploadRing::new(&device, "ring clip", 1 << 16);
1351 let ring_nv12 = UploadRing::new(&device, "ring nv12", 1 << 20);
1352
1353 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
1355 label: Some("temp ds"),
1356 size: wgpu::Extent3d {
1357 width: 1,
1358 height: 1,
1359 depth_or_array_layers: 1,
1360 },
1361 mip_level_count: 1,
1362 sample_count: 1,
1363 dimension: wgpu::TextureDimension::D2,
1364 format: wgpu::TextureFormat::Depth24PlusStencil8,
1365 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1366 view_formats: &[],
1367 });
1368 let depth_stencil_view =
1369 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1370
1371 let mut backend = Self {
1372 surface,
1373 device,
1374 queue,
1375 config,
1376
1377 surface_pipes,
1378 layer_pipes,
1379
1380 rects: InstancedPipe::new(ring_rect),
1381 borders: InstancedPipe::new(ring_border),
1382 ellipses: InstancedPipe::new(ring_ellipse),
1383 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
1384 arcs: InstancedPipe::new(ring_arc),
1385 glyph_mask: InstancedPipe::new(ring_glyph_mask),
1386 glyph_color: InstancedPipe::new(ring_glyph_color),
1387
1388 text_bind_layout,
1389
1390 image_bind_layout_rgba,
1391 image_bind_layout_nv12,
1392 image_sampler,
1393
1394 blur_ring,
1395
1396 slug_enabled,
1397 slug_ring: ring_slug,
1398 slug_cache: slug::GlyphSlugCache::new(),
1399
1400 clip_ring: ring_clip,
1401
1402 nv12: InstancedPipe::new(ring_nv12),
1403
1404 msaa_samples,
1405 depth_stencil_tex,
1406 depth_stencil_view,
1407 msaa_tex: None,
1408 msaa_view: None,
1409 globals_bind,
1410 globals_buf,
1411 globals_layout,
1412
1413 atlas_mask,
1414 atlas_color,
1415
1416 next_image_handle: 1,
1417 images: HashMap::new(),
1418
1419 frame_index: 0,
1420 image_bytes_total: 0,
1421 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
1424 };
1425
1426 backend.recreate_msaa_and_depth_stencil();
1427 Ok(backend)
1428 }
1429
1430 #[cfg(not(target_arch = "wasm32"))]
1431 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1432 pollster::block_on(Self::new_async(window))
1433 }
1434
1435 #[cfg(target_arch = "wasm32")]
1436 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1437 anyhow::bail!("Use WgpuBackend::new_async(window).await on wasm32")
1438 }
1439
1440 pub fn set_image_from_bytes(
1443 &mut self,
1444 handle: u64,
1445 data: &[u8],
1446 srgb: bool,
1447 ) -> anyhow::Result<()> {
1448 let img = image::load_from_memory(data)?;
1449 let rgba = img.to_rgba8();
1450 let (w, h) = rgba.dimensions();
1451 self.set_image_rgba8(handle, w, h, &rgba, srgb)
1452 }
1453
1454 pub fn set_image_rgba8(
1455 &mut self,
1456 handle: u64,
1457 w: u32,
1458 h: u32,
1459 rgba: &[u8],
1460 srgb: bool,
1461 ) -> anyhow::Result<()> {
1462 let expected = (w as usize) * (h as usize) * 4;
1463 if rgba.len() < expected {
1464 return Err(anyhow::anyhow!(
1465 "RGBA buffer too small: {} < {}",
1466 rgba.len(),
1467 expected
1468 ));
1469 }
1470
1471 let format = if srgb {
1472 wgpu::TextureFormat::Rgba8UnormSrgb
1473 } else {
1474 wgpu::TextureFormat::Rgba8Unorm
1475 };
1476
1477 let needs_recreate = match self.images.get(&handle) {
1478 Some(ImageTex::Rgba {
1479 w: cw,
1480 h: ch,
1481 format: cf,
1482 ..
1483 }) => *cw != w || *ch != h || *cf != format,
1484 _ => true,
1485 };
1486
1487 if needs_recreate {
1488 self.remove_image(handle);
1490
1491 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1492 label: Some("user image rgba"),
1493 size: wgpu::Extent3d {
1494 width: w,
1495 height: h,
1496 depth_or_array_layers: 1,
1497 },
1498 mip_level_count: 1,
1499 sample_count: 1,
1500 dimension: wgpu::TextureDimension::D2,
1501 format,
1502 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1503 view_formats: &[],
1504 });
1505 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1506
1507 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1508 label: Some("image bind rgba"),
1509 layout: &self.image_bind_layout_rgba,
1510 entries: &[
1511 wgpu::BindGroupEntry {
1512 binding: 0,
1513 resource: wgpu::BindingResource::TextureView(&view),
1514 },
1515 wgpu::BindGroupEntry {
1516 binding: 1,
1517 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1518 },
1519 ],
1520 });
1521
1522 let bytes = (w as u64) * (h as u64) * 4;
1523 self.image_bytes_total += bytes;
1524
1525 self.images.insert(
1526 handle,
1527 ImageTex::Rgba {
1528 tex,
1529 view,
1530 bind,
1531 w,
1532 h,
1533 format,
1534 last_used_frame: self.frame_index,
1535 bytes,
1536 },
1537 );
1538 }
1539
1540 let tex = match self.images.get(&handle) {
1541 Some(ImageTex::Rgba { tex, .. }) => tex,
1542 _ => unreachable!(),
1543 };
1544
1545 self.queue.write_texture(
1546 wgpu::TexelCopyTextureInfo {
1547 texture: tex,
1548 mip_level: 0,
1549 origin: wgpu::Origin3d::ZERO,
1550 aspect: wgpu::TextureAspect::All,
1551 },
1552 &rgba[..expected],
1553 wgpu::TexelCopyBufferLayout {
1554 offset: 0,
1555 bytes_per_row: Some(4 * w),
1556 rows_per_image: Some(h),
1557 },
1558 wgpu::Extent3d {
1559 width: w,
1560 height: h,
1561 depth_or_array_layers: 1,
1562 },
1563 );
1564
1565 self.evict_budget_excess();
1567
1568 Ok(())
1569 }
1570
1571 pub fn set_image_nv12(
1572 &mut self,
1573 handle: u64,
1574 w: u32,
1575 h: u32,
1576 y: &[u8],
1577 uv: &[u8],
1578 full_range: bool,
1579 ) -> anyhow::Result<()> {
1580 let y_expected = (w as usize) * (h as usize);
1581 let uv_w = (w / 2).max(1);
1582 let uv_h = (h / 2).max(1);
1583 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
1584
1585 if y.len() < y_expected {
1586 return Err(anyhow::anyhow!("Y plane too small"));
1587 }
1588 if uv.len() < uv_expected {
1589 return Err(anyhow::anyhow!("UV plane too small"));
1590 }
1591
1592 let needs_recreate = match self.images.get(&handle) {
1593 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
1594 _ => true,
1595 };
1596
1597 if needs_recreate {
1598 self.remove_image(handle);
1599
1600 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
1601 label: Some("nv12 Y"),
1602 size: wgpu::Extent3d {
1603 width: w,
1604 height: h,
1605 depth_or_array_layers: 1,
1606 },
1607 mip_level_count: 1,
1608 sample_count: 1,
1609 dimension: wgpu::TextureDimension::D2,
1610 format: wgpu::TextureFormat::R8Unorm,
1611 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1612 view_formats: &[],
1613 });
1614 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
1615
1616 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
1617 label: Some("nv12 UV"),
1618 size: wgpu::Extent3d {
1619 width: uv_w,
1620 height: uv_h,
1621 depth_or_array_layers: 1,
1622 },
1623 mip_level_count: 1,
1624 sample_count: 1,
1625 dimension: wgpu::TextureDimension::D2,
1626 format: wgpu::TextureFormat::Rg8Unorm,
1627 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1628 view_formats: &[],
1629 });
1630 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
1631
1632 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1633 label: Some("nv12 bind"),
1634 layout: &self.image_bind_layout_nv12,
1635 entries: &[
1636 wgpu::BindGroupEntry {
1637 binding: 0,
1638 resource: wgpu::BindingResource::TextureView(&view_y),
1639 },
1640 wgpu::BindGroupEntry {
1641 binding: 1,
1642 resource: wgpu::BindingResource::TextureView(&view_uv),
1643 },
1644 wgpu::BindGroupEntry {
1645 binding: 2,
1646 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1647 },
1648 ],
1649 });
1650
1651 let bytes = (w as u64) * (h as u64) + (uv_w as u64) * (uv_h as u64) * 2;
1652 self.image_bytes_total += bytes;
1653
1654 self.images.insert(
1655 handle,
1656 ImageTex::Nv12 {
1657 tex_y,
1658 view_y,
1659 tex_uv,
1660 view_uv,
1661 bind,
1662 w,
1663 h,
1664 full_range,
1665 last_used_frame: self.frame_index,
1666 bytes,
1667 },
1668 );
1669 }
1670
1671 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
1672 Some(ImageTex::Nv12 {
1673 tex_y,
1674 tex_uv,
1675 bind,
1676 ..
1677 }) => (tex_y, tex_uv, bind),
1678 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
1679 };
1680
1681 self.queue.write_texture(
1682 wgpu::TexelCopyTextureInfo {
1683 texture: tex_y,
1684 mip_level: 0,
1685 origin: wgpu::Origin3d::ZERO,
1686 aspect: wgpu::TextureAspect::All,
1687 },
1688 &y[..y_expected],
1689 wgpu::TexelCopyBufferLayout {
1690 offset: 0,
1691 bytes_per_row: Some(w),
1692 rows_per_image: Some(h),
1693 },
1694 wgpu::Extent3d {
1695 width: w,
1696 height: h,
1697 depth_or_array_layers: 1,
1698 },
1699 );
1700
1701 self.queue.write_texture(
1702 wgpu::TexelCopyTextureInfo {
1703 texture: tex_uv,
1704 mip_level: 0,
1705 origin: wgpu::Origin3d::ZERO,
1706 aspect: wgpu::TextureAspect::All,
1707 },
1708 &uv[..uv_expected],
1709 wgpu::TexelCopyBufferLayout {
1710 offset: 0,
1711 bytes_per_row: Some(2 * uv_w),
1712 rows_per_image: Some(uv_h),
1713 },
1714 wgpu::Extent3d {
1715 width: uv_w,
1716 height: uv_h,
1717 depth_or_array_layers: 1,
1718 },
1719 );
1720
1721 self.evict_budget_excess();
1722 Ok(())
1723 }
1724
1725 pub fn remove_image(&mut self, handle: u64) {
1726 if let Some(img) = self.images.remove(&handle) {
1727 let b = match img {
1728 ImageTex::Rgba { bytes, .. } => bytes,
1729 ImageTex::Nv12 { bytes, .. } => bytes,
1730 };
1731 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
1732 }
1733 }
1734
1735 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
1737 let handle = self.next_image_handle;
1738 self.next_image_handle += 1;
1739 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
1740 log::error!("Failed to register image: {e}");
1741 }
1742 handle
1743 }
1744
1745 fn evict_unused_images(&mut self) {
1746 let now = self.frame_index;
1747 let evict_after = self.image_evict_after_frames;
1748
1749 let mut to_remove = Vec::new();
1751 for (h, t) in self.images.iter() {
1752 let last = match t {
1753 ImageTex::Rgba {
1754 last_used_frame, ..
1755 } => *last_used_frame,
1756 ImageTex::Nv12 {
1757 last_used_frame, ..
1758 } => *last_used_frame,
1759 };
1760 if now.saturating_sub(last) > evict_after {
1761 to_remove.push(*h);
1762 }
1763 }
1764 for h in to_remove {
1765 self.remove_image(h);
1766 }
1767
1768 self.evict_budget_excess();
1769 }
1770
1771 fn evict_budget_excess(&mut self) {
1772 if self.image_bytes_total <= self.image_budget_bytes {
1773 return;
1774 }
1775 let mut candidates: Vec<(u64, u64, u64)> = self
1777 .images
1778 .iter()
1779 .map(|(h, t)| {
1780 let (last, bytes) = match t {
1781 ImageTex::Rgba {
1782 last_used_frame,
1783 bytes,
1784 ..
1785 } => (*last_used_frame, *bytes),
1786 ImageTex::Nv12 {
1787 last_used_frame,
1788 bytes,
1789 ..
1790 } => (*last_used_frame, *bytes),
1791 };
1792 (*h, last, bytes)
1793 })
1794 .collect();
1795
1796 candidates.sort_by_key(|k| k.1);
1798
1799 let now = self.frame_index;
1800 for (h, last, _bytes) in candidates {
1801 if self.image_bytes_total <= self.image_budget_bytes {
1802 break;
1803 }
1804 if last == now {
1806 continue;
1807 }
1808 self.remove_image(h);
1809 }
1810 }
1811
1812 fn recreate_msaa_and_depth_stencil(&mut self) {
1813 if self.msaa_samples > 1 {
1814 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1815 label: Some("msaa color"),
1816 size: wgpu::Extent3d {
1817 width: self.config.width.max(1),
1818 height: self.config.height.max(1),
1819 depth_or_array_layers: 1,
1820 },
1821 mip_level_count: 1,
1822 sample_count: self.msaa_samples,
1823 dimension: wgpu::TextureDimension::D2,
1824 format: self.config.format,
1825 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1826 view_formats: &[],
1827 });
1828 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1829 self.msaa_tex = Some(tex);
1830 self.msaa_view = Some(view);
1831 } else {
1832 self.msaa_tex = None;
1833 self.msaa_view = None;
1834 }
1835
1836 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1837 label: Some("depth-stencil (stencil clips)"),
1838 size: wgpu::Extent3d {
1839 width: self.config.width.max(1),
1840 height: self.config.height.max(1),
1841 depth_or_array_layers: 1,
1842 },
1843 mip_level_count: 1,
1844 sample_count: self.msaa_samples,
1845 dimension: wgpu::TextureDimension::D2,
1846 format: wgpu::TextureFormat::Depth24PlusStencil8,
1847 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1848 view_formats: &[],
1849 });
1850 self.depth_stencil_view = self
1851 .depth_stencil_tex
1852 .create_view(&wgpu::TextureViewDescriptor::default());
1853 }
1854
1855 fn init_atlas_mask(device: &wgpu::Device) -> anyhow::Result<AtlasA8> {
1856 let size = 1024u32;
1857 let tex = device.create_texture(&wgpu::TextureDescriptor {
1858 label: Some("glyph atlas A8"),
1859 size: wgpu::Extent3d {
1860 width: size,
1861 height: size,
1862 depth_or_array_layers: 1,
1863 },
1864 mip_level_count: 1,
1865 sample_count: 1,
1866 dimension: wgpu::TextureDimension::D2,
1867 format: wgpu::TextureFormat::R8Unorm,
1868 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1869 view_formats: &[],
1870 });
1871 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1872 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1873 label: Some("glyph atlas sampler A8"),
1874 address_mode_u: wgpu::AddressMode::ClampToEdge,
1875 address_mode_v: wgpu::AddressMode::ClampToEdge,
1876 address_mode_w: wgpu::AddressMode::ClampToEdge,
1877 mag_filter: wgpu::FilterMode::Linear,
1878 min_filter: wgpu::FilterMode::Linear,
1879 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1880 ..Default::default()
1881 });
1882
1883 Ok(AtlasA8 {
1884 tex,
1885 view,
1886 sampler,
1887 size,
1888 next_x: 1,
1889 next_y: 1,
1890 row_h: 0,
1891 map: HashMap::new(),
1892 })
1893 }
1894
1895 fn init_atlas_color(device: &wgpu::Device) -> anyhow::Result<AtlasRGBA> {
1896 let size = 1024u32;
1897 let tex = device.create_texture(&wgpu::TextureDescriptor {
1898 label: Some("glyph atlas RGBA"),
1899 size: wgpu::Extent3d {
1900 width: size,
1901 height: size,
1902 depth_or_array_layers: 1,
1903 },
1904 mip_level_count: 1,
1905 sample_count: 1,
1906 dimension: wgpu::TextureDimension::D2,
1907 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1908 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1909 view_formats: &[],
1910 });
1911 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1912 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1913 label: Some("glyph atlas sampler RGBA"),
1914 address_mode_u: wgpu::AddressMode::ClampToEdge,
1915 address_mode_v: wgpu::AddressMode::ClampToEdge,
1916 address_mode_w: wgpu::AddressMode::ClampToEdge,
1917 mag_filter: wgpu::FilterMode::Linear,
1918 min_filter: wgpu::FilterMode::Linear,
1919 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1920 ..Default::default()
1921 });
1922 Ok(AtlasRGBA {
1923 tex,
1924 view,
1925 sampler,
1926 size,
1927 next_x: 1,
1928 next_y: 1,
1929 row_h: 0,
1930 map: HashMap::new(),
1931 })
1932 }
1933
1934 fn get_or_create_layer(
1935 &mut self,
1936 layer_id: u32,
1937 width: u32,
1938 height: u32,
1939 rect: repose_core::Rect,
1940 ) {
1941 let needs_alloc = match self.layer_pool.get(&layer_id) {
1942 Some(lt) => lt.width != width || lt.height != height,
1943 None => true,
1944 };
1945 if !needs_alloc {
1946 return;
1947 }
1948 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1949 label: Some("graphics layer"),
1950 size: wgpu::Extent3d {
1951 width: width.max(1),
1952 height: height.max(1),
1953 depth_or_array_layers: 1,
1954 },
1955 mip_level_count: 1,
1956 sample_count: 1,
1957 dimension: wgpu::TextureDimension::D2,
1958 format: self.config.format,
1959 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1960 view_formats: &[],
1961 });
1962 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1963 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1964 label: Some("layer bind"),
1965 layout: &self.image_bind_layout_rgba,
1966 entries: &[
1967 wgpu::BindGroupEntry {
1968 binding: 0,
1969 resource: wgpu::BindingResource::TextureView(&view),
1970 },
1971 wgpu::BindGroupEntry {
1972 binding: 1,
1973 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1974 },
1975 ],
1976 });
1977 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1978 label: Some("graphics layer depth-stencil"),
1979 size: wgpu::Extent3d {
1980 width: width.max(1),
1981 height: height.max(1),
1982 depth_or_array_layers: 1,
1983 },
1984 mip_level_count: 1,
1985 sample_count: 1,
1986 dimension: wgpu::TextureDimension::D2,
1987 format: wgpu::TextureFormat::Depth24PlusStencil8,
1988 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1989 view_formats: &[],
1990 });
1991 let depth_stencil_view =
1992 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1993 self.layer_pool.insert(
1994 layer_id,
1995 LayerTarget {
1996 texture: tex,
1997 view,
1998 bind,
1999 depth_stencil_tex,
2000 depth_stencil_view,
2001 width,
2002 height,
2003 rect_px: (rect.x, rect.y, rect.w, rect.h),
2004 },
2005 );
2006 }
2007
2008 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
2009 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2010 label: Some("atlas bind"),
2011 layout: &self.text_bind_layout,
2012 entries: &[
2013 wgpu::BindGroupEntry {
2014 binding: 0,
2015 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
2016 },
2017 wgpu::BindGroupEntry {
2018 binding: 1,
2019 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
2020 },
2021 ],
2022 })
2023 }
2024
2025 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
2026 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2027 label: Some("atlas bind color"),
2028 layout: &self.text_bind_layout,
2029 entries: &[
2030 wgpu::BindGroupEntry {
2031 binding: 0,
2032 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
2033 },
2034 wgpu::BindGroupEntry {
2035 binding: 1,
2036 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
2037 },
2038 ],
2039 })
2040 }
2041
2042 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
2043 let keyp = (key, px);
2044 if let Some(info) = self.atlas_mask.map.get(&keyp) {
2045 return Some(*info);
2046 }
2047
2048 let gb = repose_text::rasterize(key, px as f32)?;
2049 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
2050 return None;
2051 }
2052
2053 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
2054
2055 let w = gb.w.max(1);
2056 let h = gb.h.max(1);
2057
2058 if !self.alloc_space_mask(w, h) {
2059 self.grow_mask_and_rebuild();
2060 }
2061 if !self.alloc_space_mask(w, h) {
2062 return None;
2063 }
2064 let x = self.atlas_mask.next_x;
2065 let y = self.atlas_mask.next_y;
2066 self.atlas_mask.next_x += w + 1;
2067 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
2068
2069 let layout = wgpu::TexelCopyBufferLayout {
2070 offset: 0,
2071 bytes_per_row: Some(w),
2072 rows_per_image: Some(h),
2073 };
2074 let size = wgpu::Extent3d {
2075 width: w,
2076 height: h,
2077 depth_or_array_layers: 1,
2078 };
2079 self.queue.write_texture(
2080 wgpu::TexelCopyTextureInfoBase {
2081 texture: &self.atlas_mask.tex,
2082 mip_level: 0,
2083 origin: wgpu::Origin3d { x, y, z: 0 },
2084 aspect: wgpu::TextureAspect::All,
2085 },
2086 &coverage,
2087 layout,
2088 size,
2089 );
2090
2091 let info = GlyphInfo {
2092 u0: x as f32 / self.atlas_mask.size as f32,
2093 v0: y as f32 / self.atlas_mask.size as f32,
2094 u1: (x + w) as f32 / self.atlas_mask.size as f32,
2095 v1: (y + h) as f32 / self.atlas_mask.size as f32,
2096 w: w as f32,
2097 h: h as f32,
2098 bearing_x: 0.0,
2099 bearing_y: 0.0,
2100 advance: 0.0,
2101 };
2102 self.atlas_mask.map.insert(keyp, info);
2103 Some(info)
2104 }
2105
2106 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
2107 let keyp = (key, px);
2108 if let Some(info) = self.atlas_color.map.get(&keyp) {
2109 return Some(*info);
2110 }
2111 let gb = repose_text::rasterize(key, px as f32)?;
2112 if !matches!(gb.content, cosmic_text::SwashContent::Color) {
2113 return None;
2114 }
2115 let w = gb.w.max(1);
2116 let h = gb.h.max(1);
2117 if !self.alloc_space_color(w, h) {
2118 self.grow_color_and_rebuild();
2119 }
2120 if !self.alloc_space_color(w, h) {
2121 return None;
2122 }
2123 let x = self.atlas_color.next_x;
2124 let y = self.atlas_color.next_y;
2125 self.atlas_color.next_x += w + 1;
2126 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
2127
2128 let layout = wgpu::TexelCopyBufferLayout {
2129 offset: 0,
2130 bytes_per_row: Some(w * 4),
2131 rows_per_image: Some(h),
2132 };
2133 let size = wgpu::Extent3d {
2134 width: w,
2135 height: h,
2136 depth_or_array_layers: 1,
2137 };
2138 self.queue.write_texture(
2139 wgpu::TexelCopyTextureInfoBase {
2140 texture: &self.atlas_color.tex,
2141 mip_level: 0,
2142 origin: wgpu::Origin3d { x, y, z: 0 },
2143 aspect: wgpu::TextureAspect::All,
2144 },
2145 &gb.data,
2146 layout,
2147 size,
2148 );
2149 let info = GlyphInfo {
2150 u0: x as f32 / self.atlas_color.size as f32,
2151 v0: y as f32 / self.atlas_color.size as f32,
2152 u1: (x + w) as f32 / self.atlas_color.size as f32,
2153 v1: (y + h) as f32 / self.atlas_color.size as f32,
2154 w: w as f32,
2155 h: h as f32,
2156 bearing_x: 0.0,
2157 bearing_y: 0.0,
2158 advance: 0.0,
2159 };
2160 self.atlas_color.map.insert(keyp, info);
2161 Some(info)
2162 }
2163
2164 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
2165 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
2166 self.atlas_mask.next_x = 1;
2167 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
2168 self.atlas_mask.row_h = 0;
2169 }
2170 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
2171 return false;
2172 }
2173 true
2174 }
2175
2176 fn grow_mask_and_rebuild(&mut self) {
2177 let new_size = (self.atlas_mask.size * 2).min(4096);
2178 if new_size == self.atlas_mask.size {
2179 return;
2180 }
2181 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2182 label: Some("glyph atlas A8 (grown)"),
2183 size: wgpu::Extent3d {
2184 width: new_size,
2185 height: new_size,
2186 depth_or_array_layers: 1,
2187 },
2188 mip_level_count: 1,
2189 sample_count: 1,
2190 dimension: wgpu::TextureDimension::D2,
2191 format: wgpu::TextureFormat::R8Unorm,
2192 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2193 view_formats: &[],
2194 });
2195 self.atlas_mask.tex = tex;
2196 self.atlas_mask.view = self
2197 .atlas_mask
2198 .tex
2199 .create_view(&wgpu::TextureViewDescriptor::default());
2200 self.atlas_mask.size = new_size;
2201 self.atlas_mask.next_x = 1;
2202 self.atlas_mask.next_y = 1;
2203 self.atlas_mask.row_h = 0;
2204 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
2205 self.atlas_mask.map.clear();
2206 for (k, px) in keys {
2207 let _ = self.upload_glyph_mask(k, px);
2208 }
2209 }
2210
2211 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
2212 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
2213 self.atlas_color.next_x = 1;
2214 self.atlas_color.next_y += self.atlas_color.row_h + 1;
2215 self.atlas_color.row_h = 0;
2216 }
2217 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
2218 return false;
2219 }
2220 true
2221 }
2222
2223 fn grow_color_and_rebuild(&mut self) {
2224 let new_size = (self.atlas_color.size * 2).min(4096);
2225 if new_size == self.atlas_color.size {
2226 return;
2227 }
2228 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2229 label: Some("glyph atlas RGBA (grown)"),
2230 size: wgpu::Extent3d {
2231 width: new_size,
2232 height: new_size,
2233 depth_or_array_layers: 1,
2234 },
2235 mip_level_count: 1,
2236 sample_count: 1,
2237 dimension: wgpu::TextureDimension::D2,
2238 format: wgpu::TextureFormat::Rgba8UnormSrgb,
2239 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2240 view_formats: &[],
2241 });
2242 self.atlas_color.tex = tex;
2243 self.atlas_color.view = self
2244 .atlas_color
2245 .tex
2246 .create_view(&wgpu::TextureViewDescriptor::default());
2247 self.atlas_color.size = new_size;
2248 self.atlas_color.next_x = 1;
2249 self.atlas_color.next_y = 1;
2250 self.atlas_color.row_h = 0;
2251 let keys: Vec<(repose_text::GlyphKey, u32)> =
2252 self.atlas_color.map.keys().copied().collect();
2253 self.atlas_color.map.clear();
2254 for (k, px) in keys {
2255 let _ = self.upload_glyph_color(k, px);
2256 }
2257 }
2258}
2259
2260fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
2261 match brush {
2262 Brush::Solid(c) => (
2263 0u32,
2264 c.to_linear(),
2265 [0.0, 0.0, 0.0, 0.0],
2266 [0.0, 0.0],
2267 [0.0, 1.0],
2268 ),
2269 Brush::Linear {
2270 start,
2271 end,
2272 start_color,
2273 end_color,
2274 } => (
2275 1u32,
2276 start_color.to_linear(),
2277 end_color.to_linear(),
2278 [start.x, start.y],
2279 [end.x, end.y],
2280 ),
2281 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
2282 }
2283}
2284
2285fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
2286 match brush {
2287 Brush::Solid(c) => c.to_linear(),
2288 Brush::Linear { start_color, .. } => start_color.to_linear(),
2289 _ => [0.0; 4],
2290 }
2291}
2292
2293impl RenderBackend for WgpuBackend {
2294 fn configure_surface(&mut self, width: u32, height: u32) {
2295 if width == 0 || height == 0 {
2296 return;
2297 }
2298 self.config.width = width;
2299 self.config.height = height;
2300 self.surface.configure(&self.device, &self.config);
2301 self.recreate_msaa_and_depth_stencil();
2302 }
2303
2304 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
2305 self.frame_index = self.frame_index.wrapping_add(1);
2307 self.slug_cache.next_frame();
2308
2309 if self.config.width == 0 || self.config.height == 0 {
2310 return;
2311 }
2312 let mut retries = 0u32;
2313 const MAX_RETRIES: u32 = 4;
2314 let frame = loop {
2315 match self.surface.get_current_texture() {
2316 wgpu::CurrentSurfaceTexture::Success(f) => break f,
2317 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
2318 log::warn!("suboptimal surface; reconfiguring");
2319 self.surface.configure(&self.device, &self.config);
2320 break f;
2321 }
2322 wgpu::CurrentSurfaceTexture::Outdated => {
2323 retries += 1;
2324 if retries >= MAX_RETRIES {
2325 log::warn!(
2326 "surface outdated persisted after {MAX_RETRIES} retries; skipping frame"
2327 );
2328 return;
2329 }
2330 log::warn!("surface outdated; reconfiguring");
2331 self.surface.configure(&self.device, &self.config);
2332 }
2333 wgpu::CurrentSurfaceTexture::Lost => {
2334 retries += 1;
2335 if retries >= MAX_RETRIES {
2336 log::warn!(
2337 "surface lost persisted after {MAX_RETRIES} retries; skipping frame"
2338 );
2339 return;
2340 }
2341 log::warn!("surface lost; reconfiguring");
2342 self.surface.configure(&self.device, &self.config);
2343 }
2344 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
2345 request_frame();
2346 return;
2347 }
2348 wgpu::CurrentSurfaceTexture::Validation => {
2349 retries += 1;
2350 if retries >= MAX_RETRIES {
2351 log::warn!(
2352 "surface validation persisted after {MAX_RETRIES} retries; skipping frame"
2353 );
2354 return;
2355 }
2356 self.surface.configure(&self.device, &self.config);
2357 }
2358 }
2359 };
2360
2361 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
2362 let x0 = (x / fb_w) * 2.0 - 1.0;
2363 let y0 = 1.0 - (y / fb_h) * 2.0;
2364 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
2365 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
2366 let min_x = x0.min(x1);
2367 let min_y = y0.min(y1);
2368 let w_ndc = (x1 - x0).abs();
2369 let h_ndc = (y1 - y0).abs();
2370 [min_x, min_y, w_ndc, h_ndc]
2371 }
2372
2373 fn rect_to_instance_ndc(
2375 rect: repose_core::Rect,
2376 transform: &Transform,
2377 fb_w: f32,
2378 fb_h: f32,
2379 ) -> ([f32; 4], [f32; 2]) {
2380 let cx = rect.x + rect.w * 0.5;
2381 let cy = rect.y + rect.h * 0.5;
2382
2383 let sx = cx * transform.scale_x;
2385 let sy = cy * transform.scale_y;
2386 let cos_a = transform.rotate.cos();
2387 let sin_a = transform.rotate.sin();
2388 let tx = sx * cos_a - sy * sin_a + transform.translate_x;
2389 let ty = sx * sin_a + sy * cos_a + transform.translate_y;
2390
2391 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
2393 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
2394 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
2396 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
2397
2398 ([ndc_cx, ndc_cy, ndc_w, ndc_h], [cos_a, sin_a])
2399 }
2400
2401 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
2402 let mut x = r.x.floor() as i64;
2403 let mut y = r.y.floor() as i64;
2404 let fb_wi = fb_w as i64;
2405 let fb_hi = fb_h as i64;
2406 x = x.clamp(0, fb_wi.saturating_sub(1));
2407 y = y.clamp(0, fb_hi.saturating_sub(1));
2408 let w_req = r.w.ceil().max(1.0) as i64;
2409 let h_req = r.h.ceil().max(1.0) as i64;
2410 let w = (w_req).min(fb_wi - x).max(1);
2411 let h = (h_req).min(fb_hi - y).max(1);
2412 (x as u32, y as u32, w as u32, h as u32)
2413 }
2414
2415 let fb_w = self.config.width as f32;
2416 let fb_h = self.config.height as f32;
2417
2418 let globals = Globals {
2419 ndc_to_px: [fb_w * 0.5, fb_h * 0.5],
2420 _pad: [0.0, 0.0],
2421 };
2422 self.queue
2423 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
2424
2425 let mut passes: Vec<Pass> = Vec::with_capacity(1);
2426 let mut current_pass: Pass = Pass {
2427 target: PassTarget::Surface,
2428 initial_scissor: (0, 0, self.config.width, self.config.height),
2429 clear_color: Some([
2430 scene.clear_color.0 as f32 / 255.0,
2431 scene.clear_color.1 as f32 / 255.0,
2432 scene.clear_color.2 as f32 / 255.0,
2433 scene.clear_color.3 as f32 / 255.0,
2434 ]),
2435 cmds: Vec::with_capacity(scene.nodes.len()),
2436 };
2437 let mut target_stack: Vec<PassTarget> = Vec::new();
2438 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
2439 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
2440
2441 struct Batch {
2442 rects: Vec<RectInstance>,
2443 borders: Vec<BorderInstance>,
2444 ellipses: Vec<EllipseInstance>,
2445 e_borders: Vec<EllipseBorderInstance>,
2446 arcs: Vec<ArcInstance>,
2447 masks: Vec<GlyphInstance>,
2448 colors: Vec<GlyphInstance>,
2449 nv12s: Vec<Nv12Instance>,
2450 }
2451
2452 impl Batch {
2453 fn new() -> Self {
2454 Self {
2455 rects: vec![],
2456 borders: vec![],
2457 ellipses: vec![],
2458 e_borders: vec![],
2459 arcs: vec![],
2460 masks: vec![],
2461 colors: vec![],
2462 nv12s: vec![],
2463 }
2464 }
2465
2466 fn is_empty(&self) -> bool {
2467 self.rects.is_empty()
2468 && self.borders.is_empty()
2469 && self.ellipses.is_empty()
2470 && self.e_borders.is_empty()
2471 && self.arcs.is_empty()
2472 && self.masks.is_empty()
2473 && self.colors.is_empty()
2474 && self.nv12s.is_empty()
2475 }
2476
2477 fn flush(
2478 &mut self,
2479 pipes: (
2480 &mut InstancedPipe<RectInstance>,
2481 &mut InstancedPipe<BorderInstance>,
2482 &mut InstancedPipe<EllipseInstance>,
2483 &mut InstancedPipe<EllipseBorderInstance>,
2484 &mut InstancedPipe<ArcInstance>,
2485 ),
2486 glyph_pipes: (
2487 &mut InstancedPipe<GlyphInstance>,
2488 &mut InstancedPipe<GlyphInstance>,
2489 ),
2490 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
2491 device: &wgpu::Device,
2492 queue: &wgpu::Queue,
2493 cmds: &mut Vec<Cmd>,
2494 ) {
2495 let (rects, borders, ellipses, e_borders, arcs) = pipes;
2496 let (masks, colors) = glyph_pipes;
2497
2498 macro_rules! flush_one {
2499 ($buf:ident, $pipe:expr, $variant:ident) => {
2500 if !self.$buf.is_empty() {
2501 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
2502 cmds.push(Cmd::$variant { off, cnt });
2503 }
2504 self.$buf.clear();
2505 }
2506 };
2507 }
2508
2509 flush_one!(rects, rects, Rect);
2510 flush_one!(borders, borders, Border);
2511 flush_one!(ellipses, ellipses, Ellipse);
2512 flush_one!(e_borders, e_borders, EllipseBorder);
2513 flush_one!(arcs, arcs, Arc);
2514 flush_one!(masks, masks, GlyphsMask);
2515 flush_one!(colors, colors, GlyphsColor);
2516
2517 if !self.nv12s.is_empty() {
2518 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
2519 let _ = (off, cnt);
2520 }
2521 self.nv12s.clear();
2522 }
2523 }
2524 }
2525
2526 self.rects.reset();
2527 self.borders.reset();
2528 self.ellipses.reset();
2529 self.ellipse_borders.reset();
2530 self.arcs.reset();
2531 self.glyph_mask.reset();
2532 self.glyph_color.reset();
2533 self.clip_ring.reset();
2534 self.blur_ring.reset();
2535 self.nv12.reset();
2536
2537 self.slug_ring.reset();
2538 let mut batch = Batch::new();
2539 let mut slug_verts_local: Vec<slug::TessVertex> = Vec::new();
2540 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
2541 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
2542 let root_clip_rect = repose_core::Rect {
2543 x: 0.0,
2544 y: 0.0,
2545 w: fb_w,
2546 h: fb_h,
2547 };
2548
2549 let mut current_prim: Option<&'static str> = None;
2550
2551 macro_rules! flush_if_prim_changed {
2552 ($prim:literal, $pipe:expr) => {
2553 if current_prim != Some($prim) {
2554 flush_batch!();
2555 current_prim = Some($prim);
2556 }
2557 };
2558 }
2559
2560 macro_rules! flush_batch {
2561 () => {
2562 if !batch.is_empty() {
2563 batch.flush(
2564 (
2565 &mut self.rects,
2566 &mut self.borders,
2567 &mut self.ellipses,
2568 &mut self.ellipse_borders,
2569 &mut self.arcs,
2570 ),
2571 (&mut self.glyph_mask, &mut self.glyph_color),
2572 &mut self.nv12,
2573 &self.device,
2574 &self.queue,
2575 &mut current_pass.cmds,
2576 )
2577 }
2578 };
2579 }
2580
2581 for node in &scene.nodes {
2582 let t_identity = Transform::identity();
2583 let current_transform = transform_stack.last().unwrap_or(&t_identity);
2584
2585 match node {
2586 SceneNode::Rect {
2587 rect,
2588 brush,
2589 radius,
2590 } => {
2591 flush_if_prim_changed!("rect", &self.rects);
2592 let (ndc, sin_cos) = rect_to_instance_ndc(
2593 *rect,
2594 current_transform,
2595 current_target_size.0,
2596 current_target_size.1,
2597 );
2598 let (brush_type, color0, color1, grad_start, grad_end) =
2599 brush_to_instance_fields(brush);
2600 batch.rects.push(RectInstance {
2601 xywh: ndc,
2602 radii: *radius,
2603 brush_type,
2604 _pad: [0.0; 3],
2605 color0,
2606 color1,
2607 grad_start,
2608 grad_end,
2609 sin_cos,
2610 });
2611 }
2612 SceneNode::Border {
2613 rect,
2614 color,
2615 width,
2616 radius,
2617 } => {
2618 flush_if_prim_changed!("border", &self.borders);
2619 let (ndc, sin_cos) = rect_to_instance_ndc(
2620 *rect,
2621 current_transform,
2622 current_target_size.0,
2623 current_target_size.1,
2624 );
2625 batch.borders.push(BorderInstance {
2626 xywh: ndc,
2627 radii: *radius,
2628 stroke: *width,
2629 color: color.to_linear(),
2630 sin_cos,
2631 });
2632 }
2633 SceneNode::Ellipse { rect, brush } => {
2634 flush_if_prim_changed!("ellipse", &self.ellipses);
2635 let (ndc, sin_cos) = rect_to_instance_ndc(
2636 *rect,
2637 current_transform,
2638 current_target_size.0,
2639 current_target_size.1,
2640 );
2641 let color = brush_to_solid_color(brush);
2642 batch.ellipses.push(EllipseInstance {
2643 xywh: ndc,
2644 color,
2645 sin_cos,
2646 });
2647 }
2648 SceneNode::EllipseBorder { rect, color, width } => {
2649 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
2650 let (ndc, sin_cos) = rect_to_instance_ndc(
2651 *rect,
2652 current_transform,
2653 current_target_size.0,
2654 current_target_size.1,
2655 );
2656 let pad_px = *width * 0.5 + 2.0;
2657 let pad = (pad_px / current_target_size.0) * 2.0;
2658 batch.e_borders.push(EllipseBorderInstance {
2659 xywh: ndc,
2660 stroke: *width,
2661 pad,
2662 color: color.to_linear(),
2663 sin_cos,
2664 });
2665 }
2666 SceneNode::Arc {
2667 rect,
2668 start_angle,
2669 sweep_angle,
2670 stroke_width,
2671 color,
2672 cap,
2673 } => {
2674 flush_if_prim_changed!("arc", &self.arcs);
2675 let (ndc, sin_cos) = rect_to_instance_ndc(
2676 *rect,
2677 current_transform,
2678 current_target_size.0,
2679 current_target_size.1,
2680 );
2681 let pad_px = *stroke_width * 0.5 + 2.0;
2682 let pad = (pad_px / current_target_size.0) * 2.0;
2683 let cap_val = match cap {
2684 StrokeCap::Butt => 0.0,
2685 StrokeCap::Round => 1.0,
2686 StrokeCap::Square => 2.0,
2687 };
2688 batch.arcs.push(ArcInstance {
2689 xywh: ndc,
2690 start_angle: *start_angle,
2691 sweep_angle: *sweep_angle,
2692 stroke: *stroke_width,
2693 pad,
2694 color: color.to_linear(),
2695 sin_cos,
2696 cap: cap_val,
2697 });
2698 }
2699 SceneNode::Text {
2700 rect,
2701 text,
2702 color,
2703 size,
2704 font_family,
2705 text_align: _,
2706 font_weight,
2707 font_style,
2708 text_decoration: _,
2709 letter_spacing: _,
2710 line_height: _,
2711 } => {
2712 flush_batch!(); let px = (*size).clamp(8.0, 96.0);
2715 let fw = font_weight.0;
2716 let fs = if *font_style == FontStyle::Italic {
2717 1
2718 } else {
2719 0
2720 };
2721 let shaped = repose_text::shape_line(text.as_ref(), px, *font_family, fw, fs);
2722
2723 let cos_a = current_transform.rotate.cos();
2724 let sin_a = current_transform.rotate.sin();
2725 let has_rotation = current_transform.rotate != 0.0;
2726
2727 let pivot_x = rect.x + rect.w * 0.5;
2729 let pivot_y = rect.y + rect.h * 0.5;
2730
2731 let make_glyph_instance =
2733 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 2]) {
2734 if has_rotation {
2735 let corners =
2736 [(gx, gy), (gx + gw, gy), (gx + gw, gy + gh), (gx, gy + gh)];
2737 let mut min_x = f32::MAX;
2738 let mut max_x = f32::MIN;
2739 let mut min_y = f32::MAX;
2740 let mut max_y = f32::MIN;
2741 for &(x, y) in &corners {
2742 let dx = x - pivot_x;
2743 let dy = y - pivot_y;
2744 let rx = pivot_x + dx * cos_a - dy * sin_a;
2745 let ry = pivot_y + dx * sin_a + dy * cos_a;
2746 min_x = min_x.min(rx);
2747 max_x = max_x.max(rx);
2748 min_y = min_y.min(ry);
2749 max_y = max_y.max(ry);
2750 }
2751 let bb_w = max_x - min_x;
2752 let bb_h = max_y - min_y;
2753 let ndc_tl = to_ndc(
2754 min_x,
2755 min_y,
2756 bb_w,
2757 bb_h,
2758 current_target_size.0,
2759 current_target_size.1,
2760 );
2761 let ndc = [
2762 ndc_tl[0] + ndc_tl[2] * 0.5,
2763 ndc_tl[1] + ndc_tl[3] * 0.5,
2764 ndc_tl[2],
2765 ndc_tl[3],
2766 ];
2767 (ndc, [cos_a, sin_a])
2768 } else {
2769 rect_to_instance_ndc(
2770 repose_core::Rect {
2771 x: gx,
2772 y: gy,
2773 w: gw,
2774 h: gh,
2775 },
2776 current_transform,
2777 current_target_size.0,
2778 current_target_size.1,
2779 )
2780 }
2781 };
2782
2783 for sg in shaped {
2784 let gx = rect.x + sg.x + sg.bearing_x;
2785 let gy = rect.y + sg.y - sg.bearing_y;
2786
2787 if self.slug_enabled {
2789 let ck = repose_text::lookup_cache_key(sg.key);
2790 if let Some(ref ck) = ck {
2791 if !self.slug_cache.contains(ck) {
2792 if let Some((ck2, commands)) =
2793 repose_text::lookup_and_extract_outline(sg.key)
2794 {
2795 let font_size_px = f32::from_bits(ck2.font_size_bits);
2796 self.slug_cache.get_or_insert(ck2, font_size_px, &commands);
2797 }
2798 }
2799 }
2800 if let Some(ref ck) = ck {
2801 self.slug_cache.touch(ck);
2802 }
2803 if let Some(entry) = ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
2804 {
2805 let ox = rect.x + sg.x;
2806 let oy = rect.y + sg.y;
2807 let scx = current_transform.scale_x;
2808 let scy = current_transform.scale_y;
2809 let ttx = current_transform.translate_x;
2810 let tty = current_transform.translate_y;
2811
2812 let tf = |x: f32, y: f32| -> (f32, f32) {
2813 if has_rotation {
2814 let dx = x - pivot_x;
2815 let dy = y - pivot_y;
2816 let rx = pivot_x + dx * cos_a - dy * sin_a;
2817 let ry = pivot_y + dx * sin_a + dy * cos_a;
2818 (rx, ry)
2819 } else {
2820 (x * scx + ttx, y * scy + tty)
2821 }
2822 };
2823
2824 let tw = current_target_size.0;
2825 let th = current_target_size.1;
2826
2827 for &v in &entry.vertices {
2828 let (sx, sy) = tf(ox + v[0] * px, oy - v[1] * px);
2829 let ndc_x = sx / tw * 2.0 - 1.0;
2830 let ndc_y = -(sy / th) * 2.0 + 1.0;
2831 slug_verts_local.push(slug::TessVertex {
2832 ndc_pos: [ndc_x, ndc_y],
2833 color: color.to_linear(),
2834 });
2835 }
2836 continue;
2837 }
2838 }
2839
2840 if let Some(info) = self.upload_glyph_color(sg.key, px as u32) {
2842 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
2843 batch.colors.push(GlyphInstance {
2844 xywh: ndc,
2845 uv: [info.u0, info.v1, info.u1, info.v0],
2846 color: color.to_linear(),
2847 sin_cos,
2848 });
2849 } else if let Some(info) = self.upload_glyph_mask(sg.key, px as u32) {
2850 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
2851 batch.masks.push(GlyphInstance {
2852 xywh: ndc,
2853 uv: [info.u0, info.v1, info.u1, info.v0],
2854 color: color.to_linear(),
2855 sin_cos,
2856 });
2857 }
2858 }
2859
2860 if !slug_verts_local.is_empty() {
2862 let bytes = bytemuck::cast_slice(&slug_verts_local);
2863 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
2864 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
2865 current_pass.cmds.push(Cmd::GlyphsVector {
2866 off,
2867 cnt: slug_verts_local.len() as u32,
2868 });
2869 slug_verts_local.clear();
2870 }
2871 }
2872 SceneNode::Image {
2873 rect,
2874 handle,
2875 tint,
2876 fit,
2877 } => {
2878 flush_batch!();
2879
2880 let (img_w, img_h, is_nv12) = if let Some(t) = self.images.get_mut(handle) {
2882 match t {
2883 ImageTex::Rgba {
2884 w,
2885 h,
2886 last_used_frame,
2887 ..
2888 } => {
2889 *last_used_frame = self.frame_index;
2890 (*w, *h, false)
2891 }
2892 ImageTex::Nv12 {
2893 w,
2894 h,
2895 last_used_frame,
2896 ..
2897 } => {
2898 *last_used_frame = self.frame_index;
2899 (*w, *h, true)
2900 }
2901 }
2902 } else {
2903 log::warn!("Image handle {} not found", handle);
2904 continue;
2905 };
2906
2907 let src_w = img_w as f32;
2908 let src_h = img_h as f32;
2909 let transformed = current_transform.apply_to_rect(*rect);
2910 let dst_w = transformed.w.max(0.0);
2911 let dst_h = transformed.h.max(0.0);
2912 if dst_w <= 0.0 || dst_h <= 0.0 {
2913 continue;
2914 }
2915
2916 let (xywh_ndc, uv_rect) = match fit {
2917 repose_core::view::ImageFit::Contain => {
2918 let scale = (dst_w / src_w).min(dst_h / src_h);
2919 let w = src_w * scale;
2920 let h = src_h * scale;
2921 let x = transformed.x + (dst_w - w) * 0.5;
2922 let y = transformed.y + (dst_h - h) * 0.5;
2923 (
2924 to_ndc(x, y, w, h, current_target_size.0, current_target_size.1),
2925 [0.0, 1.0, 1.0, 0.0],
2926 )
2927 }
2928 repose_core::view::ImageFit::Cover => {
2929 let scale = (dst_w / src_w).max(dst_h / src_h);
2930 let content_w = src_w * scale;
2931 let content_h = src_h * scale;
2932 let overflow_x = (content_w - dst_w) * 0.5;
2933 let overflow_y = (content_h - dst_h) * 0.5;
2934 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
2935 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
2936 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
2937 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
2938 (
2939 to_ndc(
2940 transformed.x,
2941 transformed.y,
2942 dst_w,
2943 dst_h,
2944 current_target_size.0,
2945 current_target_size.1,
2946 ),
2947 [u0, 1.0 - v1, u1, 1.0 - v0],
2948 )
2949 }
2950 repose_core::view::ImageFit::FitWidth => {
2951 let scale = dst_w / src_w;
2952 let w = dst_w;
2953 let h = src_h * scale;
2954 let y = transformed.y + (dst_h - h) * 0.5;
2955 (
2956 to_ndc(
2957 transformed.x,
2958 y,
2959 w,
2960 h,
2961 current_target_size.0,
2962 current_target_size.1,
2963 ),
2964 [0.0, 1.0, 1.0, 0.0],
2965 )
2966 }
2967 repose_core::view::ImageFit::FitHeight => {
2968 let scale = dst_h / src_h;
2969 let w = src_w * scale;
2970 let h = dst_h;
2971 let x = transformed.x + (dst_w - w) * 0.5;
2972 (
2973 to_ndc(
2974 x,
2975 transformed.y,
2976 w,
2977 h,
2978 current_target_size.0,
2979 current_target_size.1,
2980 ),
2981 [0.0, 1.0, 1.0, 0.0],
2982 )
2983 }
2984 _ => ([0.0; 4], [0.0; 4]),
2985 };
2986
2987 let ndc_center = [
2989 xywh_ndc[0] + xywh_ndc[2] * 0.5,
2990 xywh_ndc[1] + xywh_ndc[3] * 0.5,
2991 xywh_ndc[2],
2992 xywh_ndc[3],
2993 ];
2994
2995 if is_nv12 {
2996 let full_range = if let Some(ImageTex::Nv12 { full_range, .. }) =
2997 self.images.get(handle)
2998 {
2999 if *full_range { 1.0 } else { 0.0 }
3000 } else {
3001 0.0
3002 };
3003
3004 let inst = Nv12Instance {
3005 xywh: ndc_center,
3006 uv: uv_rect,
3007 color: tint.to_linear(),
3008 full_range,
3009 sin_cos: [1.0, 0.0],
3010 _pad: [0.0],
3011 };
3012 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
3013 {
3014 current_pass.cmds.push(Cmd::ImageNv12 {
3015 off,
3016 cnt: 1,
3017 handle: *handle,
3018 });
3019 }
3020 } else {
3021 let inst = GlyphInstance {
3023 xywh: ndc_center,
3024 uv: uv_rect,
3025 color: tint.to_linear(),
3026 sin_cos: [1.0, 0.0],
3027 };
3028 if let Some((off, _)) =
3029 self.glyph_color.upload(&self.device, &self.queue, &[inst])
3030 {
3031 current_pass.cmds.push(Cmd::ImageRgba {
3032 off,
3033 cnt: 1,
3034 handle: *handle,
3035 });
3036 }
3037 }
3038 }
3039 SceneNode::PushClip { rect, radius } => {
3040 flush_batch!(); let t_identity = Transform::identity();
3043 let current_transform = transform_stack.last().unwrap_or(&t_identity);
3044 let transformed = current_transform.apply_to_rect(*rect);
3045
3046 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
3047 let next_scissor = intersect(top, transformed);
3048 scissor_stack.push(next_scissor);
3049 let scissor = to_scissor(
3050 &next_scissor,
3051 current_target_size.0 as u32,
3052 current_target_size.1 as u32,
3053 );
3054
3055 let clip_ndc_tl = to_ndc(
3056 transformed.x,
3057 transformed.y,
3058 transformed.w,
3059 transformed.h,
3060 current_target_size.0,
3061 current_target_size.1,
3062 );
3063 let inst = ClipInstance {
3064 xywh: [
3065 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
3066 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
3067 clip_ndc_tl[2],
3068 clip_ndc_tl[3],
3069 ],
3070 radii: *radius,
3071 sin_cos: [1.0, 0.0],
3072 };
3073 let bytes = bytemuck::bytes_of(&inst);
3074 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
3075 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
3076
3077 current_pass.cmds.push(Cmd::ClipPush {
3078 off,
3079 cnt: 1,
3080 scissor,
3081 });
3082 }
3083 SceneNode::PopClip => {
3084 flush_batch!();
3085
3086 if !scissor_stack.is_empty() {
3087 scissor_stack.pop();
3088 } else {
3089 log::warn!("PopClip with empty stack");
3090 }
3091
3092 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
3093 let scissor = to_scissor(
3094 &top,
3095 current_target_size.0 as u32,
3096 current_target_size.1 as u32,
3097 );
3098 current_pass.cmds.push(Cmd::ClipPop { scissor });
3099 }
3100 SceneNode::Shadow {
3101 rect,
3102 radius,
3103 elevation: _,
3104 color,
3105 } => {
3106 flush_if_prim_changed!("rect", &self.rects);
3107 let (ndc, sin_cos) = rect_to_instance_ndc(
3108 *rect,
3109 current_transform,
3110 current_target_size.0,
3111 current_target_size.1,
3112 );
3113 let (brush_type, color0, _color1, _grad_start, _grad_end) =
3114 brush_to_instance_fields(&Brush::Solid(*color));
3115 batch.rects.push(RectInstance {
3116 xywh: ndc,
3117 radii: *radius,
3118 brush_type,
3119 _pad: [0.0; 3],
3120 color0,
3121 color1: [0.0; 4],
3122 grad_start: [0.0; 2],
3123 grad_end: [0.0; 2],
3124 sin_cos,
3125 });
3126 }
3127 SceneNode::PushTransform { transform } => {
3128 flush_batch!(); let combined = current_transform.combine(transform);
3130 transform_stack.push(combined);
3131 }
3132 SceneNode::PopTransform => {
3133 flush_batch!(); transform_stack.pop();
3135 }
3136 SceneNode::BeginLayer {
3137 rect,
3138 layer_id,
3139 alpha,
3140 } => {
3141 flush_batch!();
3142 let w = (rect.w.max(1.0)).ceil() as u32;
3143 let h = (rect.h.max(1.0)).ceil() as u32;
3144 let prev_target = current_pass.target;
3146 let prev_scissor = current_pass.initial_scissor;
3147 let saved = std::mem::replace(
3148 &mut current_pass,
3149 Pass {
3150 target: PassTarget::Layer(*layer_id),
3151 initial_scissor: (0, 0, w, h),
3152 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
3153 cmds: Vec::new(),
3154 },
3155 );
3156 passes.push(saved);
3157 target_stack.push(prev_target);
3158 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
3162 current_target_size = (w as f32, h as f32);
3163 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
3164 }
3165 SceneNode::EndLayer { layer_id } => {
3166 flush_batch!();
3167 let saved = std::mem::replace(
3169 &mut current_pass,
3170 Pass {
3171 target: target_stack.pop().unwrap_or(PassTarget::Surface),
3172 initial_scissor: (0, 0, self.config.width, self.config.height),
3173 clear_color: None, cmds: Vec::new(),
3175 },
3176 );
3177 passes.push(saved);
3178 current_target_size = (fb_w, fb_h);
3179 if let Some((_, layer_alpha, _)) = layer_alphas
3181 .iter()
3182 .find(|(id, _, _)| id == layer_id)
3183 .copied()
3184 {
3185 let layer = self.layer_pool.get(layer_id).expect("layer target");
3186 let ndc_tl = to_ndc(
3187 layer.rect_px.0,
3188 layer.rect_px.1,
3189 layer.rect_px.2,
3190 layer.rect_px.3,
3191 fb_w,
3192 fb_h,
3193 );
3194 let inst = GlyphInstance {
3195 xywh: [
3196 ndc_tl[0] + ndc_tl[2] * 0.5,
3197 ndc_tl[1] + ndc_tl[3] * 0.5,
3198 ndc_tl[2],
3199 ndc_tl[3],
3200 ],
3201 uv: [0.0, 1.0, 1.0, 0.0],
3202 color: [1.0, 1.0, 1.0, layer_alpha],
3203 sin_cos: [1.0, 0.0],
3204 };
3205 if let Some((off, cnt)) =
3206 self.glyph_color.upload(&self.device, &self.queue, &[inst])
3207 {
3208 current_pass.cmds.push(Cmd::CompositeLayer {
3209 off,
3210 cnt,
3211 layer_id: *layer_id,
3212 alpha: layer_alpha,
3213 });
3214 }
3215 }
3216 }
3217 SceneNode::CompositeShadow {
3218 layer_id,
3219 blur_px,
3220 offset_px,
3221 color,
3222 } => {
3223 flush_batch!();
3224 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
3225 let sx = layer.rect_px.0 + offset_px.0;
3227 let sy = layer.rect_px.1 + offset_px.1;
3228 let sw = layer.rect_px.2;
3229 let sh = layer.rect_px.3;
3230 let bw_uv = (blur_px * 1.5) / layer.width.max(1) as f32;
3233 let bh_uv = (blur_px * 1.5) / layer.height.max(1) as f32;
3234 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
3235 let inst = BlurInstance {
3236 xywh: [
3237 ndc_tl[0] + ndc_tl[2] * 0.5,
3238 ndc_tl[1] + ndc_tl[3] * 0.5,
3239 ndc_tl[2],
3240 ndc_tl[3],
3241 ],
3242 uv: [0.0, 0.0, 1.0, 1.0],
3243 color: [
3244 color.0 as f32 / 255.0,
3245 color.1 as f32 / 255.0,
3246 color.2 as f32 / 255.0,
3247 color.3 as f32 / 255.0,
3248 ],
3249 blur_uv: [bw_uv, bh_uv],
3250 sin_cos: [1.0, 0.0],
3251 };
3252 self.blur_ring
3253 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
3254 let bytes = bytemuck::bytes_of(&inst);
3255 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
3256 current_pass.cmds.push(Cmd::CompositeShadow {
3257 off,
3258 cnt: 1,
3259 layer_id: *layer_id,
3260 });
3261 }
3262 }
3263 _ => {}
3264 }
3265 }
3266
3267 flush_batch!();
3268
3269 passes.push(current_pass);
3271
3272 let mut encoder = self
3273 .device
3274 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3275 label: Some("frame encoder"),
3276 });
3277
3278 let bind_mask = self.atlas_bind_group_mask();
3279 let bind_color = self.atlas_bind_group_color();
3280 let mut clip_depth: u32 = 0;
3281
3282 for pass in std::mem::take(&mut passes) {
3283 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
3284 PassTarget::Surface => {
3285 let swap_view = frame
3286 .texture
3287 .create_view(&wgpu::TextureViewDescriptor::default());
3288 let (color, resolve) = if let Some(msaa_view) = &self.msaa_view {
3289 (msaa_view.clone(), Some(swap_view))
3290 } else {
3291 (swap_view, None)
3292 };
3293 (color, resolve, self.depth_stencil_view.clone(), false)
3294 }
3295 PassTarget::Layer(layer_id) => {
3296 if let Some(lt) = self.layer_pool.get(&layer_id) {
3297 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
3298 } else {
3299 log::warn!("missing layer target {layer_id}");
3300 continue;
3301 }
3302 }
3303 };
3304
3305 if is_layer {
3306 clip_depth = 0;
3307 }
3308
3309 let pipes: &Pipelines = if is_layer {
3310 &self.layer_pipes
3311 } else {
3312 &self.surface_pipes
3313 };
3314
3315 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3316 label: Some("pass"),
3317 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3318 view: &color_view,
3319 resolve_target: resolve_target.as_ref(),
3320 ops: wgpu::Operations {
3321 load: match pass.clear_color {
3322 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
3323 r: c[0] as f64,
3324 g: c[1] as f64,
3325 b: c[2] as f64,
3326 a: c[3] as f64,
3327 }),
3328 None => wgpu::LoadOp::Load,
3329 },
3330 store: wgpu::StoreOp::Store,
3331 },
3332 depth_slice: None,
3333 })],
3334 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
3335 view: &depth_stencil_view,
3336 depth_ops: None,
3337 stencil_ops: Some(wgpu::Operations {
3338 load: if is_layer || pass.clear_color.is_some() {
3339 wgpu::LoadOp::Clear(0)
3340 } else {
3341 wgpu::LoadOp::Load
3342 },
3343 store: wgpu::StoreOp::Store,
3344 }),
3345 }),
3346 timestamp_writes: None,
3347 occlusion_query_set: None,
3348 multiview_mask: None,
3349 });
3350
3351 rpass.set_bind_group(0, &self.globals_bind, &[]);
3352 rpass.set_stencil_reference(clip_depth);
3353 rpass.set_scissor_rect(
3354 pass.initial_scissor.0,
3355 pass.initial_scissor.1,
3356 pass.initial_scissor.2,
3357 pass.initial_scissor.3,
3358 );
3359
3360 macro_rules! draw_simple {
3361 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
3362 rpass.set_pipeline($pipeline);
3363 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
3364 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
3365 rpass.draw(0..6, 0..$n);
3366 }};
3367 }
3368
3369 macro_rules! draw_with_bind {
3370 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
3371 rpass.set_pipeline($pipeline);
3372 rpass.set_bind_group(1, $bind, &[]);
3373 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
3374 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
3375 rpass.draw(0..6, 0..$n);
3376 }};
3377 }
3378
3379 for cmd in pass.cmds {
3380 match cmd {
3381 Cmd::ClipPush {
3382 off,
3383 cnt: n,
3384 scissor,
3385 } => {
3386 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
3387 rpass.set_stencil_reference(clip_depth);
3388
3389 if self.msaa_samples > 1 && !is_layer {
3390 rpass.set_pipeline(&pipes.clip_a2c);
3391 } else {
3392 rpass.set_pipeline(&pipes.clip_bin);
3393 }
3394
3395 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
3396 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
3397 rpass.draw(0..6, 0..n);
3398
3399 clip_depth = (clip_depth + 1).min(255);
3400 rpass.set_stencil_reference(clip_depth);
3401 }
3402
3403 Cmd::ClipPop { scissor } => {
3404 clip_depth = clip_depth.saturating_sub(1);
3405 rpass.set_stencil_reference(clip_depth);
3406 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
3407 }
3408
3409 Cmd::Rect { off, cnt: n } => {
3410 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
3411 }
3412
3413 Cmd::Border { off, cnt: n } => {
3414 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
3415 }
3416
3417 Cmd::GlyphsMask { off, cnt: n } => {
3418 draw_with_bind!(
3419 &pipes.text_mask,
3420 self.glyph_mask.ring,
3421 GlyphInstance,
3422 &bind_mask,
3423 off,
3424 n
3425 );
3426 }
3427
3428 Cmd::GlyphsColor { off, cnt: n } => {
3429 draw_with_bind!(
3430 &pipes.text_color,
3431 self.glyph_color.ring,
3432 GlyphInstance,
3433 &bind_color,
3434 off,
3435 n
3436 );
3437 }
3438
3439 Cmd::GlyphsVector { off, cnt: n } => {
3440 if let Some(ref slug_pipe) = pipes.slug.as_ref() {
3441 rpass.set_pipeline(slug_pipe);
3442 let bytes = (n as u64) * std::mem::size_of::<slug::TessVertex>() as u64;
3443 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
3444 rpass.draw(0..n, 0..1);
3445 }
3446 }
3447
3448 Cmd::ImageRgba {
3449 off,
3450 cnt: n,
3451 handle,
3452 } => {
3453 if let Some(ImageTex::Rgba { bind, .. }) = self.images.get(&handle) {
3454 draw_with_bind!(
3455 &pipes.image_rgba,
3456 self.glyph_color.ring,
3457 GlyphInstance,
3458 bind,
3459 off,
3460 n
3461 );
3462 }
3463 }
3464
3465 Cmd::ImageNv12 {
3466 off,
3467 cnt: n,
3468 handle,
3469 } => {
3470 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
3471 draw_with_bind!(
3472 &pipes.image_nv12,
3473 self.nv12.ring,
3474 Nv12Instance,
3475 bind,
3476 off,
3477 n
3478 );
3479 }
3480 }
3481
3482 Cmd::Ellipse { off, cnt: n } => {
3483 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
3484 }
3485
3486 Cmd::EllipseBorder { off, cnt: n } => {
3487 draw_simple!(
3488 &pipes.ellipse_borders,
3489 self.ellipse_borders.ring,
3490 EllipseBorderInstance,
3491 off,
3492 n
3493 );
3494 }
3495
3496 Cmd::Arc { off, cnt: n } => {
3497 draw_simple!(&pipes.arcs, self.arcs.ring, ArcInstance, off, n);
3498 }
3499
3500 Cmd::PushTransform(_) => {}
3501 Cmd::PopTransform => {}
3502 Cmd::CompositeLayer {
3503 off,
3504 cnt: n,
3505 layer_id,
3506 alpha: _,
3507 } => {
3508 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
3509 draw_with_bind!(
3510 &pipes.image_rgba,
3511 self.glyph_color.ring,
3512 GlyphInstance,
3513 <.bind,
3514 off,
3515 n
3516 );
3517 }
3518 }
3519 Cmd::CompositeShadow {
3520 off,
3521 cnt: n,
3522 layer_id,
3523 } => {
3524 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
3525 draw_with_bind!(
3526 &pipes.blur,
3527 self.blur_ring,
3528 BlurInstance,
3529 <.bind,
3530 off,
3531 n
3532 );
3533 }
3534 }
3535 }
3536 }
3537 }
3538
3539 self.queue.submit(std::iter::once(encoder.finish()));
3540 if let Err(e) = catch_unwind(AssertUnwindSafe(|| frame.present())) {
3541 log::warn!("frame.present panicked: {:?}", e);
3542 }
3543
3544 self.evict_unused_images();
3546 }
3547}
3548
3549fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
3550 let x0 = a.x.max(b.x);
3551 let y0 = a.y.max(b.y);
3552 let x1 = (a.x + a.w).min(b.x + b.w);
3553 let y1 = (a.y + a.h).min(b.y + b.h);
3554 repose_core::Rect {
3555 x: x0,
3556 y: y0,
3557 w: (x1 - x0).max(0.0),
3558 h: (y1 - y0).max(0.0),
3559 }
3560}