1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::num::NonZero;
4#[cfg(feature = "winit-surface")]
5use std::sync::Arc;
6#[cfg(feature = "winit-surface")]
7use std::panic::{AssertUnwindSafe, catch_unwind};
8use std::ops::{Deref, DerefMut};
9
10use repose_core::color::{ChromaSiting, ColorInfo, PixelFormat};
11use repose_core::request_frame;
12use repose_core::{
13 Brush, FontStyle, GlyphRasterConfig, PresentModePref, RenderBackend, Scene, SceneNode,
14 StrokeCap, Transform,
15};
16use wgpu::Instance;
17
18mod slug;
19
20#[derive(Clone)]
21struct UploadRing {
22 buf: wgpu::Buffer,
23 cap: u64,
24 head: u64,
25 usage: wgpu::BufferUsages,
26}
27
28impl UploadRing {
29 fn new(device: &wgpu::Device, label: &str, cap: u64, usage: wgpu::BufferUsages) -> Self {
30 let buf = device.create_buffer(&wgpu::BufferDescriptor {
31 label: Some(label),
32 size: cap,
33 usage,
34 mapped_at_creation: false,
35 });
36 Self {
37 buf,
38 cap,
39 head: 0,
40 usage,
41 }
42 }
43
44 fn reset(&mut self) {
45 self.head = 0;
46 }
47
48 fn grow_to_fit(&mut self, device: &wgpu::Device, needed: u64) {
49 let start = (self.head + 3) & !3;
50 if start + needed <= self.cap {
51 return;
52 }
53 let new_cap = (start + needed).next_power_of_two();
54 self.buf = device.create_buffer(&wgpu::BufferDescriptor {
55 label: Some("upload ring (grown)"),
56 size: new_cap,
57 usage: self.usage,
58 mapped_at_creation: false,
59 });
60 self.cap = new_cap;
61 }
62
63 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> (u64, u64) {
64 let len = bytes.len() as u64;
65 let start = (self.head + 3) & !3; let end = start + len;
67 assert!(end <= self.cap, "ring overflow - call grow_to_fit first");
68 queue.write_buffer(&self.buf, start, bytes);
69 self.head = end;
70 (start, len)
71 }
72}
73
74struct InstancedPipe<I: bytemuck::Pod> {
75 ring: UploadRing,
76 _marker: std::marker::PhantomData<I>,
77}
78
79impl<I: bytemuck::Pod> InstancedPipe<I> {
80 fn new(ring: UploadRing) -> Self {
81 Self {
82 ring,
83 _marker: std::marker::PhantomData,
84 }
85 }
86
87 fn upload(
88 &mut self,
89 device: &wgpu::Device,
90 queue: &wgpu::Queue,
91 data: &[I],
92 ) -> Option<(u64, u32)> {
93 if data.is_empty() {
94 return None;
95 }
96 let bytes = bytemuck::cast_slice(data);
97 self.ring.grow_to_fit(device, bytes.len() as u64);
98 let (off, wrote) = self.ring.alloc_write(queue, bytes);
99 debug_assert_eq!(wrote as usize, bytes.len());
100 Some((off, data.len() as u32))
101 }
102
103 fn reset(&mut self) {
104 self.ring.reset();
105 }
106}
107
108#[repr(C)]
109#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
110struct Globals {
111 ndc_to_px: [f32; 2],
112 _pad: [f32; 2],
113}
114
115fn make_globals(target_w: f32, target_h: f32) -> Globals {
116 Globals {
117 ndc_to_px: [target_w * 0.5, target_h * 0.5],
118 _pad: [0.0, 0.0],
119 }
120}
121
122pub struct WgpuSceneRenderer {
123 pub device: wgpu::Device,
124 pub queue: wgpu::Queue,
125 pub output_format: wgpu::TextureFormat,
126 pub output_width: u32,
127 pub output_height: u32,
128
129 surface_pipes: Pipelines,
132 layer_pipes: Pipelines,
133
134 rects: InstancedPipe<RectInstance>,
136 borders: InstancedPipe<BorderInstance>,
137 ellipses: InstancedPipe<EllipseInstance>,
138 ellipse_borders: InstancedPipe<EllipseBorderInstance>,
139 arcs: InstancedPipe<ArcInstance>,
140 glyph_mask: InstancedPipe<GlyphInstance>,
141 glyph_color: InstancedPipe<GlyphInstance>,
142
143 image_bind_layout_rgba: wgpu::BindGroupLayout,
145 image_bind_layout_nv12: wgpu::BindGroupLayout,
146 image_sampler: wgpu::Sampler,
147 layer_sampler: wgpu::Sampler,
148 layer_sampler_linear: wgpu::Sampler,
149
150 blur_ring: UploadRing,
152
153 text_bind_layout: wgpu::BindGroupLayout,
154
155 clip_ring: UploadRing,
157
158 slug_enabled: bool,
160 slug_ring: UploadRing,
161 slug_cache: slug::GlyphSlugCache,
162
163 nv12: InstancedPipe<Nv12Instance>,
165
166 mesh_verts: UploadRing,
168 mesh_indices: UploadRing,
169 mesh_uniform_buf: wgpu::Buffer,
170 mesh_bind_layout: wgpu::BindGroupLayout,
171 mesh_bind: wgpu::BindGroup,
172 mesh_uniform_head: u64,
173 mesh_clip_stack: Vec<(u64, u32, u64, u32, u64)>,
177
178 msaa_samples: u32,
179
180 depth_stencil_tex: wgpu::Texture,
182 depth_stencil_view: wgpu::TextureView,
183
184 msaa_tex: Option<wgpu::Texture>,
186 msaa_view: Option<wgpu::TextureView>,
187
188 globals_layout: wgpu::BindGroupLayout,
189 globals_buf: wgpu::Buffer,
190 globals_bind: wgpu::BindGroup,
191
192 atlas_mask: AtlasA8,
194 atlas_color: AtlasRGBA,
195
196 next_image_handle: u64,
198 images: HashMap<u64, ImageTex>,
199
200 frame_index: u64,
202 image_bytes_total: u64,
203 image_evict_after_frames: u64,
204 image_budget_bytes: u64,
205
206 layer_pool: HashMap<u32, LayerTarget>,
209
210 working_space: bool,
214 ws_tex: Option<wgpu::Texture>,
215 ws_view: Option<wgpu::TextureView>,
216 ws_bind: Option<wgpu::BindGroup>,
217 display_pipeline: Option<wgpu::RenderPipeline>,
218 display_layout: Option<wgpu::BindGroupLayout>,
219}
220
221pub struct WgpuSurfaceBackend {
222 pub surface: Option<wgpu::Surface<'static>>,
223 pub surface_config: Option<wgpu::SurfaceConfiguration>,
224 pub renderer: WgpuSceneRenderer,
225}
226
227impl std::ops::Deref for WgpuSurfaceBackend {
228 type Target = WgpuSceneRenderer;
229 fn deref(&self) -> &Self::Target { &self.renderer }
230}
231impl std::ops::DerefMut for WgpuSurfaceBackend {
232 fn deref_mut(&mut self) -> &mut Self::Target { &mut self.renderer }
233}
234
235#[cfg(feature = "winit-surface")]
236pub type WgpuBackend = WgpuSurfaceBackend;
237
238impl Drop for WgpuSceneRenderer {
239 fn drop(&mut self) {
240 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
241 }
242}
243
244#[derive(Clone)]
245struct LayerTarget {
246 texture: wgpu::Texture,
247 view: wgpu::TextureView,
248 bind: wgpu::BindGroup,
249 bind_linear: wgpu::BindGroup,
250 depth_stencil_tex: wgpu::Texture,
251 depth_stencil_view: wgpu::TextureView,
252 width: u32,
253 height: u32,
254 rect_px: (f32, f32, f32, f32),
255}
256
257#[derive(Clone, Copy)]
259enum PassTarget {
260 Surface,
261 Layer(u32),
262}
263
264struct Pipelines {
269 rects: wgpu::RenderPipeline,
270 borders: wgpu::RenderPipeline,
271 ellipses: wgpu::RenderPipeline,
272 ellipse_borders: wgpu::RenderPipeline,
273 arcs: wgpu::RenderPipeline,
274 text_mask: wgpu::RenderPipeline,
275 text_color: wgpu::RenderPipeline,
276 image_rgba: wgpu::RenderPipeline,
277 image_nv12: wgpu::RenderPipeline,
278 blur: wgpu::RenderPipeline,
279 blur_content: wgpu::RenderPipeline,
280 clip_a2c: wgpu::RenderPipeline,
281 clip_bin: wgpu::RenderPipeline,
282 clip_dec: wgpu::RenderPipeline,
283 slug: Option<wgpu::RenderPipeline>,
284 mesh: wgpu::RenderPipeline,
288 mesh_overlay: wgpu::RenderPipeline,
291 mesh_clip_inc: wgpu::RenderPipeline,
293 mesh_clip_dec: wgpu::RenderPipeline,
295}
296
297impl Pipelines {
298 fn create(
299 device: &wgpu::Device,
300 format: wgpu::TextureFormat,
301 sample_count: u32,
302 globals_layout: &wgpu::BindGroupLayout,
303 text_bind_layout: &wgpu::BindGroupLayout,
304 image_bind_layout_nv12: &wgpu::BindGroupLayout,
305 clip_pipeline_layout: &wgpu::PipelineLayout,
306 stencil_for_content: &wgpu::DepthStencilState,
307 stencil_for_clip_inc: &wgpu::DepthStencilState,
308 stencil_for_clip_dec: &wgpu::DepthStencilState,
309 clip_color_target: &wgpu::ColorTargetState,
310 clip_vertex_layout: &wgpu::VertexBufferLayout,
311 mesh_bind_layout: &wgpu::BindGroupLayout,
312 ) -> Self {
313 let msaa_state = wgpu::MultisampleState {
314 count: sample_count,
315 mask: !0,
316 alpha_to_coverage_enabled: false,
317 };
318
319 macro_rules! make_content_pipeline {
320 ($name:ident, $shader:literal, $inst_type:ty, $attrs:expr) => {
321 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
322 label: Some(concat!($shader, ".wgsl")),
323 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(concat!(
324 "shaders/", $shader, ".wgsl"
325 )))),
326 });
327 let pipeline_layout =
328 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
329 label: Some(concat!($shader, " pipeline layout")),
330 bind_group_layouts: &[Some(globals_layout)],
331 immediate_size: 0,
332 });
333 let $name = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
334 label: Some(concat!($shader, " pipeline")),
335 layout: Some(&pipeline_layout),
336 vertex: wgpu::VertexState {
337 module: &shader_module,
338 entry_point: Some("vs_main"),
339 buffers: &[Some(wgpu::VertexBufferLayout {
340 array_stride: std::mem::size_of::<$inst_type>() as u64,
341 step_mode: wgpu::VertexStepMode::Instance,
342 attributes: $attrs,
343 })],
344 compilation_options: wgpu::PipelineCompilationOptions::default(),
345 },
346 fragment: Some(wgpu::FragmentState {
347 module: &shader_module,
348 entry_point: Some("fs_main"),
349 targets: &[Some(wgpu::ColorTargetState {
350 format,
351 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
352 write_mask: wgpu::ColorWrites::ALL,
353 })],
354 compilation_options: wgpu::PipelineCompilationOptions::default(),
355 }),
356 primitive: wgpu::PrimitiveState::default(),
357 depth_stencil: Some(stencil_for_content.clone()),
358 multisample: msaa_state,
359 multiview_mask: None,
360 cache: None,
361 });
362 };
363 }
364
365 let rect_attrs: &[wgpu::VertexAttribute] = &[
366 wgpu::VertexAttribute {
367 shader_location: 0,
368 offset: 0,
369 format: wgpu::VertexFormat::Float32x4,
370 },
371 wgpu::VertexAttribute {
372 shader_location: 1,
373 offset: 16,
374 format: wgpu::VertexFormat::Float32x4,
375 },
376 wgpu::VertexAttribute {
377 shader_location: 2,
378 offset: 32,
379 format: wgpu::VertexFormat::Uint32,
380 },
381 wgpu::VertexAttribute {
382 shader_location: 3,
383 offset: 48,
384 format: wgpu::VertexFormat::Float32x4,
385 },
386 wgpu::VertexAttribute {
387 shader_location: 4,
388 offset: 64,
389 format: wgpu::VertexFormat::Float32x4,
390 },
391 wgpu::VertexAttribute {
392 shader_location: 5,
393 offset: 80,
394 format: wgpu::VertexFormat::Float32x2,
395 },
396 wgpu::VertexAttribute {
397 shader_location: 6,
398 offset: 88,
399 format: wgpu::VertexFormat::Float32x2,
400 },
401 wgpu::VertexAttribute {
402 shader_location: 7,
403 offset: 96,
404 format: wgpu::VertexFormat::Float32x2,
405 },
406 ];
407 let border_attrs: &[wgpu::VertexAttribute] = &[
408 wgpu::VertexAttribute {
409 shader_location: 0,
410 offset: 0,
411 format: wgpu::VertexFormat::Float32x4,
412 },
413 wgpu::VertexAttribute {
414 shader_location: 1,
415 offset: 16,
416 format: wgpu::VertexFormat::Float32x4,
417 },
418 wgpu::VertexAttribute {
419 shader_location: 2,
420 offset: 32,
421 format: wgpu::VertexFormat::Float32,
422 },
423 wgpu::VertexAttribute {
424 shader_location: 3,
425 offset: 36,
426 format: wgpu::VertexFormat::Float32x4,
427 },
428 wgpu::VertexAttribute {
429 shader_location: 4,
430 offset: 52,
431 format: wgpu::VertexFormat::Float32x2,
432 },
433 ];
434 let ellipse_attrs: &[wgpu::VertexAttribute] = &[
435 wgpu::VertexAttribute {
436 shader_location: 0,
437 offset: 0,
438 format: wgpu::VertexFormat::Float32x4,
439 },
440 wgpu::VertexAttribute {
441 shader_location: 1,
442 offset: 16,
443 format: wgpu::VertexFormat::Float32x4,
444 },
445 wgpu::VertexAttribute {
446 shader_location: 2,
447 offset: 32,
448 format: wgpu::VertexFormat::Float32x2,
449 },
450 ];
451 let ellipse_border_attrs: &[wgpu::VertexAttribute] = &[
452 wgpu::VertexAttribute {
453 shader_location: 0,
454 offset: 0,
455 format: wgpu::VertexFormat::Float32x4,
456 },
457 wgpu::VertexAttribute {
458 shader_location: 1,
459 offset: 16,
460 format: wgpu::VertexFormat::Float32,
461 },
462 wgpu::VertexAttribute {
463 shader_location: 2,
464 offset: 20,
465 format: wgpu::VertexFormat::Float32,
466 },
467 wgpu::VertexAttribute {
468 shader_location: 3,
469 offset: 24,
470 format: wgpu::VertexFormat::Float32x4,
471 },
472 wgpu::VertexAttribute {
473 shader_location: 4,
474 offset: 40,
475 format: wgpu::VertexFormat::Float32x2,
476 },
477 ];
478
479 make_content_pipeline!(rects, "rect", RectInstance, rect_attrs);
480 make_content_pipeline!(borders, "border", BorderInstance, border_attrs);
481 make_content_pipeline!(ellipses, "ellipse", EllipseInstance, ellipse_attrs);
482 make_content_pipeline!(
483 ellipse_borders,
484 "ellipse_border",
485 EllipseBorderInstance,
486 ellipse_border_attrs
487 );
488
489 let arc_attrs: &[wgpu::VertexAttribute] = &[
490 wgpu::VertexAttribute {
491 shader_location: 0,
492 offset: 0,
493 format: wgpu::VertexFormat::Float32x4,
494 },
495 wgpu::VertexAttribute {
496 shader_location: 1,
497 offset: 16,
498 format: wgpu::VertexFormat::Float32,
499 },
500 wgpu::VertexAttribute {
501 shader_location: 2,
502 offset: 20,
503 format: wgpu::VertexFormat::Float32,
504 },
505 wgpu::VertexAttribute {
506 shader_location: 3,
507 offset: 24,
508 format: wgpu::VertexFormat::Float32,
509 },
510 wgpu::VertexAttribute {
511 shader_location: 4,
512 offset: 28,
513 format: wgpu::VertexFormat::Float32,
514 },
515 wgpu::VertexAttribute {
516 shader_location: 5,
517 offset: 32,
518 format: wgpu::VertexFormat::Float32x4,
519 },
520 wgpu::VertexAttribute {
521 shader_location: 6,
522 offset: 48,
523 format: wgpu::VertexFormat::Float32x2,
524 },
525 wgpu::VertexAttribute {
526 shader_location: 7,
527 offset: 56,
528 format: wgpu::VertexFormat::Float32,
529 },
530 ];
531
532 make_content_pipeline!(arcs, "arc", ArcInstance, arc_attrs);
533
534 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
536 label: Some("text.wgsl"),
537 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
538 });
539 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
541 label: Some("text_color.wgsl"),
542 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
543 "shaders/text_color.wgsl"
544 ))),
545 });
546 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
547 label: Some("text pipeline layout"),
548 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
549 immediate_size: 0,
550 });
551 let glyph_vertex = wgpu::VertexBufferLayout {
552 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
553 step_mode: wgpu::VertexStepMode::Instance,
554 attributes: &[
555 wgpu::VertexAttribute {
556 shader_location: 0,
557 offset: 0,
558 format: wgpu::VertexFormat::Float32x4,
559 },
560 wgpu::VertexAttribute {
561 shader_location: 1,
562 offset: 16,
563 format: wgpu::VertexFormat::Float32x4,
564 },
565 wgpu::VertexAttribute {
566 shader_location: 2,
567 offset: 32,
568 format: wgpu::VertexFormat::Float32x4,
569 },
570 wgpu::VertexAttribute {
571 shader_location: 3,
572 offset: 48,
573 format: wgpu::VertexFormat::Float32x2,
574 },
575 ],
576 };
577 let text_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
578 label: Some("text pipeline (mask)"),
579 layout: Some(&text_pipeline_layout),
580 vertex: wgpu::VertexState {
581 module: &text_mask_shader,
582 entry_point: Some("vs_main"),
583 buffers: &[Some(glyph_vertex.clone())],
584 compilation_options: wgpu::PipelineCompilationOptions::default(),
585 },
586 fragment: Some(wgpu::FragmentState {
587 module: &text_mask_shader,
588 entry_point: Some("fs_main"),
589 targets: &[Some(wgpu::ColorTargetState {
590 format,
591 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
592 write_mask: wgpu::ColorWrites::ALL,
593 })],
594 compilation_options: wgpu::PipelineCompilationOptions::default(),
595 }),
596 primitive: wgpu::PrimitiveState::default(),
597 depth_stencil: Some(stencil_for_content.clone()),
598 multisample: msaa_state,
599 multiview_mask: None,
600 cache: None,
601 });
602 let text_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
603 label: Some("text pipeline (color)"),
604 layout: Some(&text_pipeline_layout),
605 vertex: wgpu::VertexState {
606 module: &text_color_shader,
607 entry_point: Some("vs_main"),
608 buffers: &[Some(glyph_vertex)],
609 compilation_options: wgpu::PipelineCompilationOptions::default(),
610 },
611 fragment: Some(wgpu::FragmentState {
612 module: &text_color_shader,
613 entry_point: Some("fs_main"),
614 targets: &[Some(wgpu::ColorTargetState {
615 format,
616 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
617 write_mask: wgpu::ColorWrites::ALL,
618 })],
619 compilation_options: wgpu::PipelineCompilationOptions::default(),
620 }),
621 primitive: wgpu::PrimitiveState::default(),
622 depth_stencil: Some(stencil_for_content.clone()),
623 multisample: msaa_state,
624 multiview_mask: None,
625 cache: None,
626 });
627 let image_rgba = text_color.clone();
629
630 let blur_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
632 label: Some("blur_shadow.wgsl"),
633 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
634 "shaders/blur_shadow.wgsl"
635 ))),
636 });
637 let blur_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
638 label: Some("blur pipeline layout"),
639 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
640 immediate_size: 0,
641 });
642 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
643 label: Some("blur pipeline"),
644 layout: Some(&blur_pipeline_layout),
645 vertex: wgpu::VertexState {
646 module: &blur_shader,
647 entry_point: Some("vs_main"),
648 buffers: &[Some(wgpu::VertexBufferLayout {
649 array_stride: std::mem::size_of::<BlurInstance>() as u64,
650 step_mode: wgpu::VertexStepMode::Instance,
651 attributes: &[
652 wgpu::VertexAttribute {
653 shader_location: 0,
654 offset: 0,
655 format: wgpu::VertexFormat::Float32x4,
656 },
657 wgpu::VertexAttribute {
658 shader_location: 1,
659 offset: 16,
660 format: wgpu::VertexFormat::Float32x4,
661 },
662 wgpu::VertexAttribute {
663 shader_location: 2,
664 offset: 32,
665 format: wgpu::VertexFormat::Float32x4,
666 },
667 wgpu::VertexAttribute {
668 shader_location: 3,
669 offset: 48,
670 format: wgpu::VertexFormat::Float32x2,
671 },
672 wgpu::VertexAttribute {
673 shader_location: 4,
674 offset: 56,
675 format: wgpu::VertexFormat::Float32x2,
676 },
677 ],
678 })],
679 compilation_options: wgpu::PipelineCompilationOptions::default(),
680 },
681 fragment: Some(wgpu::FragmentState {
682 module: &blur_shader,
683 entry_point: Some("fs_main"),
684 targets: &[Some(wgpu::ColorTargetState {
685 format,
686 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
687 write_mask: wgpu::ColorWrites::ALL,
688 })],
689 compilation_options: wgpu::PipelineCompilationOptions::default(),
690 }),
691 primitive: wgpu::PrimitiveState::default(),
692 depth_stencil: Some(stencil_for_content.clone()),
693 multisample: msaa_state,
694 multiview_mask: None,
695 cache: None,
696 });
697
698 let blur_content_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
700 label: Some("blur_content.wgsl"),
701 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
702 "shaders/blur_content.wgsl"
703 ))),
704 });
705 let blur_content = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
706 label: Some("blur content pipeline"),
707 layout: Some(&blur_pipeline_layout),
708 vertex: wgpu::VertexState {
709 module: &blur_content_shader,
710 entry_point: Some("vs_main"),
711 buffers: &[Some(wgpu::VertexBufferLayout {
712 array_stride: std::mem::size_of::<BlurInstance>() as u64,
713 step_mode: wgpu::VertexStepMode::Instance,
714 attributes: &[
715 wgpu::VertexAttribute {
716 shader_location: 0,
717 offset: 0,
718 format: wgpu::VertexFormat::Float32x4,
719 },
720 wgpu::VertexAttribute {
721 shader_location: 1,
722 offset: 16,
723 format: wgpu::VertexFormat::Float32x4,
724 },
725 wgpu::VertexAttribute {
726 shader_location: 2,
727 offset: 32,
728 format: wgpu::VertexFormat::Float32x4,
729 },
730 wgpu::VertexAttribute {
731 shader_location: 3,
732 offset: 48,
733 format: wgpu::VertexFormat::Float32x2,
734 },
735 wgpu::VertexAttribute {
736 shader_location: 4,
737 offset: 56,
738 format: wgpu::VertexFormat::Float32x2,
739 },
740 ],
741 })],
742 compilation_options: wgpu::PipelineCompilationOptions::default(),
743 },
744 fragment: Some(wgpu::FragmentState {
745 module: &blur_content_shader,
746 entry_point: Some("fs_main"),
747 targets: &[Some(wgpu::ColorTargetState {
748 format,
749 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
750 write_mask: wgpu::ColorWrites::ALL,
751 })],
752 compilation_options: wgpu::PipelineCompilationOptions::default(),
753 }),
754 primitive: wgpu::PrimitiveState::default(),
755 depth_stencil: Some(stencil_for_content.clone()),
756 multisample: msaa_state,
757 multiview_mask: None,
758 cache: None,
759 });
760
761 let image_nv12_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
763 label: Some("image_nv12.wgsl"),
764 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
765 "shaders/image_nv12.wgsl"
766 ))),
767 });
768 let image_nv12_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
769 label: Some("image nv12 pipeline layout"),
770 bind_group_layouts: &[Some(globals_layout), Some(image_bind_layout_nv12)],
771 immediate_size: 0,
772 });
773 let image_nv12 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
774 label: Some("image nv12 pipeline"),
775 layout: Some(&image_nv12_layout),
776 vertex: wgpu::VertexState {
777 module: &image_nv12_shader,
778 entry_point: Some("vs_main"),
779 buffers: &[Some(wgpu::VertexBufferLayout {
780 array_stride: std::mem::size_of::<Nv12Instance>() as u64,
781 step_mode: wgpu::VertexStepMode::Instance,
782 attributes: &[
783 wgpu::VertexAttribute {
784 shader_location: 0,
785 offset: 0,
786 format: wgpu::VertexFormat::Float32x4,
787 },
788 wgpu::VertexAttribute {
789 shader_location: 1,
790 offset: 16,
791 format: wgpu::VertexFormat::Float32x4,
792 },
793 wgpu::VertexAttribute {
794 shader_location: 2,
795 offset: 32,
796 format: wgpu::VertexFormat::Float32x4,
797 },
798 wgpu::VertexAttribute {
799 shader_location: 3,
800 offset: 48,
801 format: wgpu::VertexFormat::Float32,
802 },
803 wgpu::VertexAttribute {
804 shader_location: 4,
805 offset: 52,
806 format: wgpu::VertexFormat::Float32x2,
807 },
808 ],
809 })],
810 compilation_options: wgpu::PipelineCompilationOptions::default(),
811 },
812 fragment: Some(wgpu::FragmentState {
813 module: &image_nv12_shader,
814 entry_point: Some("fs_main"),
815 targets: &[Some(wgpu::ColorTargetState {
816 format,
817 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
818 write_mask: wgpu::ColorWrites::ALL,
819 })],
820 compilation_options: wgpu::PipelineCompilationOptions::default(),
821 }),
822 primitive: wgpu::PrimitiveState::default(),
823 depth_stencil: Some(stencil_for_content.clone()),
824 multisample: msaa_state,
825 multiview_mask: None,
826 cache: None,
827 });
828
829 let clip_shader_a2c = device.create_shader_module(wgpu::ShaderModuleDescriptor {
831 label: Some("clip_round_rect_a2c.wgsl"),
832 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
833 "shaders/clip_round_rect_a2c.wgsl"
834 ))),
835 });
836 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
837 label: Some("clip_round_rect_bin.wgsl"),
838 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
839 "shaders/clip_round_rect_bin.wgsl"
840 ))),
841 });
842 let clip_a2c = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
843 label: Some("clip pipeline (a2c)"),
844 layout: Some(clip_pipeline_layout),
845 vertex: wgpu::VertexState {
846 module: &clip_shader_a2c,
847 entry_point: Some("vs_main"),
848 buffers: &[Some(clip_vertex_layout.clone())],
849 compilation_options: wgpu::PipelineCompilationOptions::default(),
850 },
851 fragment: Some(wgpu::FragmentState {
852 module: &clip_shader_a2c,
853 entry_point: Some("fs_main"),
854 targets: &[Some(clip_color_target.clone())],
855 compilation_options: wgpu::PipelineCompilationOptions::default(),
856 }),
857 primitive: wgpu::PrimitiveState::default(),
858 depth_stencil: Some(stencil_for_clip_inc.clone()),
859 multisample: wgpu::MultisampleState {
860 count: sample_count,
861 mask: !0,
862 alpha_to_coverage_enabled: sample_count > 1,
863 },
864 multiview_mask: None,
865 cache: None,
866 });
867 let clip_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
868 label: Some("clip pipeline (bin)"),
869 layout: Some(clip_pipeline_layout),
870 vertex: wgpu::VertexState {
871 module: &clip_shader_bin,
872 entry_point: Some("vs_main"),
873 buffers: &[Some(clip_vertex_layout.clone())],
874 compilation_options: wgpu::PipelineCompilationOptions::default(),
875 },
876 fragment: Some(wgpu::FragmentState {
877 module: &clip_shader_bin,
878 entry_point: Some("fs_main"),
879 targets: &[Some(clip_color_target.clone())],
880 compilation_options: wgpu::PipelineCompilationOptions::default(),
881 }),
882 primitive: wgpu::PrimitiveState::default(),
883 depth_stencil: Some(stencil_for_clip_inc.clone()),
884 multisample: wgpu::MultisampleState {
885 count: sample_count,
886 mask: !0,
887 alpha_to_coverage_enabled: false,
888 },
889 multiview_mask: None,
890 cache: None,
891 });
892 let clip_dec = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
893 label: Some("clip pipeline (dec)"),
894 layout: Some(clip_pipeline_layout),
895 vertex: wgpu::VertexState {
896 module: &clip_shader_bin,
897 entry_point: Some("vs_main"),
898 buffers: &[Some(clip_vertex_layout.clone())],
899 compilation_options: wgpu::PipelineCompilationOptions::default(),
900 },
901 fragment: Some(wgpu::FragmentState {
902 module: &clip_shader_bin,
903 entry_point: Some("fs_main"),
904 targets: &[Some(clip_color_target.clone())],
905 compilation_options: wgpu::PipelineCompilationOptions::default(),
906 }),
907 primitive: wgpu::PrimitiveState::default(),
908 depth_stencil: Some(stencil_for_clip_dec.clone()),
909 multisample: wgpu::MultisampleState {
910 count: sample_count,
911 mask: !0,
912 alpha_to_coverage_enabled: false,
913 },
914 multiview_mask: None,
915 cache: None,
916 });
917
918 let slug = Some(slug::create_pipeline(
919 device,
920 format,
921 sample_count,
922 stencil_for_content,
923 ));
924
925 let mesh_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
927 label: Some("mesh.wgsl"),
928 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/mesh.wgsl"))),
929 });
930 let mesh_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
931 label: Some("mesh pipeline layout"),
932 bind_group_layouts: &[Some(globals_layout), Some(mesh_bind_layout)],
933 immediate_size: 0,
934 });
935 let mesh_vertex_layout = wgpu::VertexBufferLayout {
936 array_stride: std::mem::size_of::<MeshVertex>() as u64,
937 step_mode: wgpu::VertexStepMode::Vertex,
938 attributes: &[
939 wgpu::VertexAttribute {
940 shader_location: 0,
941 offset: 0,
942 format: wgpu::VertexFormat::Float32x2,
943 },
944 wgpu::VertexAttribute {
945 shader_location: 1,
946 offset: 8,
947 format: wgpu::VertexFormat::Float32x4,
948 },
949 wgpu::VertexAttribute {
950 shader_location: 2,
951 offset: 24,
952 format: wgpu::VertexFormat::Float32x2,
953 },
954 ],
955 };
956 let make_mesh_pipeline = |label: &str, depth: &wgpu::DepthStencilState, color: &wgpu::ColorTargetState| {
957 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
958 label: Some(label),
959 layout: Some(&mesh_pipeline_layout),
960 vertex: wgpu::VertexState {
961 module: &mesh_shader,
962 entry_point: Some("vs_main"),
963 buffers: &[Some(mesh_vertex_layout.clone())],
964 compilation_options: wgpu::PipelineCompilationOptions::default(),
965 },
966 fragment: Some(wgpu::FragmentState {
967 module: &mesh_shader,
968 entry_point: Some("fs_main"),
969 targets: &[Some(color.clone())],
970 compilation_options: wgpu::PipelineCompilationOptions::default(),
971 }),
972 primitive: wgpu::PrimitiveState {
973 topology: wgpu::PrimitiveTopology::TriangleList,
974 ..Default::default()
975 },
976 depth_stencil: Some(depth.clone()),
977 multisample: msaa_state,
978 multiview_mask: None,
979 cache: None,
980 })
981 };
982 let mesh_color_target = wgpu::ColorTargetState {
983 format,
984 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
985 write_mask: wgpu::ColorWrites::ALL,
986 };
987 let mut stencil_for_mesh = stencil_for_content.clone();
988 stencil_for_mesh.stencil.front.compare = wgpu::CompareFunction::Equal;
989 stencil_for_mesh.stencil.back.compare = wgpu::CompareFunction::Equal;
990 let mesh = make_mesh_pipeline("mesh pipeline", &stencil_for_mesh, &mesh_color_target);
991 let mesh_overlay = make_mesh_pipeline("mesh overlay pipeline", stencil_for_content, &mesh_color_target);
992 let mesh_clip_inc = make_mesh_pipeline("mesh clip (inc) pipeline", stencil_for_clip_inc, clip_color_target);
993 let mesh_clip_dec = make_mesh_pipeline("mesh clip (dec) pipeline", stencil_for_clip_dec, clip_color_target);
994
995 Self {
996 rects,
997 borders,
998 ellipses,
999 ellipse_borders,
1000 arcs,
1001 text_mask,
1002 text_color,
1003 image_rgba,
1004 image_nv12,
1005 blur,
1006 blur_content,
1007 clip_a2c,
1008 clip_bin,
1009 clip_dec,
1010 slug,
1011 mesh,
1012 mesh_overlay,
1013 mesh_clip_inc,
1014 mesh_clip_dec,
1015 }
1016 }
1017}
1018
1019struct Pass {
1021 target: PassTarget,
1022 initial_scissor: (u32, u32, u32, u32),
1024 clear_color: Option<[f32; 4]>,
1027 cmds: Vec<Cmd>,
1028}
1029
1030#[allow(non_snake_case)]
1031enum Cmd {
1032 ClipPush {
1033 off: u64,
1034 cnt: u32,
1035 scissor: (u32, u32, u32, u32),
1036 difference: bool,
1037 rounded: bool,
1038 },
1039 ClipPop {
1040 scissor: (u32, u32, u32, u32),
1041 },
1042 Rect {
1043 off: u64,
1044 cnt: u32,
1045 },
1046 Border {
1047 off: u64,
1048 cnt: u32,
1049 },
1050 Ellipse {
1051 off: u64,
1052 cnt: u32,
1053 },
1054 EllipseBorder {
1055 off: u64,
1056 cnt: u32,
1057 },
1058 Arc {
1059 off: u64,
1060 cnt: u32,
1061 },
1062 GlyphsMask {
1063 off: u64,
1064 cnt: u32,
1065 },
1066 GlyphsColor {
1067 off: u64,
1068 cnt: u32,
1069 },
1070 GlyphsVector {
1071 off: u64,
1072 cnt: u32,
1073 },
1074 ImageRgba {
1075 off: u64,
1076 cnt: u32,
1077 handle: u64,
1078 },
1079 ImageNv12 {
1080 off: u64,
1081 cnt: u32,
1082 handle: u64,
1083 },
1084 PushTransform(Transform),
1085 PopTransform,
1086 CompositeLayer {
1090 off: u64,
1091 cnt: u32,
1092 layer_id: u32,
1093 alpha: f32,
1094 },
1095 CompositeShadow {
1099 off: u64,
1100 cnt: u32,
1101 layer_id: u32,
1102 },
1103 CompositeBlur {
1106 off: u64,
1107 cnt: u32,
1108 layer_id: u32,
1109 },
1110 VectorMesh {
1112 voff: u64,
1113 vcnt: u32,
1114 ioff: u64,
1115 icnt: u32,
1116 uoff: u64,
1117 },
1118 VectorOverlay {
1120 voff: u64,
1121 vcnt: u32,
1122 ioff: u64,
1123 icnt: u32,
1124 uoff: u64,
1125 },
1126 VectorClipPush {
1128 voff: u64,
1129 vcnt: u32,
1130 ioff: u64,
1131 icnt: u32,
1132 uoff: u64,
1133 scissor: (u32, u32, u32, u32),
1134 },
1135 VectorClipPop {
1137 voff: u64,
1138 vcnt: u32,
1139 ioff: u64,
1140 icnt: u32,
1141 uoff: u64,
1142 scissor: (u32, u32, u32, u32),
1143 },
1144}
1145
1146enum ImageTex {
1147 Rgba {
1148 tex: wgpu::Texture,
1149 view: wgpu::TextureView,
1150 bind: wgpu::BindGroup,
1151 w: u32,
1152 h: u32,
1153 format: wgpu::TextureFormat,
1154 last_used_frame: u64,
1155 bytes: u64,
1156 },
1157 Nv12 {
1158 tex_y: wgpu::Texture,
1159 view_y: wgpu::TextureView,
1160 tex_uv: wgpu::Texture,
1161 view_uv: wgpu::TextureView,
1162 bind: wgpu::BindGroup,
1163 yuv_buf: wgpu::Buffer,
1164 w: u32,
1165 h: u32,
1166 color_info: ColorInfo,
1167 last_used_frame: u64,
1168 bytes: u64,
1169 },
1170}
1171
1172struct AtlasA8 {
1173 tex: wgpu::Texture,
1174 view: wgpu::TextureView,
1175 sampler: wgpu::Sampler,
1176 size: u32,
1177 next_x: u32,
1178 next_y: u32,
1179 row_h: u32,
1180 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
1181}
1182
1183struct AtlasRGBA {
1184 tex: wgpu::Texture,
1185 view: wgpu::TextureView,
1186 sampler: wgpu::Sampler,
1187 size: u32,
1188 next_x: u32,
1189 next_y: u32,
1190 row_h: u32,
1191 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
1192}
1193
1194#[derive(Clone, Copy)]
1195struct GlyphInfo {
1196 u0: f32,
1197 v0: f32,
1198 u1: f32,
1199 v1: f32,
1200 w: f32,
1201 h: f32,
1202 bearing_x: f32,
1203 bearing_y: f32,
1204 advance: f32,
1205}
1206
1207#[repr(C)]
1208#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1209struct RectInstance {
1210 xywh: [f32; 4],
1211 radii: [f32; 4],
1212 brush_type: u32,
1213 _pad: [f32; 3],
1214 color0: [f32; 4],
1215 color1: [f32; 4],
1216 grad_start: [f32; 2],
1217 grad_end: [f32; 2],
1218 sin_cos: [f32; 2],
1219}
1220
1221#[repr(C)]
1222#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1223struct BorderInstance {
1224 xywh: [f32; 4],
1225 radii: [f32; 4],
1226 stroke: f32,
1227 color: [f32; 4],
1228 sin_cos: [f32; 2],
1229}
1230
1231#[repr(C)]
1232#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1233struct EllipseInstance {
1234 xywh: [f32; 4],
1235 color: [f32; 4],
1236 sin_cos: [f32; 2],
1237}
1238
1239#[repr(C)]
1240#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1241struct EllipseBorderInstance {
1242 xywh: [f32; 4],
1243 stroke: f32,
1244 pad: f32,
1245 color: [f32; 4],
1246 sin_cos: [f32; 2],
1247}
1248
1249#[repr(C)]
1250#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1251struct ArcInstance {
1252 xywh: [f32; 4],
1253 start_angle: f32,
1254 sweep_angle: f32,
1255 stroke: f32,
1256 pad: f32,
1257 color: [f32; 4],
1258 sin_cos: [f32; 2],
1259 cap: f32, }
1261
1262#[repr(C)]
1263#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1264struct GlyphInstance {
1265 xywh: [f32; 4],
1266 uv: [f32; 4],
1267 color: [f32; 4],
1268 sin_cos: [f32; 2],
1269}
1270
1271#[repr(C)]
1272#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1273struct BlurInstance {
1274 xywh: [f32; 4],
1275 uv: [f32; 4],
1276 color: [f32; 4],
1277 blur_uv: [f32; 2],
1278 sin_cos: [f32; 2],
1279}
1280
1281#[repr(C)]
1284#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1285struct YuvTransformRaw {
1286 row0: [f32; 4],
1287 row1: [f32; 4],
1288 row2: [f32; 4],
1289 b: [f32; 4],
1290}
1291
1292#[repr(C)]
1293#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1294struct Nv12Instance {
1295 xywh: [f32; 4],
1296 uv: [f32; 4],
1297 color: [f32; 4], uv_x_offset: f32,
1299 sin_cos: [f32; 2],
1300 _pad: [f32; 1],
1301}
1302
1303#[repr(C)]
1304#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1305struct ClipInstance {
1306 xywh: [f32; 4],
1307 radii: [f32; 4],
1308 sin_cos: [f32; 2],
1309}
1310
1311#[repr(C)]
1312#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1313struct MeshVertex {
1314 pos: [f32; 2],
1315 color: [f32; 4],
1316 uv: [f32; 2],
1317}
1318
1319#[repr(C)]
1320#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1321struct MeshUniform {
1322 m0: [f32; 4],
1323 m1: [f32; 4],
1324 paint: [u32; 4],
1325 color0: [f32; 4],
1326 color1: [f32; 4],
1327 grad_start: [f32; 2],
1328 _p3: [f32; 2],
1329 grad_end: [f32; 2],
1330 _p4: [f32; 2],
1331}
1332
1333const MESH_UNIFORM_SLOT: u64 = 256;
1335const MESH_UNIFORM_CAP: u64 = 4 * 1024 * 1024;
1336
1337impl MeshUniform {
1338 fn identity() -> Self {
1339 Self {
1340 m0: [1.0, 0.0, 0.0, 0.0],
1341 m1: [0.0, 1.0, 0.0, 0.0],
1342 paint: [0; 4],
1343 color0: [0.0; 4],
1344 color1: [0.0; 4],
1345 grad_start: [0.0; 2],
1346 _p3: [0.0; 2],
1347 grad_end: [0.0; 2],
1348 _p4: [0.0; 2],
1349 }
1350 }
1351}
1352
1353fn mesh_uniform_from_paint(affine: [f32; 6], paint: &repose_core::PaintDesc) -> MeshUniform {
1354 let (paint_type, color0, color1, grad_start, grad_end) = match paint {
1355 repose_core::PaintDesc::Solid => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
1356 repose_core::PaintDesc::Linear {
1357 start,
1358 end,
1359 start_color,
1360 end_color,
1361 } => (
1362 1u32,
1363 start_color.to_linear(),
1364 end_color.to_linear(),
1365 [start.x, start.y],
1366 [end.x, end.y],
1367 ),
1368 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
1370 };
1371 MeshUniform {
1372 m0: [affine[0], affine[1], affine[2], 0.0],
1373 m1: [affine[3], affine[4], affine[5], 0.0],
1374 paint: [paint_type, 0, 0, 0],
1375 color0,
1376 color1,
1377 grad_start,
1378 _p3: [0.0; 2],
1379 grad_end,
1380 _p4: [0.0; 2],
1381 }
1382}
1383
1384fn combine_mesh_affine(current: &Transform, mesh: [f32; 6]) -> [f32; 6] {
1385 let cos_a = current.rotate.cos();
1386 let sin_a = current.rotate.sin();
1387 let cm00 = current.scale_x * cos_a;
1389 let cm01 = -current.scale_y * sin_a;
1390 let cm10 = current.scale_x * sin_a;
1391 let cm11 = current.scale_y * cos_a;
1392 let mm00 = mesh[0];
1394 let mm01 = mesh[1];
1395 let mm10 = mesh[2];
1396 let mm11 = mesh[3];
1397 let mtx = mesh[4];
1398 let mty = mesh[5];
1399 let r00 = cm00 * mm00 + cm01 * mm10;
1400 let r01 = cm00 * mm01 + cm01 * mm11;
1401 let r10 = cm10 * mm00 + cm11 * mm10;
1402 let r11 = cm10 * mm01 + cm11 * mm11;
1403 let tx = cm00 * mtx + cm01 * mty + current.translate_x;
1404 let ty = cm10 * mtx + cm11 * mty + current.translate_y;
1405 [r00, r01, tx, r10, r11, ty]
1408}
1409
1410fn mesh_aabb(mesh: &repose_core::VectorMeshData, affine: [f32; 6]) -> repose_core::Rect {
1411 let mut min_x = f32::MAX;
1412 let mut min_y = f32::MAX;
1413 let mut max_x = f32::MIN;
1414 let mut max_y = f32::MIN;
1415 for v in mesh.vertices.iter() {
1416 let x = affine[0] * v.pos[0] + affine[1] * v.pos[1] + affine[2];
1417 let y = affine[3] * v.pos[0] + affine[4] * v.pos[1] + affine[5];
1418 min_x = min_x.min(x);
1419 min_y = min_y.min(y);
1420 max_x = max_x.max(x);
1421 max_y = max_y.max(y);
1422 }
1423 let w = (max_x - min_x).max(0.0);
1424 let h = (max_y - min_y).max(0.0);
1425 if !min_x.is_finite() || !min_y.is_finite() {
1426 return repose_core::Rect {
1427 x: 0.0,
1428 y: 0.0,
1429 w: 0.0,
1430 h: 0.0,
1431 };
1432 }
1433 repose_core::Rect {
1434 x: min_x,
1435 y: min_y,
1436 w,
1437 h,
1438 }
1439}
1440
1441fn swash_to_a8_coverage(content: repose_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
1442 match content {
1443 repose_text::SwashContent::Mask => Some(data.to_vec()),
1444 repose_text::SwashContent::SubpixelMask => {
1445 let mut out = Vec::with_capacity(data.len() / 4);
1446 for px in data.chunks_exact(4) {
1447 let r = px[0];
1448 let g = px[1];
1449 let b = px[2];
1450 out.push(r.max(g).max(b));
1451 }
1452 Some(out)
1453 }
1454 repose_text::SwashContent::Color => None,
1455 }
1456}
1457
1458impl WgpuSceneRenderer {
1459 pub fn from_device(
1460 device: wgpu::Device,
1461 queue: wgpu::Queue,
1462 output_format: wgpu::TextureFormat,
1463 msaa_samples: u32,
1464 ) -> Self {
1465 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1466 label: Some("globals layout"),
1467 entries: &[wgpu::BindGroupLayoutEntry {
1468 binding: 0,
1469 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1470 ty: wgpu::BindingType::Buffer {
1471 ty: wgpu::BufferBindingType::Uniform,
1472 has_dynamic_offset: false,
1473 min_binding_size: None,
1474 },
1475 count: None,
1476 }],
1477 });
1478
1479 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
1480 label: Some("globals buf"),
1481 size: std::mem::size_of::<Globals>() as u64,
1482 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1483 mapped_at_creation: false,
1484 });
1485
1486 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1487 label: Some("globals bind"),
1488 layout: &globals_layout,
1489 entries: &[wgpu::BindGroupEntry {
1490 binding: 0,
1491 resource: globals_buf.as_entire_binding(),
1492 }],
1493 });
1494
1495
1496 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
1497
1498 let stencil_for_content = wgpu::DepthStencilState {
1499 format: ds_format,
1500 depth_write_enabled: Some(false),
1501 depth_compare: Some(wgpu::CompareFunction::Always),
1502 stencil: wgpu::StencilState {
1503 front: wgpu::StencilFaceState {
1504 compare: wgpu::CompareFunction::LessEqual,
1505 fail_op: wgpu::StencilOperation::Keep,
1506 depth_fail_op: wgpu::StencilOperation::Keep,
1507 pass_op: wgpu::StencilOperation::Keep,
1508 },
1509 back: wgpu::StencilFaceState {
1510 compare: wgpu::CompareFunction::LessEqual,
1511 fail_op: wgpu::StencilOperation::Keep,
1512 depth_fail_op: wgpu::StencilOperation::Keep,
1513 pass_op: wgpu::StencilOperation::Keep,
1514 },
1515 read_mask: 0xFF,
1516 write_mask: 0x00,
1517 },
1518 bias: wgpu::DepthBiasState::default(),
1519 };
1520
1521 let stencil_for_clip_inc = wgpu::DepthStencilState {
1522 format: ds_format,
1523 depth_write_enabled: Some(false),
1524 depth_compare: Some(wgpu::CompareFunction::Always),
1525 stencil: wgpu::StencilState {
1526 front: wgpu::StencilFaceState {
1527 compare: wgpu::CompareFunction::Equal,
1528 fail_op: wgpu::StencilOperation::Keep,
1529 depth_fail_op: wgpu::StencilOperation::Keep,
1530 pass_op: wgpu::StencilOperation::IncrementClamp,
1531 },
1532 back: wgpu::StencilFaceState {
1533 compare: wgpu::CompareFunction::Equal,
1534 fail_op: wgpu::StencilOperation::Keep,
1535 depth_fail_op: wgpu::StencilOperation::Keep,
1536 pass_op: wgpu::StencilOperation::IncrementClamp,
1537 },
1538 read_mask: 0xFF,
1539 write_mask: 0xFF,
1540 },
1541 bias: wgpu::DepthBiasState::default(),
1542 };
1543
1544 let stencil_for_clip_dec = wgpu::DepthStencilState {
1545 format: ds_format,
1546 depth_write_enabled: Some(false),
1547 depth_compare: Some(wgpu::CompareFunction::Always),
1548 stencil: wgpu::StencilState {
1549 front: wgpu::StencilFaceState {
1550 compare: wgpu::CompareFunction::Equal,
1551 fail_op: wgpu::StencilOperation::Keep,
1552 depth_fail_op: wgpu::StencilOperation::Keep,
1553 pass_op: wgpu::StencilOperation::DecrementClamp,
1554 },
1555 back: wgpu::StencilFaceState {
1556 compare: wgpu::CompareFunction::Equal,
1557 fail_op: wgpu::StencilOperation::Keep,
1558 depth_fail_op: wgpu::StencilOperation::Keep,
1559 pass_op: wgpu::StencilOperation::DecrementClamp,
1560 },
1561 read_mask: 0xFF,
1562 write_mask: 0xFF,
1563 },
1564 bias: wgpu::DepthBiasState::default(),
1565 };
1566
1567 let _multisample_state = wgpu::MultisampleState {
1568 count: msaa_samples,
1569 mask: !0,
1570 alpha_to_coverage_enabled: false,
1571 };
1572
1573 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1577 label: Some("image/text sampler"),
1578 address_mode_u: wgpu::AddressMode::ClampToEdge,
1579 address_mode_v: wgpu::AddressMode::ClampToEdge,
1580 mag_filter: wgpu::FilterMode::Linear,
1581 min_filter: wgpu::FilterMode::Linear,
1582 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1583 ..Default::default()
1584 });
1585
1586 let layer_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1588 label: Some("layer nearest sampler"),
1589 address_mode_u: wgpu::AddressMode::ClampToEdge,
1590 address_mode_v: wgpu::AddressMode::ClampToEdge,
1591 mag_filter: wgpu::FilterMode::Nearest,
1592 min_filter: wgpu::FilterMode::Nearest,
1593 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
1594 ..Default::default()
1595 });
1596
1597 let layer_sampler_linear = device.create_sampler(&wgpu::SamplerDescriptor {
1600 label: Some("layer linear sampler"),
1601 address_mode_u: wgpu::AddressMode::ClampToEdge,
1602 address_mode_v: wgpu::AddressMode::ClampToEdge,
1603 mag_filter: wgpu::FilterMode::Linear,
1604 min_filter: wgpu::FilterMode::Linear,
1605 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1606 ..Default::default()
1607 });
1608
1609 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1611 label: Some("text/rgba bind layout"),
1612 entries: &[
1613 wgpu::BindGroupLayoutEntry {
1614 binding: 0,
1615 visibility: wgpu::ShaderStages::FRAGMENT,
1616 ty: wgpu::BindingType::Texture {
1617 multisampled: false,
1618 view_dimension: wgpu::TextureViewDimension::D2,
1619 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1620 },
1621 count: None,
1622 },
1623 wgpu::BindGroupLayoutEntry {
1624 binding: 1,
1625 visibility: wgpu::ShaderStages::FRAGMENT,
1626 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1627 count: None,
1628 },
1629 ],
1630 });
1631 let image_bind_layout_rgba = text_bind_layout.clone();
1633
1634 let image_bind_layout_nv12 =
1636 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1637 label: Some("image bind layout nv12"),
1638 entries: &[
1639 wgpu::BindGroupLayoutEntry {
1641 binding: 0,
1642 visibility: wgpu::ShaderStages::FRAGMENT,
1643 ty: wgpu::BindingType::Texture {
1644 multisampled: false,
1645 view_dimension: wgpu::TextureViewDimension::D2,
1646 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1647 },
1648 count: None,
1649 },
1650 wgpu::BindGroupLayoutEntry {
1652 binding: 1,
1653 visibility: wgpu::ShaderStages::FRAGMENT,
1654 ty: wgpu::BindingType::Texture {
1655 multisampled: false,
1656 view_dimension: wgpu::TextureViewDimension::D2,
1657 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1658 },
1659 count: None,
1660 },
1661 wgpu::BindGroupLayoutEntry {
1663 binding: 2,
1664 visibility: wgpu::ShaderStages::FRAGMENT,
1665 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1666 count: None,
1667 },
1668 wgpu::BindGroupLayoutEntry {
1670 binding: 3,
1671 visibility: wgpu::ShaderStages::FRAGMENT,
1672 ty: wgpu::BindingType::Buffer {
1673 ty: wgpu::BufferBindingType::Uniform,
1674 has_dynamic_offset: false,
1675 min_binding_size: None,
1676 },
1677 count: None,
1678 },
1679 ],
1680 });
1681
1682 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1684 label: Some("clip pipeline layout"),
1685 bind_group_layouts: &[Some(&globals_layout)],
1686 immediate_size: 0,
1687 });
1688 let clip_vertex_layout = wgpu::VertexBufferLayout {
1689 array_stride: std::mem::size_of::<ClipInstance>() as u64,
1690 step_mode: wgpu::VertexStepMode::Instance,
1691 attributes: &[
1692 wgpu::VertexAttribute {
1693 shader_location: 0,
1694 offset: 0,
1695 format: wgpu::VertexFormat::Float32x4,
1696 },
1697 wgpu::VertexAttribute {
1698 shader_location: 1,
1699 offset: 16,
1700 format: wgpu::VertexFormat::Float32x4,
1701 },
1702 wgpu::VertexAttribute {
1703 shader_location: 2,
1704 offset: 32,
1705 format: wgpu::VertexFormat::Float32x2,
1706 },
1707 ],
1708 };
1709 let clip_color_target = wgpu::ColorTargetState {
1710 format: output_format,
1711 blend: None,
1712 write_mask: wgpu::ColorWrites::empty(),
1713 };
1714
1715 let mesh_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1717 label: Some("mesh uniform layout"),
1718 entries: &[wgpu::BindGroupLayoutEntry {
1719 binding: 0,
1720 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1721 ty: wgpu::BindingType::Buffer {
1722 ty: wgpu::BufferBindingType::Uniform,
1723 has_dynamic_offset: true,
1724 min_binding_size: NonZero::new(MESH_UNIFORM_SLOT),
1725 },
1726 count: None,
1727 }],
1728 });
1729 let mesh_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
1730 label: Some("mesh uniform buffer"),
1731 size: MESH_UNIFORM_CAP,
1732 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1733 mapped_at_creation: false,
1734 });
1735 let mesh_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1736 label: Some("mesh uniform bind"),
1737 layout: &mesh_bind_layout,
1738 entries: &[wgpu::BindGroupEntry {
1739 binding: 0,
1740 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1741 buffer: &mesh_uniform_buf,
1742 offset: 0,
1743 size: NonZero::new(MESH_UNIFORM_SLOT),
1744 }),
1745 }],
1746 });
1747
1748 let surface_pipes = Pipelines::create(
1751 &device,
1752 output_format,
1753 msaa_samples,
1754 &globals_layout,
1755 &text_bind_layout,
1756 &image_bind_layout_nv12,
1757 &clip_pipeline_layout,
1758 &stencil_for_content,
1759 &stencil_for_clip_inc,
1760 &stencil_for_clip_dec,
1761 &clip_color_target,
1762 &clip_vertex_layout,
1763 &mesh_bind_layout,
1764 );
1765 let layer_pipes = Pipelines::create(
1766 &device,
1767 output_format,
1768 1,
1769 &globals_layout,
1770 &text_bind_layout,
1771 &image_bind_layout_nv12,
1772 &clip_pipeline_layout,
1773 &stencil_for_content,
1774 &stencil_for_clip_inc,
1775 &stencil_for_clip_dec,
1776 &clip_color_target,
1777 &clip_vertex_layout,
1778 &mesh_bind_layout,
1779 );
1780
1781 let slug_enabled = true;
1783
1784 let blur_ring = UploadRing::new(&device, "blur ring", 1024 * 1024, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1786
1787 let atlas_mask = init_atlas_mask(&device);
1789 let atlas_color = init_atlas_color(&device);
1790
1791 let ring_rect = UploadRing::new(&device, "ring rect", 1 << 20, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1793 let ring_border = UploadRing::new(&device, "ring border", 1 << 20, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1794 let ring_ellipse = UploadRing::new(&device, "ring ellipse", 1 << 20, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1795 let ring_ellipse_border = UploadRing::new(&device, "ring ellipse border", 1 << 20, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1796 let ring_arc = UploadRing::new(&device, "ring arc", 1 << 20, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1797 let ring_glyph_mask = UploadRing::new(&device, "ring glyph mask", 1 << 20, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1798 let ring_glyph_color = UploadRing::new(&device, "ring glyph color", 1 << 20, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1799 let ring_slug = UploadRing::new(&device, "ring slug", 1 << 22, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1800 let ring_clip = UploadRing::new(&device, "ring clip", 1 << 16, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1801 let ring_nv12 = UploadRing::new(&device, "ring nv12", 1 << 20, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1802 let ring_mesh_verts = UploadRing::new(&device, "ring mesh verts", 1 << 22, wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST);
1803 let ring_mesh_indices = UploadRing::new(&device, "ring mesh indices", 1 << 22, wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST);
1804
1805 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
1807 label: Some("temp ds"),
1808 size: wgpu::Extent3d {
1809 width: 1,
1810 height: 1,
1811 depth_or_array_layers: 1,
1812 },
1813 mip_level_count: 1,
1814 sample_count: 1,
1815 dimension: wgpu::TextureDimension::D2,
1816 format: wgpu::TextureFormat::Depth24PlusStencil8,
1817 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1818 view_formats: &[],
1819 });
1820 let depth_stencil_view =
1821 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1822
1823 let mut renderer = WgpuSceneRenderer {
1824 device,
1825 queue,
1826 output_format,
1827 output_width: 0,
1828 output_height: 0,
1829
1830 surface_pipes,
1831 layer_pipes,
1832
1833 rects: InstancedPipe::new(ring_rect),
1834 borders: InstancedPipe::new(ring_border),
1835 ellipses: InstancedPipe::new(ring_ellipse),
1836 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
1837 arcs: InstancedPipe::new(ring_arc),
1838 glyph_mask: InstancedPipe::new(ring_glyph_mask),
1839 glyph_color: InstancedPipe::new(ring_glyph_color),
1840
1841 text_bind_layout,
1842
1843 image_bind_layout_rgba,
1844 image_bind_layout_nv12,
1845 image_sampler,
1846 layer_sampler,
1847 layer_sampler_linear,
1848
1849 blur_ring,
1850
1851 slug_enabled,
1852 slug_ring: ring_slug,
1853 slug_cache: slug::GlyphSlugCache::new(),
1854
1855 clip_ring: ring_clip,
1856
1857 nv12: InstancedPipe::new(ring_nv12),
1858
1859 mesh_verts: ring_mesh_verts,
1860 mesh_indices: ring_mesh_indices,
1861 mesh_uniform_buf,
1862 mesh_bind_layout,
1863 mesh_bind,
1864 mesh_uniform_head: 0,
1865 mesh_clip_stack: Vec::new(),
1866
1867 msaa_samples,
1868 depth_stencil_tex,
1869 depth_stencil_view,
1870 msaa_tex: None,
1871 msaa_view: None,
1872 globals_bind,
1873 globals_buf,
1874 globals_layout,
1875
1876 atlas_mask,
1877 atlas_color,
1878
1879 next_image_handle: 1,
1880 images: HashMap::new(),
1881
1882 frame_index: 0,
1883 image_bytes_total: 0,
1884 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
1887
1888 working_space: false,
1889 ws_tex: None,
1890 ws_view: None,
1891 ws_bind: None,
1892 display_pipeline: None,
1893 display_layout: None,
1894 };
1895
1896 renderer.recreate_msaa_and_depth_stencil();
1897 renderer
1898 }
1899}
1900
1901impl WgpuSurfaceBackend {
1902 #[cfg(feature = "winit-surface")]
1903 pub async fn new_async(window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
1904 Self::new_async_with_options(window, 4, PresentModePref::Auto).await
1905 }
1906
1907 #[cfg(feature = "winit-surface")]
1910 pub async fn new_async_with_msaa(
1911 window: Arc<winit::window::Window>,
1912 msaa_samples: u32,
1913 ) -> anyhow::Result<WgpuSurfaceBackend> {
1914 Self::new_async_with_options(window, msaa_samples, PresentModePref::Auto).await
1915 }
1916
1917 #[cfg(feature = "winit-surface")]
1920 pub async fn new_async_with_options(
1921 window: Arc<winit::window::Window>,
1922 msaa_samples: u32,
1923 present_mode: PresentModePref,
1924 ) -> anyhow::Result<WgpuSurfaceBackend> {
1925 let instance: Instance;
1926
1927 if cfg!(target_arch = "wasm32") {
1928 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
1929 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
1930 instance = wgpu::util::new_instance_with_webgpu_detection(desc).await;
1931 } else {
1932 instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
1933 };
1934
1935 let surface = instance.create_surface(window.clone())?;
1936
1937 let adapter = instance
1938 .request_adapter(&wgpu::RequestAdapterOptions {
1939 power_preference: wgpu::PowerPreference::HighPerformance,
1940 compatible_surface: Some(&surface),
1941 force_fallback_adapter: false,
1942 apply_limit_buckets: false,
1943 })
1944 .await
1945 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
1946
1947 let limits = adapter.limits();
1948
1949 #[cfg(target_os = "linux")]
1950 let features = {
1951 let af = adapter.features();
1952 let mut f = wgpu::Features::empty();
1953 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD) {
1954 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD;
1955 }
1956 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF) {
1957 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF;
1958 }
1959 f
1960 };
1961 #[cfg(not(target_os = "linux"))]
1962 let features = wgpu::Features::empty();
1963
1964 let (device, queue) = adapter
1965 .request_device(&wgpu::DeviceDescriptor {
1966 label: Some("repose-rs device"),
1967 required_features: features,
1968 required_limits: limits,
1969 experimental_features: wgpu::ExperimentalFeatures::disabled(),
1970 memory_hints: wgpu::MemoryHints::default(),
1971 trace: wgpu::Trace::Off,
1972 })
1973 .await
1974 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
1975
1976 let size = window.inner_size();
1977
1978 let caps = surface.get_capabilities(&adapter);
1979 let format = caps
1980 .formats
1981 .iter()
1982 .copied()
1983 .find(|f| f.is_srgb())
1984 .unwrap_or(caps.formats[0]);
1985 let present_mode = pick_present_mode(&caps, present_mode);
1986 let alpha_mode = caps.alpha_modes[0];
1987
1988 let msaa_samples = pick_surface_msaa(&adapter, format, msaa_samples);
1992
1993 let renderer = WgpuSceneRenderer::from_device(device, queue, format, msaa_samples);
1994
1995 let config = wgpu::SurfaceConfiguration {
1996 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1997 format,
1998 width: size.width.max(1),
1999 height: size.height.max(1),
2000 present_mode,
2001 alpha_mode,
2002 color_space: wgpu::SurfaceColorSpace::Auto,
2003 view_formats: vec![],
2004 desired_maximum_frame_latency: 1,
2005 };
2006 surface.configure(&renderer.device, &config);
2007
2008 Ok(WgpuSurfaceBackend { surface: Some(surface), surface_config: Some(config), renderer })
2009 }
2010
2011 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2012 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2013 pollster::block_on(Self::new_async(window))
2014 }
2015
2016 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2017 pub fn new_with_msaa(
2018 window: Arc<winit::window::Window>,
2019 msaa_samples: u32,
2020 ) -> anyhow::Result<WgpuSurfaceBackend> {
2021 pollster::block_on(Self::new_async_with_msaa(window, msaa_samples))
2022 }
2023
2024 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2025 pub fn new_with_options(
2026 window: Arc<winit::window::Window>,
2027 msaa_samples: u32,
2028 present_mode: PresentModePref,
2029 ) -> anyhow::Result<WgpuSurfaceBackend> {
2030 pollster::block_on(Self::new_async_with_options(
2031 window,
2032 msaa_samples,
2033 present_mode,
2034 ))
2035 }
2036
2037 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2038 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2039 anyhow::bail!("Use WgpuSurfaceBackend::new_async(window).await on wasm32")
2040 }
2041
2042 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2043 pub fn new_with_msaa(
2044 _window: Arc<winit::window::Window>,
2045 _msaa_samples: u32,
2046 ) -> anyhow::Result<WgpuSurfaceBackend> {
2047 anyhow::bail!("Use WgpuSurfaceBackend::new_async_with_msaa(window, msaa).await on wasm32")
2048 }
2049
2050 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2051 pub fn new_with_options(
2052 _window: Arc<winit::window::Window>,
2053 _msaa_samples: u32,
2054 _present_mode: PresentModePref,
2055 ) -> anyhow::Result<WgpuSurfaceBackend> {
2056 anyhow::bail!(
2057 "Use WgpuSurfaceBackend::new_async_with_options(window, msaa, mode).await on wasm32"
2058 )
2059 }
2060}
2061
2062fn pick_present_mode(caps: &wgpu::SurfaceCapabilities, pref: PresentModePref) -> wgpu::PresentMode {
2065 let auto = || {
2066 caps.present_modes
2067 .iter()
2068 .copied()
2069 .find(|m| *m == wgpu::PresentMode::Fifo)
2070 .or_else(|| {
2071 caps.present_modes
2072 .iter()
2073 .copied()
2074 .find(|m| *m == wgpu::PresentMode::Mailbox)
2075 })
2076 .unwrap_or(wgpu::PresentMode::Immediate)
2077 };
2078 match pref {
2079 PresentModePref::Auto => auto(),
2080 PresentModePref::Fifo if caps.present_modes.contains(&wgpu::PresentMode::Fifo) => {
2081 wgpu::PresentMode::Fifo
2082 }
2083 PresentModePref::Mailbox if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) => {
2084 wgpu::PresentMode::Mailbox
2085 }
2086 PresentModePref::Immediate
2087 if caps.present_modes.contains(&wgpu::PresentMode::Immediate) =>
2088 {
2089 wgpu::PresentMode::Immediate
2090 }
2091 _ => auto(),
2092 }
2093}
2094
2095fn pick_surface_msaa(adapter: &wgpu::Adapter, format: wgpu::TextureFormat, requested: u32) -> u32 {
2098 let requested = requested.max(1);
2099 let color_feat = adapter.get_texture_format_features(format);
2100 let depth_feat = adapter.get_texture_format_features(wgpu::TextureFormat::Depth24PlusStencil8);
2101 let supported = |n: u32| {
2102 color_feat.flags.sample_count_supported(n)
2103 && color_feat
2104 .flags
2105 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
2106 && depth_feat.flags.sample_count_supported(n)
2107 };
2108 let mut candidates = vec![requested];
2109 for n in [8, 4, 2, 1] {
2110 if n < requested {
2111 candidates.push(n);
2112 }
2113 }
2114 let chosen = candidates.into_iter().find(|&n| supported(n)).unwrap_or(1);
2115 if chosen != requested {
2116 log::info!("requested MSAA x{requested}, using x{chosen}");
2117 }
2118 chosen
2119}
2120
2121impl WgpuSceneRenderer {
2122 pub fn set_image_from_bytes(
2125 &mut self,
2126 handle: u64,
2127 data: &[u8],
2128 srgb: bool,
2129 ) -> anyhow::Result<()> {
2130 let img = image::load_from_memory(data)?;
2131 let rgba = img.to_rgba8();
2132 let (w, h) = rgba.dimensions();
2133 self.set_image_rgba8(handle, w, h, &rgba, srgb)
2134 }
2135
2136 pub fn set_image_rgba8(
2137 &mut self,
2138 handle: u64,
2139 w: u32,
2140 h: u32,
2141 rgba: &[u8],
2142 srgb: bool,
2143 ) -> anyhow::Result<()> {
2144 let expected = (w as usize) * (h as usize) * 4;
2145 if rgba.len() < expected {
2146 return Err(anyhow::anyhow!(
2147 "RGBA buffer too small: {} < {}",
2148 rgba.len(),
2149 expected
2150 ));
2151 }
2152
2153 let format = if srgb {
2154 wgpu::TextureFormat::Rgba8UnormSrgb
2155 } else {
2156 wgpu::TextureFormat::Rgba8Unorm
2157 };
2158
2159 let needs_recreate = match self.images.get(&handle) {
2160 Some(ImageTex::Rgba {
2161 w: cw,
2162 h: ch,
2163 format: cf,
2164 ..
2165 }) => *cw != w || *ch != h || *cf != format,
2166 _ => true,
2167 };
2168
2169 if needs_recreate {
2170 self.remove_image(handle);
2172
2173 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2174 label: Some("user image rgba"),
2175 size: wgpu::Extent3d {
2176 width: w,
2177 height: h,
2178 depth_or_array_layers: 1,
2179 },
2180 mip_level_count: 1,
2181 sample_count: 1,
2182 dimension: wgpu::TextureDimension::D2,
2183 format,
2184 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2185 view_formats: &[],
2186 });
2187 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
2188
2189 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2190 label: Some("image bind rgba"),
2191 layout: &self.image_bind_layout_rgba,
2192 entries: &[
2193 wgpu::BindGroupEntry {
2194 binding: 0,
2195 resource: wgpu::BindingResource::TextureView(&view),
2196 },
2197 wgpu::BindGroupEntry {
2198 binding: 1,
2199 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2200 },
2201 ],
2202 });
2203
2204 let bytes = (w as u64) * (h as u64) * 4;
2205 self.image_bytes_total += bytes;
2206
2207 self.images.insert(
2208 handle,
2209 ImageTex::Rgba {
2210 tex,
2211 view,
2212 bind,
2213 w,
2214 h,
2215 format,
2216 last_used_frame: self.frame_index,
2217 bytes,
2218 },
2219 );
2220 }
2221
2222 let tex = match self.images.get(&handle) {
2223 Some(ImageTex::Rgba { tex, .. }) => tex,
2224 _ => unreachable!(),
2225 };
2226
2227 self.queue.write_texture(
2228 wgpu::TexelCopyTextureInfo {
2229 texture: tex,
2230 mip_level: 0,
2231 origin: wgpu::Origin3d::ZERO,
2232 aspect: wgpu::TextureAspect::All,
2233 },
2234 &rgba[..expected],
2235 wgpu::TexelCopyBufferLayout {
2236 offset: 0,
2237 bytes_per_row: Some(4 * w),
2238 rows_per_image: Some(h),
2239 },
2240 wgpu::Extent3d {
2241 width: w,
2242 height: h,
2243 depth_or_array_layers: 1,
2244 },
2245 );
2246
2247 self.evict_budget_excess();
2249
2250 Ok(())
2251 }
2252
2253 pub fn set_image_nv12(
2254 &mut self,
2255 handle: u64,
2256 w: u32,
2257 h: u32,
2258 y: &[u8],
2259 uv: &[u8],
2260 color_info: ColorInfo,
2261 ) -> anyhow::Result<()> {
2262 let y_expected = (w as usize) * (h as usize);
2263 let uv_w = w.div_ceil(2);
2264 let uv_h = h.div_ceil(2);
2265 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
2266
2267 if y.len() < y_expected {
2268 return Err(anyhow::anyhow!("Y plane too small"));
2269 }
2270 if uv.len() < uv_expected {
2271 return Err(anyhow::anyhow!("UV plane too small"));
2272 }
2273
2274 let needs_recreate = match self.images.get(&handle) {
2275 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
2276 _ => true,
2277 };
2278
2279 let yuv = color_info.to_yuv_transform();
2281 let yuv_raw = YuvTransformRaw {
2282 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2283 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2284 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2285 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
2286 };
2287
2288 if needs_recreate {
2289 self.remove_image(handle);
2290
2291 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
2292 label: Some("nv12 Y"),
2293 size: wgpu::Extent3d {
2294 width: w,
2295 height: h,
2296 depth_or_array_layers: 1,
2297 },
2298 mip_level_count: 1,
2299 sample_count: 1,
2300 dimension: wgpu::TextureDimension::D2,
2301 format: wgpu::TextureFormat::R8Unorm,
2302 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2303 view_formats: &[],
2304 });
2305 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
2306
2307 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
2308 label: Some("nv12 UV"),
2309 size: wgpu::Extent3d {
2310 width: uv_w,
2311 height: uv_h,
2312 depth_or_array_layers: 1,
2313 },
2314 mip_level_count: 1,
2315 sample_count: 1,
2316 dimension: wgpu::TextureDimension::D2,
2317 format: wgpu::TextureFormat::Rg8Unorm,
2318 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2319 view_formats: &[],
2320 });
2321 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
2322
2323 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2325 label: Some("nv12 yuv transform"),
2326 size: std::mem::size_of::<YuvTransformRaw>() as u64,
2327 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2328 mapped_at_creation: false,
2329 });
2330
2331 self.queue
2333 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2334
2335 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2336 label: Some("nv12 bind"),
2337 layout: &self.image_bind_layout_nv12,
2338 entries: &[
2339 wgpu::BindGroupEntry {
2340 binding: 0,
2341 resource: wgpu::BindingResource::TextureView(&view_y),
2342 },
2343 wgpu::BindGroupEntry {
2344 binding: 1,
2345 resource: wgpu::BindingResource::TextureView(&view_uv),
2346 },
2347 wgpu::BindGroupEntry {
2348 binding: 2,
2349 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2350 },
2351 wgpu::BindGroupEntry {
2352 binding: 3,
2353 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2354 buffer: &yuv_buf,
2355 offset: 0,
2356 size: None,
2357 }),
2358 },
2359 ],
2360 });
2361
2362 let bytes = (w as u64) * (h as u64)
2363 + (uv_w as u64) * (uv_h as u64) * 2
2364 + std::mem::size_of::<YuvTransformRaw>() as u64;
2365 self.image_bytes_total += bytes;
2366
2367 self.images.insert(
2368 handle,
2369 ImageTex::Nv12 {
2370 tex_y,
2371 view_y,
2372 tex_uv,
2373 view_uv,
2374 bind,
2375 yuv_buf,
2376 w,
2377 h,
2378 color_info,
2379 last_used_frame: self.frame_index,
2380 bytes,
2381 },
2382 );
2383 } else {
2384 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
2386 self.queue
2387 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2388 }
2389 }
2390
2391 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
2392 Some(ImageTex::Nv12 {
2393 tex_y,
2394 tex_uv,
2395 bind,
2396 ..
2397 }) => (tex_y, tex_uv, bind),
2398 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
2399 };
2400
2401 self.queue.write_texture(
2402 wgpu::TexelCopyTextureInfo {
2403 texture: tex_y,
2404 mip_level: 0,
2405 origin: wgpu::Origin3d::ZERO,
2406 aspect: wgpu::TextureAspect::All,
2407 },
2408 &y[..y_expected],
2409 wgpu::TexelCopyBufferLayout {
2410 offset: 0,
2411 bytes_per_row: Some(w),
2412 rows_per_image: Some(h),
2413 },
2414 wgpu::Extent3d {
2415 width: w,
2416 height: h,
2417 depth_or_array_layers: 1,
2418 },
2419 );
2420
2421 self.queue.write_texture(
2422 wgpu::TexelCopyTextureInfo {
2423 texture: tex_uv,
2424 mip_level: 0,
2425 origin: wgpu::Origin3d::ZERO,
2426 aspect: wgpu::TextureAspect::All,
2427 },
2428 &uv[..uv_expected],
2429 wgpu::TexelCopyBufferLayout {
2430 offset: 0,
2431 bytes_per_row: Some(2 * uv_w),
2432 rows_per_image: Some(uv_h),
2433 },
2434 wgpu::Extent3d {
2435 width: uv_w,
2436 height: uv_h,
2437 depth_or_array_layers: 1,
2438 },
2439 );
2440
2441 self.evict_budget_excess();
2442 Ok(())
2443 }
2444
2445 pub fn set_image_planes(
2446 &mut self,
2447 handle: u64,
2448 w: u32,
2449 h: u32,
2450 pixel_format: PixelFormat,
2451 planes: &[&[u8]],
2452 color_info: ColorInfo,
2453 ) -> anyhow::Result<()> {
2454 match pixel_format {
2455 PixelFormat::Nv12 => {
2456 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
2457 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
2458 self.set_image_nv12(handle, w, h, y, uv, color_info)
2459 }
2460 PixelFormat::P010 => {
2461 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
2462 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
2463 self.set_image_p010(handle, w, h, y, uv, color_info)
2464 }
2465 PixelFormat::I420 | PixelFormat::I444 => Err(anyhow::anyhow!(
2466 "I420/I444 not implemented and unlikely -> cheap to convert to NV12 (better for the GPU too)"
2467 )),
2468 PixelFormat::Rgba => {
2469 let rgba = planes
2470 .first()
2471 .ok_or(anyhow::anyhow!("missing RGBA plane"))?;
2472 self.set_image_rgba8(handle, w, h, rgba, false)
2473 }
2474 }
2475 }
2476
2477 fn set_image_p010(
2478 &mut self,
2479 handle: u64,
2480 w: u32,
2481 h: u32,
2482 y: &[u8],
2483 uv: &[u8],
2484 color_info: ColorInfo,
2485 ) -> anyhow::Result<()> {
2486 let uv_w = w.div_ceil(2);
2487 let uv_h = h.div_ceil(2);
2488
2489 let y_expected = (w as usize) * 2;
2490 let uv_expected = (uv_w as usize) * (uv_h as usize) * 4;
2491
2492 if y.len() < y_expected {
2493 return Err(anyhow::anyhow!("P010 Y plane too small"));
2494 }
2495 if uv.len() < uv_expected {
2496 return Err(anyhow::anyhow!("P010 UV plane too small"));
2497 }
2498
2499 let needs_recreate = match self.images.get(&handle) {
2503 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
2504 _ => true,
2505 };
2506
2507 let yuv = color_info.to_yuv_transform();
2508 let yuv_raw = YuvTransformRaw {
2509 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2510 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2511 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2512 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
2513 };
2514
2515 if needs_recreate {
2516 self.remove_image(handle);
2517
2518 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
2519 label: Some("p010 Y"),
2520 size: wgpu::Extent3d {
2521 width: w,
2522 height: h,
2523 depth_or_array_layers: 1,
2524 },
2525 mip_level_count: 1,
2526 sample_count: 1,
2527 dimension: wgpu::TextureDimension::D2,
2528 format: wgpu::TextureFormat::R16Unorm,
2529 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2530 view_formats: &[],
2531 });
2532 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
2533
2534 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
2535 label: Some("p010 UV"),
2536 size: wgpu::Extent3d {
2537 width: uv_w,
2538 height: uv_h,
2539 depth_or_array_layers: 1,
2540 },
2541 mip_level_count: 1,
2542 sample_count: 1,
2543 dimension: wgpu::TextureDimension::D2,
2544 format: wgpu::TextureFormat::Rg16Unorm,
2545 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2546 view_formats: &[],
2547 });
2548 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
2549
2550 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2551 label: Some("p010 yuv transform"),
2552 size: std::mem::size_of::<YuvTransformRaw>() as u64,
2553 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2554 mapped_at_creation: false,
2555 });
2556 self.queue
2557 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2558
2559 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2560 label: Some("p010 bind"),
2561 layout: &self.image_bind_layout_nv12,
2562 entries: &[
2563 wgpu::BindGroupEntry {
2564 binding: 0,
2565 resource: wgpu::BindingResource::TextureView(&view_y),
2566 },
2567 wgpu::BindGroupEntry {
2568 binding: 1,
2569 resource: wgpu::BindingResource::TextureView(&view_uv),
2570 },
2571 wgpu::BindGroupEntry {
2572 binding: 2,
2573 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2574 },
2575 wgpu::BindGroupEntry {
2576 binding: 3,
2577 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2578 buffer: &yuv_buf,
2579 offset: 0,
2580 size: None,
2581 }),
2582 },
2583 ],
2584 });
2585
2586 let bytes = (w as u64) * 2
2587 + (uv_w as u64) * (uv_h as u64) * 4
2588 + std::mem::size_of::<YuvTransformRaw>() as u64;
2589 self.image_bytes_total += bytes;
2590
2591 self.images.insert(
2592 handle,
2593 ImageTex::Nv12 {
2594 tex_y,
2595 view_y,
2596 tex_uv,
2597 view_uv,
2598 bind,
2599 yuv_buf,
2600 w,
2601 h,
2602 color_info,
2603 last_used_frame: self.frame_index,
2604 bytes,
2605 },
2606 );
2607 } else {
2608 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
2609 self.queue
2610 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2611 }
2612 }
2613
2614 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
2615 Some(ImageTex::Nv12 {
2616 tex_y,
2617 tex_uv,
2618 bind,
2619 ..
2620 }) => (tex_y, tex_uv, bind),
2621 _ => return Err(anyhow::anyhow!("Handle is not P010/NV12")),
2622 };
2623
2624 self.queue.write_texture(
2625 wgpu::TexelCopyTextureInfo {
2626 texture: tex_y,
2627 mip_level: 0,
2628 origin: wgpu::Origin3d::ZERO,
2629 aspect: wgpu::TextureAspect::All,
2630 },
2631 &y[..y_expected],
2632 wgpu::TexelCopyBufferLayout {
2633 offset: 0,
2634 bytes_per_row: Some(w * 2),
2635 rows_per_image: Some(h),
2636 },
2637 wgpu::Extent3d {
2638 width: w,
2639 height: h,
2640 depth_or_array_layers: 1,
2641 },
2642 );
2643 self.queue.write_texture(
2644 wgpu::TexelCopyTextureInfo {
2645 texture: tex_uv,
2646 mip_level: 0,
2647 origin: wgpu::Origin3d::ZERO,
2648 aspect: wgpu::TextureAspect::All,
2649 },
2650 &uv[..uv_expected],
2651 wgpu::TexelCopyBufferLayout {
2652 offset: 0,
2653 bytes_per_row: Some(uv_w * 4),
2654 rows_per_image: Some(uv_h),
2655 },
2656 wgpu::Extent3d {
2657 width: uv_w,
2658 height: uv_h,
2659 depth_or_array_layers: 1,
2660 },
2661 );
2662
2663 self.evict_budget_excess();
2664 Ok(())
2665 }
2666
2667 #[cfg(target_os = "linux")]
2668 pub fn set_image_dmabuf(
2669 &mut self,
2670 handle: u64,
2671 w: u32,
2672 h: u32,
2673 fds: Vec<std::os::unix::io::OwnedFd>,
2674 modifier: u64,
2675 strides: Vec<u32>,
2676 offsets: Vec<u64>,
2677 color_info: ColorInfo,
2678 ) -> anyhow::Result<()> {
2679 log::info!("set_image_dmabuf handle={handle} {}x{} fds={} modifier=0x{modifier:x}", w, h, fds.len());
2680
2681 self.remove_image(handle);
2682
2683 let yuv = color_info.to_yuv_transform();
2684 let yuv_raw = YuvTransformRaw {
2685 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2686 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2687 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2688 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
2689 };
2690
2691 if fds.len() != 2 {
2692 return Err(anyhow::anyhow!(
2693 "unsupported fd count {} - need exactly 2 for separate Y/UV planes",
2694 fds.len()
2695 ));
2696 }
2697
2698 let uv_w = w.div_ceil(2);
2699 let uv_h = h.div_ceil(2);
2700
2701 let hal_y_desc = wgpu::hal::TextureDescriptor {
2702 label: Some("dmabuf y"),
2703 size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
2704 mip_level_count: 1,
2705 sample_count: 1,
2706 dimension: wgpu::TextureDimension::D2,
2707 format: wgpu::TextureFormat::R8Unorm,
2708 usage: wgpu::wgt::TextureUses::RESOURCE,
2709 memory_flags: wgpu::hal::MemoryFlags::empty(),
2710 view_formats: vec![],
2711 };
2712 let hal_uv_desc = wgpu::hal::TextureDescriptor {
2713 label: Some("dmabuf uv"),
2714 size: wgpu::Extent3d { width: uv_w, height: uv_h, depth_or_array_layers: 1 },
2715 mip_level_count: 1,
2716 sample_count: 1,
2717 dimension: wgpu::TextureDimension::D2,
2718 format: wgpu::TextureFormat::Rg8Unorm,
2719 usage: wgpu::wgt::TextureUses::RESOURCE,
2720 memory_flags: wgpu::hal::MemoryFlags::empty(),
2721 view_formats: vec![],
2722 };
2723
2724 let wgpu_y_desc = wgpu::TextureDescriptor {
2725 label: Some("dmabuf y"),
2726 size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
2727 mip_level_count: 1,
2728 sample_count: 1,
2729 dimension: wgpu::TextureDimension::D2,
2730 format: wgpu::TextureFormat::R8Unorm,
2731 usage: wgpu::TextureUsages::TEXTURE_BINDING,
2732 view_formats: &[],
2733 };
2734 let wgpu_uv_desc = wgpu::TextureDescriptor {
2735 label: Some("dmabuf uv"),
2736 size: wgpu::Extent3d { width: uv_w, height: uv_h, depth_or_array_layers: 1 },
2737 mip_level_count: 1,
2738 sample_count: 1,
2739 dimension: wgpu::TextureDimension::D2,
2740 format: wgpu::TextureFormat::Rg8Unorm,
2741 usage: wgpu::TextureUsages::TEXTURE_BINDING,
2742 view_formats: &[],
2743 };
2744
2745 let (tex_y, view_y, tex_uv, view_uv) = unsafe {
2746 let mut hal_guard = self.device.as_hal::<wgpu::hal::vulkan::Api>()
2747 .ok_or_else(|| {
2748 log::warn!("as_hal::<vulkan::Api> returned None");
2749 anyhow::anyhow!("Device is not Vulkan")
2750 })?;
2751
2752 let mut fds = fds;
2753 let uv_fd = fds.remove(1);
2754 let y_fd = fds.remove(0);
2755
2756 let yt = hal_guard
2757 .texture_from_dmabuf_fd(y_fd, &hal_y_desc, modifier, strides[0] as u64, offsets[0] as u64)
2758 .map_err(|e| anyhow::anyhow!("import Y dmabuf: {e:?}"))?;
2759 log::info!("imported Y dmabuf OK");
2760
2761 let uvt = hal_guard
2762 .texture_from_dmabuf_fd(uv_fd, &hal_uv_desc, modifier, strides[1] as u64, offsets[1] as u64)
2763 .map_err(|e| anyhow::anyhow!("import UV dmabuf: {e:?}"))?;
2764 log::info!("imported UV dmabuf OK");
2765
2766 drop(hal_guard);
2767
2768 let tex_y = self.device.create_texture_from_hal::<wgpu::hal::vulkan::Api>(
2769 yt,
2770 &wgpu_y_desc,
2771 wgpu::wgt::TextureUses::UNINITIALIZED,
2772 );
2773 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
2774
2775 let tex_uv = self.device.create_texture_from_hal::<wgpu::hal::vulkan::Api>(
2776 uvt,
2777 &wgpu_uv_desc,
2778 wgpu::wgt::TextureUses::UNINITIALIZED,
2779 );
2780 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
2781
2782 (tex_y, view_y, tex_uv, view_uv)
2783 };
2784
2785 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2786 label: Some("dmabuf yuv transform"),
2787 size: std::mem::size_of::<YuvTransformRaw>() as u64,
2788 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2789 mapped_at_creation: false,
2790 });
2791 self.queue.write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2792
2793 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2794 label: Some("dmabuf nv12 bind"),
2795 layout: &self.image_bind_layout_nv12,
2796 entries: &[
2797 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&view_y) },
2798 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::TextureView(&view_uv) },
2799 wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Sampler(&self.image_sampler) },
2800 wgpu::BindGroupEntry {
2801 binding: 3,
2802 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2803 buffer: &yuv_buf,
2804 offset: 0,
2805 size: None,
2806 }),
2807 },
2808 ],
2809 });
2810
2811 let bytes = (w as u64) * (h as u64)
2812 + (uv_w as u64) * (uv_h as u64) * 2
2813 + std::mem::size_of::<YuvTransformRaw>() as u64;
2814
2815 self.images.insert(
2816 handle,
2817 ImageTex::Nv12 {
2818 tex_y,
2819 view_y,
2820 tex_uv,
2821 view_uv,
2822 bind,
2823 yuv_buf,
2824 w,
2825 h,
2826 color_info,
2827 last_used_frame: self.frame_index,
2828 bytes,
2829 },
2830 );
2831
2832 self.evict_budget_excess();
2833 Ok(())
2834 }
2835
2836 pub fn remove_image(&mut self, handle: u64) {
2837 if let Some(img) = self.images.remove(&handle) {
2838 let b = match &img {
2839 ImageTex::Rgba { bytes, .. } => *bytes,
2840 ImageTex::Nv12 { bytes, .. } => *bytes,
2841 };
2842 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
2843 }
2844 }
2845
2846 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
2848 let handle = self.next_image_handle;
2849 self.next_image_handle += 1;
2850 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
2851 log::error!("Failed to register image: {e}");
2852 }
2853 handle
2854 }
2855
2856 fn evict_unused_images(&mut self) {
2857 let now = self.frame_index;
2858 let evict_after = self.image_evict_after_frames;
2859
2860 let mut to_remove = Vec::new();
2862 for (h, t) in self.images.iter() {
2863 let last = match t {
2864 ImageTex::Rgba {
2865 last_used_frame, ..
2866 } => *last_used_frame,
2867 ImageTex::Nv12 {
2868 last_used_frame, ..
2869 } => *last_used_frame,
2870 };
2871 if now.saturating_sub(last) > evict_after {
2872 to_remove.push(*h);
2873 }
2874 }
2875 for h in to_remove {
2876 self.remove_image(h);
2877 }
2878
2879 self.evict_budget_excess();
2880 }
2881
2882 fn evict_budget_excess(&mut self) {
2883 if self.image_bytes_total <= self.image_budget_bytes {
2884 return;
2885 }
2886 let mut candidates: Vec<(u64, u64, u64)> = self
2888 .images
2889 .iter()
2890 .map(|(h, t)| {
2891 let (last, bytes) = match t {
2892 ImageTex::Rgba {
2893 last_used_frame,
2894 bytes,
2895 ..
2896 } => (*last_used_frame, *bytes),
2897 ImageTex::Nv12 {
2898 last_used_frame,
2899 bytes,
2900 ..
2901 } => (*last_used_frame, *bytes),
2902 };
2903 (*h, last, bytes)
2904 })
2905 .collect();
2906
2907 candidates.sort_by_key(|k| k.1);
2909
2910 let now = self.frame_index;
2911 for (h, last, _bytes) in candidates {
2912 if self.image_bytes_total <= self.image_budget_bytes {
2913 break;
2914 }
2915 if last == now {
2917 continue;
2918 }
2919 self.remove_image(h);
2920 }
2921 }
2922
2923 pub fn set_working_space(&mut self, enabled: bool) {
2927 if enabled == self.working_space {
2928 return;
2929 }
2930 self.working_space = enabled;
2931 if enabled {
2932 self.ensure_display_pipeline();
2933 self.recreate_working_space_texture();
2934 } else {
2935 self.ws_tex = None;
2936 self.ws_view = None;
2937 self.ws_bind = None;
2938 }
2939 }
2940
2941 fn ensure_display_pipeline(&mut self) {
2942 if self.display_pipeline.is_some() {
2943 return;
2944 }
2945
2946 let layout = self
2947 .device
2948 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2949 label: Some("display transform layout"),
2950 entries: &[
2951 wgpu::BindGroupLayoutEntry {
2952 binding: 0,
2953 visibility: wgpu::ShaderStages::FRAGMENT,
2954 ty: wgpu::BindingType::Texture {
2955 multisampled: false,
2956 view_dimension: wgpu::TextureViewDimension::D2,
2957 sample_type: wgpu::TextureSampleType::Float { filterable: true },
2958 },
2959 count: None,
2960 },
2961 wgpu::BindGroupLayoutEntry {
2962 binding: 1,
2963 visibility: wgpu::ShaderStages::FRAGMENT,
2964 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
2965 count: None,
2966 },
2967 ],
2968 });
2969 self.display_layout = Some(layout);
2970
2971 let shader = self
2972 .device
2973 .create_shader_module(wgpu::ShaderModuleDescriptor {
2974 label: Some("display_transform.wgsl"),
2975 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
2976 "shaders/display_transform.wgsl"
2977 ))),
2978 });
2979
2980 let pipeline_layout = self
2981 .device
2982 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
2983 label: Some("display transform pipeline layout"),
2984 bind_group_layouts: &[None, self.display_layout.as_ref()],
2985 immediate_size: 0,
2986 });
2987
2988 let pipeline = self
2989 .device
2990 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
2991 label: Some("display transform pipeline"),
2992 layout: Some(&pipeline_layout),
2993 vertex: wgpu::VertexState {
2994 module: &shader,
2995 entry_point: Some("vs_main"),
2996 buffers: &[],
2997 compilation_options: wgpu::PipelineCompilationOptions::default(),
2998 },
2999 fragment: Some(wgpu::FragmentState {
3000 module: &shader,
3001 entry_point: Some("fs_main"),
3002 targets: &[Some(wgpu::ColorTargetState {
3003 format: self.output_format,
3004 blend: None,
3005 write_mask: wgpu::ColorWrites::ALL,
3006 })],
3007 compilation_options: wgpu::PipelineCompilationOptions::default(),
3008 }),
3009 primitive: wgpu::PrimitiveState::default(),
3010 depth_stencil: None,
3011 multisample: wgpu::MultisampleState::default(),
3012 multiview_mask: None,
3013 cache: None,
3014 });
3015 self.display_pipeline = Some(pipeline);
3016 }
3017
3018 pub fn resize(&mut self, width: u32, height: u32) {
3023 self.output_width = width;
3024 self.output_height = height;
3025 self.recreate_msaa_and_depth_stencil();
3026 self.recreate_working_space_texture();
3027 }
3028
3029 fn recreate_working_space_texture(&mut self) {
3030 if !self.working_space {
3031 return;
3032 }
3033 let w = self.output_width.max(1);
3034 let h = self.output_height.max(1);
3035
3036 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3037 label: Some("working space"),
3038 size: wgpu::Extent3d {
3039 width: w,
3040 height: h,
3041 depth_or_array_layers: 1,
3042 },
3043 mip_level_count: 1,
3044 sample_count: 1,
3045 dimension: wgpu::TextureDimension::D2,
3046 format: wgpu::TextureFormat::Rgba16Float,
3047 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
3048 view_formats: &[],
3049 });
3050 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3051
3052 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3053 label: Some("working space bind"),
3054 layout: self.display_layout.as_ref().unwrap(),
3055 entries: &[
3056 wgpu::BindGroupEntry {
3057 binding: 0,
3058 resource: wgpu::BindingResource::TextureView(&view),
3059 },
3060 wgpu::BindGroupEntry {
3061 binding: 1,
3062 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3063 },
3064 ],
3065 });
3066
3067 self.ws_tex = Some(tex);
3068 self.ws_view = Some(view);
3069 self.ws_bind = Some(bind);
3070 }
3071
3072 fn recreate_msaa_and_depth_stencil(&mut self) {
3073 if self.msaa_samples > 1 {
3074 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3075 label: Some("msaa color"),
3076 size: wgpu::Extent3d {
3077 width: self.output_width.max(1),
3078 height: self.output_height.max(1),
3079 depth_or_array_layers: 1,
3080 },
3081 mip_level_count: 1,
3082 sample_count: self.msaa_samples,
3083 dimension: wgpu::TextureDimension::D2,
3084 format: self.output_format,
3085 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3086 view_formats: &[],
3087 });
3088 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3089 self.msaa_tex = Some(tex);
3090 self.msaa_view = Some(view);
3091 } else {
3092 self.msaa_tex = None;
3093 self.msaa_view = None;
3094 }
3095
3096 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
3097 label: Some("depth-stencil (stencil clips)"),
3098 size: wgpu::Extent3d {
3099 width: self.output_width.max(1),
3100 height: self.output_height.max(1),
3101 depth_or_array_layers: 1,
3102 },
3103 mip_level_count: 1,
3104 sample_count: self.msaa_samples,
3105 dimension: wgpu::TextureDimension::D2,
3106 format: wgpu::TextureFormat::Depth24PlusStencil8,
3107 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3108 view_formats: &[],
3109 });
3110 self.depth_stencil_view = self
3111 .depth_stencil_tex
3112 .create_view(&wgpu::TextureViewDescriptor::default());
3113 }
3114
3115
3116
3117 fn get_or_create_layer(
3118 &mut self,
3119 layer_id: u32,
3120 width: u32,
3121 height: u32,
3122 rect: repose_core::Rect,
3123 ) {
3124 let needs_alloc = match self.layer_pool.get(&layer_id) {
3125 Some(lt) => lt.width != width || lt.height != height,
3126 None => true,
3127 };
3128 if !needs_alloc {
3129 if let Some(lt) = self.layer_pool.get_mut(&layer_id) {
3130 lt.rect_px = (rect.x, rect.y, rect.w, rect.h);
3131 }
3132 return;
3133 }
3134 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3135 label: Some("graphics layer"),
3136 size: wgpu::Extent3d {
3137 width: width.max(1),
3138 height: height.max(1),
3139 depth_or_array_layers: 1,
3140 },
3141 mip_level_count: 1,
3142 sample_count: 1,
3143 dimension: wgpu::TextureDimension::D2,
3144 format: self.output_format,
3145 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
3146 view_formats: &[],
3147 });
3148 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3149 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3150 label: Some("layer bind"),
3151 layout: &self.image_bind_layout_rgba,
3152 entries: &[
3153 wgpu::BindGroupEntry {
3154 binding: 0,
3155 resource: wgpu::BindingResource::TextureView(&view),
3156 },
3157 wgpu::BindGroupEntry {
3158 binding: 1,
3159 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
3160 },
3161 ],
3162 });
3163 let bind_linear = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3164 label: Some("layer bind linear"),
3165 layout: &self.image_bind_layout_rgba,
3166 entries: &[
3167 wgpu::BindGroupEntry {
3168 binding: 0,
3169 resource: wgpu::BindingResource::TextureView(&view),
3170 },
3171 wgpu::BindGroupEntry {
3172 binding: 1,
3173 resource: wgpu::BindingResource::Sampler(&self.layer_sampler_linear),
3174 },
3175 ],
3176 });
3177 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
3178 label: Some("graphics layer depth-stencil"),
3179 size: wgpu::Extent3d {
3180 width: width.max(1),
3181 height: height.max(1),
3182 depth_or_array_layers: 1,
3183 },
3184 mip_level_count: 1,
3185 sample_count: 1,
3186 dimension: wgpu::TextureDimension::D2,
3187 format: wgpu::TextureFormat::Depth24PlusStencil8,
3188 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3189 view_formats: &[],
3190 });
3191 let depth_stencil_view =
3192 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
3193 self.layer_pool.insert(
3194 layer_id,
3195 LayerTarget {
3196 texture: tex,
3197 view,
3198 bind,
3199 bind_linear,
3200 depth_stencil_tex,
3201 depth_stencil_view,
3202 width,
3203 height,
3204 rect_px: (rect.x, rect.y, rect.w, rect.h),
3205 },
3206 );
3207 }
3208
3209 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
3210 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3211 label: Some("atlas bind"),
3212 layout: &self.text_bind_layout,
3213 entries: &[
3214 wgpu::BindGroupEntry {
3215 binding: 0,
3216 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
3217 },
3218 wgpu::BindGroupEntry {
3219 binding: 1,
3220 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
3221 },
3222 ],
3223 })
3224 }
3225
3226 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
3227 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3228 label: Some("atlas bind color"),
3229 layout: &self.text_bind_layout,
3230 entries: &[
3231 wgpu::BindGroupEntry {
3232 binding: 0,
3233 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
3234 },
3235 wgpu::BindGroupEntry {
3236 binding: 1,
3237 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
3238 },
3239 ],
3240 })
3241 }
3242
3243 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
3244 let keyp = (key, px.to_bits());
3245 if let Some(info) = self.atlas_mask.map.get(&keyp) {
3246 return Some(*info);
3247 }
3248
3249 let gb = repose_text::rasterize(key, px)?;
3250 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
3251 return None;
3252 }
3253
3254 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
3255
3256 let w = gb.w.max(1);
3257 let h = gb.h.max(1);
3258
3259 if !self.alloc_space_mask(w, h) {
3260 self.grow_mask_and_rebuild();
3261 }
3262 if !self.alloc_space_mask(w, h) {
3263 return None;
3264 }
3265 let x = self.atlas_mask.next_x;
3266 let y = self.atlas_mask.next_y;
3267 self.atlas_mask.next_x += w + 1;
3268 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
3269
3270 let layout = wgpu::TexelCopyBufferLayout {
3271 offset: 0,
3272 bytes_per_row: Some(w),
3273 rows_per_image: Some(h),
3274 };
3275 let size = wgpu::Extent3d {
3276 width: w,
3277 height: h,
3278 depth_or_array_layers: 1,
3279 };
3280 self.queue.write_texture(
3281 wgpu::TexelCopyTextureInfoBase {
3282 texture: &self.atlas_mask.tex,
3283 mip_level: 0,
3284 origin: wgpu::Origin3d { x, y, z: 0 },
3285 aspect: wgpu::TextureAspect::All,
3286 },
3287 &coverage,
3288 layout,
3289 size,
3290 );
3291
3292 let info = GlyphInfo {
3293 u0: x as f32 / self.atlas_mask.size as f32,
3294 v0: y as f32 / self.atlas_mask.size as f32,
3295 u1: (x + w) as f32 / self.atlas_mask.size as f32,
3296 v1: (y + h) as f32 / self.atlas_mask.size as f32,
3297 w: w as f32,
3298 h: h as f32,
3299 bearing_x: 0.0,
3300 bearing_y: 0.0,
3301 advance: 0.0,
3302 };
3303 self.atlas_mask.map.insert(keyp, info);
3304 Some(info)
3305 }
3306
3307 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
3308 let keyp = (key, px.to_bits());
3309 if let Some(info) = self.atlas_color.map.get(&keyp) {
3310 return Some(*info);
3311 }
3312 let gb = repose_text::rasterize(key, px)?;
3313 if !matches!(gb.content, repose_text::SwashContent::Color) {
3314 return None;
3315 }
3316 let w = gb.w.max(1);
3317 let h = gb.h.max(1);
3318 if !self.alloc_space_color(w, h) {
3319 self.grow_color_and_rebuild();
3320 }
3321 if !self.alloc_space_color(w, h) {
3322 return None;
3323 }
3324 let x = self.atlas_color.next_x;
3325 let y = self.atlas_color.next_y;
3326 self.atlas_color.next_x += w + 1;
3327 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
3328
3329 let layout = wgpu::TexelCopyBufferLayout {
3330 offset: 0,
3331 bytes_per_row: Some(w * 4),
3332 rows_per_image: Some(h),
3333 };
3334 let size = wgpu::Extent3d {
3335 width: w,
3336 height: h,
3337 depth_or_array_layers: 1,
3338 };
3339 self.queue.write_texture(
3340 wgpu::TexelCopyTextureInfoBase {
3341 texture: &self.atlas_color.tex,
3342 mip_level: 0,
3343 origin: wgpu::Origin3d { x, y, z: 0 },
3344 aspect: wgpu::TextureAspect::All,
3345 },
3346 &gb.data,
3347 layout,
3348 size,
3349 );
3350 let info = GlyphInfo {
3351 u0: x as f32 / self.atlas_color.size as f32,
3352 v0: y as f32 / self.atlas_color.size as f32,
3353 u1: (x + w) as f32 / self.atlas_color.size as f32,
3354 v1: (y + h) as f32 / self.atlas_color.size as f32,
3355 w: w as f32,
3356 h: h as f32,
3357 bearing_x: 0.0,
3358 bearing_y: 0.0,
3359 advance: 0.0,
3360 };
3361 self.atlas_color.map.insert(keyp, info);
3362 Some(info)
3363 }
3364
3365 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
3366 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
3367 self.atlas_mask.next_x = 1;
3368 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
3369 self.atlas_mask.row_h = 0;
3370 }
3371 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
3372 return false;
3373 }
3374 true
3375 }
3376
3377 fn grow_mask_and_rebuild(&mut self) {
3378 let new_size = (self.atlas_mask.size * 2).min(4096);
3379 if new_size == self.atlas_mask.size {
3380 return;
3381 }
3382 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3383 label: Some("glyph atlas A8 (grown)"),
3384 size: wgpu::Extent3d {
3385 width: new_size,
3386 height: new_size,
3387 depth_or_array_layers: 1,
3388 },
3389 mip_level_count: 1,
3390 sample_count: 1,
3391 dimension: wgpu::TextureDimension::D2,
3392 format: wgpu::TextureFormat::R8Unorm,
3393 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3394 view_formats: &[],
3395 });
3396 self.atlas_mask.tex = tex;
3397 self.atlas_mask.view = self
3398 .atlas_mask
3399 .tex
3400 .create_view(&wgpu::TextureViewDescriptor::default());
3401 self.atlas_mask.size = new_size;
3402 self.atlas_mask.next_x = 1;
3403 self.atlas_mask.next_y = 1;
3404 self.atlas_mask.row_h = 0;
3405 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
3406 self.atlas_mask.map.clear();
3407 for (k, px_bits) in keys {
3408 let _ = self.upload_glyph_mask(k, f32::from_bits(px_bits));
3409 }
3410 }
3411
3412 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
3413 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
3414 self.atlas_color.next_x = 1;
3415 self.atlas_color.next_y += self.atlas_color.row_h + 1;
3416 self.atlas_color.row_h = 0;
3417 }
3418 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
3419 return false;
3420 }
3421 true
3422 }
3423
3424 fn grow_color_and_rebuild(&mut self) {
3425 let new_size = (self.atlas_color.size * 2).min(4096);
3426 if new_size == self.atlas_color.size {
3427 return;
3428 }
3429 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3430 label: Some("glyph atlas RGBA (grown)"),
3431 size: wgpu::Extent3d {
3432 width: new_size,
3433 height: new_size,
3434 depth_or_array_layers: 1,
3435 },
3436 mip_level_count: 1,
3437 sample_count: 1,
3438 dimension: wgpu::TextureDimension::D2,
3439 format: wgpu::TextureFormat::Rgba8UnormSrgb,
3440 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3441 view_formats: &[],
3442 });
3443 self.atlas_color.tex = tex;
3444 self.atlas_color.view = self
3445 .atlas_color
3446 .tex
3447 .create_view(&wgpu::TextureViewDescriptor::default());
3448 self.atlas_color.size = new_size;
3449 self.atlas_color.next_x = 1;
3450 self.atlas_color.next_y = 1;
3451 self.atlas_color.row_h = 0;
3452 let keys: Vec<(repose_text::GlyphKey, u32)> =
3453 self.atlas_color.map.keys().copied().collect();
3454 self.atlas_color.map.clear();
3455 for (k, px_bits) in keys {
3456 let _ = self.upload_glyph_color(k, f32::from_bits(px_bits));
3457 }
3458 }
3459}
3460
3461fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
3462 match brush {
3463 Brush::Solid(c) => (
3464 0u32,
3465 c.to_linear(),
3466 [0.0, 0.0, 0.0, 0.0],
3467 [0.0, 0.0],
3468 [0.0, 1.0],
3469 ),
3470 Brush::Linear {
3471 start,
3472 end,
3473 start_color,
3474 end_color,
3475 } => (
3476 1u32,
3477 start_color.to_linear(),
3478 end_color.to_linear(),
3479 [start.x, start.y],
3480 [end.x, end.y],
3481 ),
3482 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
3483 }
3484}
3485
3486fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
3487 match brush {
3488 Brush::Solid(c) => c.to_linear(),
3489 Brush::Linear { start_color, .. } => start_color.to_linear(),
3490 _ => [0.0; 4],
3491 }
3492}
3493
3494fn init_atlas_mask(device: &wgpu::Device) -> AtlasA8 {
3495 let size = 1024u32;
3496 let tex = device.create_texture(&wgpu::TextureDescriptor {
3497 label: Some("glyph atlas A8"),
3498 size: wgpu::Extent3d {
3499 width: size,
3500 height: size,
3501 depth_or_array_layers: 1,
3502 },
3503 mip_level_count: 1,
3504 sample_count: 1,
3505 dimension: wgpu::TextureDimension::D2,
3506 format: wgpu::TextureFormat::R8Unorm,
3507 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3508 view_formats: &[],
3509 });
3510 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3511 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
3512 label: Some("glyph atlas sampler A8"),
3513 address_mode_u: wgpu::AddressMode::ClampToEdge,
3514 address_mode_v: wgpu::AddressMode::ClampToEdge,
3515 address_mode_w: wgpu::AddressMode::ClampToEdge,
3516 mag_filter: wgpu::FilterMode::Linear,
3517 min_filter: wgpu::FilterMode::Linear,
3518 mipmap_filter: wgpu::MipmapFilterMode::Linear,
3519 ..Default::default()
3520 });
3521
3522 AtlasA8 {
3523 tex,
3524 view,
3525 sampler,
3526 size,
3527 next_x: 1,
3528 next_y: 1,
3529 row_h: 0,
3530 map: HashMap::new(),
3531 }
3532}
3533
3534fn init_atlas_color(device: &wgpu::Device) -> AtlasRGBA {
3535 let size = 1024u32;
3536 let tex = device.create_texture(&wgpu::TextureDescriptor {
3537 label: Some("glyph atlas RGBA"),
3538 size: wgpu::Extent3d {
3539 width: size,
3540 height: size,
3541 depth_or_array_layers: 1,
3542 },
3543 mip_level_count: 1,
3544 sample_count: 1,
3545 dimension: wgpu::TextureDimension::D2,
3546 format: wgpu::TextureFormat::Rgba8UnormSrgb,
3547 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3548 view_formats: &[],
3549 });
3550 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3551 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
3552 label: Some("glyph atlas sampler RGBA"),
3553 address_mode_u: wgpu::AddressMode::ClampToEdge,
3554 address_mode_v: wgpu::AddressMode::ClampToEdge,
3555 address_mode_w: wgpu::AddressMode::ClampToEdge,
3556 mag_filter: wgpu::FilterMode::Linear,
3557 min_filter: wgpu::FilterMode::Linear,
3558 mipmap_filter: wgpu::MipmapFilterMode::Linear,
3559 ..Default::default()
3560 });
3561 AtlasRGBA {
3562 tex,
3563 view,
3564 sampler,
3565 size,
3566 next_x: 1,
3567 next_y: 1,
3568 row_h: 0,
3569 map: HashMap::new(),
3570 }
3571}
3572
3573#[cfg(feature = "winit-surface")]
3574impl RenderBackend for WgpuSurfaceBackend {
3575 fn configure_surface(&mut self, width: u32, height: u32) {
3576 if width == 0 || height == 0 {
3577 return;
3578 }
3579 self.renderer.output_width = width;
3580 self.renderer.output_height = height;
3581 if let Some(ref mut config) = self.surface_config {
3582 config.width = width;
3583 config.height = height;
3584 }
3585 if let (Some(surface), Some(config)) = (self.surface.as_ref(), self.surface_config.as_ref()) {
3586 surface.configure(&self.renderer.device, config);
3587 }
3588 self.renderer.recreate_msaa_and_depth_stencil();
3589 self.renderer.recreate_working_space_texture();
3590 }
3591
3592 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
3593 let surface = self.surface.as_ref().expect("WgpuSurfaceBackend::frame() requires a surface (use from_device + render_to_view instead)");
3594 let surface_config = self.surface_config.as_ref().expect("surface_config required for frame()");
3595
3596 self.renderer.frame_index = self.renderer.frame_index.wrapping_add(1);
3597 self.renderer.slug_cache.next_frame();
3598
3599 if self.renderer.output_width == 0 || self.renderer.output_height == 0 {
3600 return;
3601 }
3602
3603 let mut retries = 0u32;
3604 const MAX_RETRIES: u32 = 4;
3605 let frame = loop {
3606 match surface.get_current_texture() {
3607 wgpu::CurrentSurfaceTexture::Success(f) => break f,
3608 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
3609 log::warn!("suboptimal surface; reconfiguring");
3610 surface.configure(&self.renderer.device, surface_config);
3611 break f;
3612 }
3613 wgpu::CurrentSurfaceTexture::Outdated => {
3614 retries += 1;
3615 if retries >= MAX_RETRIES {
3616 log::warn!("surface outdated persisted after {MAX_RETRIES} retries; skipping frame");
3617 return;
3618 }
3619 log::warn!("surface outdated; reconfiguring");
3620 surface.configure(&self.renderer.device, surface_config);
3621 }
3622 wgpu::CurrentSurfaceTexture::Lost => {
3623 retries += 1;
3624 if retries >= MAX_RETRIES {
3625 log::warn!("surface lost persisted after {MAX_RETRIES} retries; skipping frame");
3626 return;
3627 }
3628 log::warn!("surface lost; reconfiguring");
3629 surface.configure(&self.renderer.device, surface_config);
3630 }
3631 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
3632 request_frame();
3633 return;
3634 }
3635 wgpu::CurrentSurfaceTexture::Validation => {
3636 retries += 1;
3637 if retries >= MAX_RETRIES {
3638 log::warn!("surface validation persisted after {MAX_RETRIES} retries; skipping frame");
3639 return;
3640 }
3641 surface.configure(&self.renderer.device, surface_config);
3642 }
3643 }
3644 };
3645
3646 let swap_view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
3647 let mut encoder = self.renderer.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
3648 label: Some("frame encoder"),
3649 });
3650
3651 let clear_color = Some([
3652 scene.clear_color.0 as f64 / 255.0,
3653 scene.clear_color.1 as f64 / 255.0,
3654 scene.clear_color.2 as f64 / 255.0,
3655 scene.clear_color.3 as f64 / 255.0,
3656 ]);
3657
3658 self.renderer.render_scene_to_encoder(scene, &mut encoder, &swap_view, clear_color);
3659
3660 self.renderer.queue.submit(std::iter::once(encoder.finish()));
3661 if let Err(e) = catch_unwind(AssertUnwindSafe(|| self.renderer.queue.present(frame))) {
3662 log::warn!("queue.present panicked: {:?}", e);
3663 }
3664 }
3665}
3666
3667impl WgpuSceneRenderer {
3668 fn upload_mesh_geometry(
3669 &mut self,
3670 mesh: &repose_core::VectorMeshData,
3671 ) -> (u64, u32, u64, u32) {
3672 let verts: Vec<MeshVertex> = mesh
3673 .vertices
3674 .iter()
3675 .map(|v| MeshVertex {
3676 pos: v.pos,
3677 color: v.color,
3678 uv: v.uv,
3679 })
3680 .collect();
3681 let vbytes = bytemuck::cast_slice(&verts);
3682 self.mesh_verts.grow_to_fit(&self.device, vbytes.len() as u64);
3683 let (voff, _) = self.mesh_verts.alloc_write(&self.queue, vbytes);
3684 let ibytes = bytemuck::cast_slice(&mesh.indices);
3685 self.mesh_indices
3686 .grow_to_fit(&self.device, ibytes.len() as u64);
3687 let (ioff, _) = self.mesh_indices.alloc_write(&self.queue, ibytes);
3688 (voff, verts.len() as u32, ioff, mesh.indices.len() as u32)
3689 }
3690
3691 fn alloc_mesh_uniform(&mut self, u: MeshUniform) -> u64 {
3692 if self.mesh_uniform_head + MESH_UNIFORM_SLOT > MESH_UNIFORM_CAP {
3693 log::warn!("mesh uniform buffer overflow; regenerating");
3694 self.recreate_mesh_uniform_buffer();
3695 }
3696 let slot = self.mesh_uniform_head;
3697 self.queue
3698 .write_buffer(&self.mesh_uniform_buf, slot, bytemuck::bytes_of(&u));
3699 self.mesh_uniform_head = slot + MESH_UNIFORM_SLOT;
3700 slot
3701 }
3702
3703 fn recreate_mesh_uniform_buffer(&mut self) {
3704 let new_cap = self.mesh_uniform_head + MESH_UNIFORM_SLOT;
3705 self.mesh_uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3706 label: Some("mesh uniform buffer"),
3707 size: new_cap,
3708 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3709 mapped_at_creation: false,
3710 });
3711 self.mesh_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3712 label: Some("mesh uniform bind"),
3713 layout: &self.mesh_bind_layout,
3714 entries: &[wgpu::BindGroupEntry {
3715 binding: 0,
3716 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3717 buffer: &self.mesh_uniform_buf,
3718 offset: 0,
3719 size: NonZero::new(MESH_UNIFORM_SLOT),
3720 }),
3721 }],
3722 });
3723 self.mesh_uniform_head = 0;
3724 }
3725
3726 #[allow(clippy::too_many_arguments)]
3727 fn emit_vector_mesh(
3728 &mut self,
3729 current_transform: &Transform,
3730 mesh: &repose_core::VectorMeshData,
3731 transform: [f32; 6],
3732 paint: &repose_core::PaintDesc,
3733 cmds: &mut Vec<Cmd>,
3734 ) {
3735 let affine = combine_mesh_affine(current_transform, transform);
3736 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
3737 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(affine, paint));
3738 cmds.push(Cmd::VectorMesh {
3739 voff,
3740 vcnt,
3741 ioff,
3742 icnt,
3743 uoff,
3744 });
3745 }
3746
3747 pub fn render_scene_to_encoder(
3748 &mut self,
3749 scene: &Scene,
3750 encoder: &mut wgpu::CommandEncoder,
3751 target_view: &wgpu::TextureView,
3752 clear_color_override: Option<[f64; 4]>,
3753 ) {
3754 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
3755 let x0 = (x / fb_w) * 2.0 - 1.0;
3756 let y0 = 1.0 - (y / fb_h) * 2.0;
3757 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
3758 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
3759 let min_x = x0.min(x1);
3760 let min_y = y0.min(y1);
3761 let w_ndc = (x1 - x0).abs();
3762 let h_ndc = (y1 - y0).abs();
3763 [min_x, min_y, w_ndc, h_ndc]
3764 }
3765
3766 fn rect_to_instance_ndc(
3768 rect: repose_core::Rect,
3769 transform: &Transform,
3770 fb_w: f32,
3771 fb_h: f32,
3772 ) -> ([f32; 4], [f32; 2]) {
3773 let cx = rect.x + rect.w * 0.5;
3774 let cy = rect.y + rect.h * 0.5;
3775
3776 let sx = cx * transform.scale_x;
3778 let sy = cy * transform.scale_y;
3779 let cos_a = transform.rotate.cos();
3780 let sin_a = transform.rotate.sin();
3781 let tx = sx * cos_a - sy * sin_a + transform.translate_x;
3782 let ty = sx * sin_a + sy * cos_a + transform.translate_y;
3783
3784 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
3786 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
3787 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
3789 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
3790
3791 ([ndc_cx, ndc_cy, ndc_w, ndc_h], [cos_a, sin_a])
3792 }
3793
3794 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
3795 let mut x = r.x.floor() as i64;
3796 let mut y = r.y.floor() as i64;
3797 let fb_wi = fb_w as i64;
3798 let fb_hi = fb_h as i64;
3799 x = x.clamp(0, fb_wi.saturating_sub(1));
3800 y = y.clamp(0, fb_hi.saturating_sub(1));
3801 let w_req = r.w.ceil().max(1.0) as i64;
3802 let h_req = r.h.ceil().max(1.0) as i64;
3803 let w = (w_req).min(fb_wi - x).max(1);
3804 let h = (h_req).min(fb_hi - y).max(1);
3805 (x as u32, y as u32, w as u32, h as u32)
3806 }
3807
3808 let fb_w = self.output_width as f32;
3809 let fb_h = self.output_height as f32;
3810
3811 let mut passes: Vec<Pass> = Vec::with_capacity(1);
3812 let clear_color = clear_color_override.unwrap_or_else(|| {
3813 [
3814 scene.clear_color.0 as f64 / 255.0,
3815 scene.clear_color.1 as f64 / 255.0,
3816 scene.clear_color.2 as f64 / 255.0,
3817 scene.clear_color.3 as f64 / 255.0,
3818 ]
3819 });
3820 let mut current_pass: Pass = Pass {
3821 target: PassTarget::Surface,
3822 initial_scissor: (0, 0, self.output_width, self.output_height),
3823 clear_color: Some([
3824 clear_color[0] as f32,
3825 clear_color[1] as f32,
3826 clear_color[2] as f32,
3827 clear_color[3] as f32,
3828 ]),
3829 cmds: Vec::with_capacity(scene.nodes.len()),
3830 };
3831 let mut target_stack: Vec<PassTarget> = Vec::new();
3832 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
3833 let mut layer_blurs: Vec<(u32, f32, f32)> = Vec::new();
3834 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
3835
3836 struct Batch {
3837 rects: Vec<RectInstance>,
3838 borders: Vec<BorderInstance>,
3839 ellipses: Vec<EllipseInstance>,
3840 e_borders: Vec<EllipseBorderInstance>,
3841 arcs: Vec<ArcInstance>,
3842 masks: Vec<GlyphInstance>,
3843 colors: Vec<GlyphInstance>,
3844 nv12s: Vec<Nv12Instance>,
3845 }
3846
3847 impl Batch {
3848 fn new() -> Self {
3849 Self {
3850 rects: vec![],
3851 borders: vec![],
3852 ellipses: vec![],
3853 e_borders: vec![],
3854 arcs: vec![],
3855 masks: vec![],
3856 colors: vec![],
3857 nv12s: vec![],
3858 }
3859 }
3860
3861 fn is_empty(&self) -> bool {
3862 self.rects.is_empty()
3863 && self.borders.is_empty()
3864 && self.ellipses.is_empty()
3865 && self.e_borders.is_empty()
3866 && self.arcs.is_empty()
3867 && self.masks.is_empty()
3868 && self.colors.is_empty()
3869 && self.nv12s.is_empty()
3870 }
3871
3872 fn flush(
3873 &mut self,
3874 pipes: (
3875 &mut InstancedPipe<RectInstance>,
3876 &mut InstancedPipe<BorderInstance>,
3877 &mut InstancedPipe<EllipseInstance>,
3878 &mut InstancedPipe<EllipseBorderInstance>,
3879 &mut InstancedPipe<ArcInstance>,
3880 ),
3881 glyph_pipes: (
3882 &mut InstancedPipe<GlyphInstance>,
3883 &mut InstancedPipe<GlyphInstance>,
3884 ),
3885 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
3886 device: &wgpu::Device,
3887 queue: &wgpu::Queue,
3888 cmds: &mut Vec<Cmd>,
3889 ) {
3890 let (rects, borders, ellipses, e_borders, arcs) = pipes;
3891 let (masks, colors) = glyph_pipes;
3892
3893 macro_rules! flush_one {
3894 ($buf:ident, $pipe:expr, $variant:ident) => {
3895 if !self.$buf.is_empty() {
3896 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
3897 cmds.push(Cmd::$variant { off, cnt });
3898 }
3899 self.$buf.clear();
3900 }
3901 };
3902 }
3903
3904 flush_one!(rects, rects, Rect);
3905 flush_one!(borders, borders, Border);
3906 flush_one!(ellipses, ellipses, Ellipse);
3907 flush_one!(e_borders, e_borders, EllipseBorder);
3908 flush_one!(arcs, arcs, Arc);
3909 flush_one!(masks, masks, GlyphsMask);
3910 flush_one!(colors, colors, GlyphsColor);
3911
3912 if !self.nv12s.is_empty() {
3913 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
3914 let _ = (off, cnt);
3915 }
3916 self.nv12s.clear();
3917 }
3918 }
3919 }
3920
3921 self.rects.reset();
3922 self.borders.reset();
3923 self.ellipses.reset();
3924 self.ellipse_borders.reset();
3925 self.arcs.reset();
3926 self.glyph_mask.reset();
3927 self.glyph_color.reset();
3928 self.clip_ring.reset();
3929 self.blur_ring.reset();
3930 self.nv12.reset();
3931
3932 self.slug_ring.reset();
3933 self.mesh_verts.reset();
3934 self.mesh_indices.reset();
3935 self.mesh_uniform_head = 0;
3936 self.mesh_clip_stack.clear();
3937 let mut batch = Batch::new();
3938 let mut slug_verts_local: Vec<slug::TessVertex> = Vec::new();
3939 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
3940 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
3941 let root_clip_rect = repose_core::Rect {
3942 x: 0.0,
3943 y: 0.0,
3944 w: fb_w,
3945 h: fb_h,
3946 };
3947
3948 let mut current_prim: Option<&'static str> = None;
3949
3950 macro_rules! flush_if_prim_changed {
3951 ($prim:literal, $pipe:expr) => {
3952 if current_prim != Some($prim) {
3953 flush_batch!();
3954 current_prim = Some($prim);
3955 }
3956 };
3957 }
3958
3959 macro_rules! flush_batch {
3960 () => {
3961 if !batch.is_empty() {
3962 batch.flush(
3963 (
3964 &mut self.rects,
3965 &mut self.borders,
3966 &mut self.ellipses,
3967 &mut self.ellipse_borders,
3968 &mut self.arcs,
3969 ),
3970 (&mut self.glyph_mask, &mut self.glyph_color),
3971 &mut self.nv12,
3972 &self.device,
3973 &self.queue,
3974 &mut current_pass.cmds,
3975 )
3976 }
3977 };
3978 }
3979 for node in &scene.nodes {
3980 let t_identity = Transform::identity();
3981 let current_transform = transform_stack.last().unwrap_or(&t_identity);
3982
3983 match node {
3984 SceneNode::Rect {
3985 rect,
3986 brush,
3987 radius,
3988 } => {
3989 flush_if_prim_changed!("rect", &self.rects);
3990 let (ndc, sin_cos) = rect_to_instance_ndc(
3991 *rect,
3992 current_transform,
3993 current_target_size.0,
3994 current_target_size.1,
3995 );
3996 let (brush_type, color0, color1, grad_start, grad_end) =
3997 brush_to_instance_fields(brush);
3998 batch.rects.push(RectInstance {
3999 xywh: ndc,
4000 radii: *radius,
4001 brush_type,
4002 _pad: [0.0; 3],
4003 color0,
4004 color1,
4005 grad_start,
4006 grad_end,
4007 sin_cos,
4008 });
4009 }
4010 SceneNode::Border {
4011 rect,
4012 color,
4013 width,
4014 radius,
4015 } => {
4016 flush_if_prim_changed!("border", &self.borders);
4017 let (ndc, sin_cos) = rect_to_instance_ndc(
4018 *rect,
4019 current_transform,
4020 current_target_size.0,
4021 current_target_size.1,
4022 );
4023 batch.borders.push(BorderInstance {
4024 xywh: ndc,
4025 radii: *radius,
4026 stroke: *width,
4027 color: color.to_linear(),
4028 sin_cos,
4029 });
4030 }
4031 SceneNode::Ellipse { rect, brush } => {
4032 flush_if_prim_changed!("ellipse", &self.ellipses);
4033 let (ndc, sin_cos) = rect_to_instance_ndc(
4034 *rect,
4035 current_transform,
4036 current_target_size.0,
4037 current_target_size.1,
4038 );
4039 let color = brush_to_solid_color(brush);
4040 batch.ellipses.push(EllipseInstance {
4041 xywh: ndc,
4042 color,
4043 sin_cos,
4044 });
4045 }
4046 SceneNode::EllipseBorder { rect, color, width } => {
4047 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
4048 let (ndc, sin_cos) = rect_to_instance_ndc(
4049 *rect,
4050 current_transform,
4051 current_target_size.0,
4052 current_target_size.1,
4053 );
4054 let pad_px = *width * 0.5 + 2.0;
4055 let pad = (pad_px / current_target_size.0) * 2.0;
4056 batch.e_borders.push(EllipseBorderInstance {
4057 xywh: ndc,
4058 stroke: *width,
4059 pad,
4060 color: color.to_linear(),
4061 sin_cos,
4062 });
4063 }
4064 SceneNode::Arc {
4065 rect,
4066 start_angle,
4067 sweep_angle,
4068 stroke_width,
4069 color,
4070 cap,
4071 } => {
4072 flush_if_prim_changed!("arc", &self.arcs);
4073 let (ndc, sin_cos) = rect_to_instance_ndc(
4074 *rect,
4075 current_transform,
4076 current_target_size.0,
4077 current_target_size.1,
4078 );
4079 let pad_px = *stroke_width * 0.5 + 2.0;
4080 let pad = (pad_px / current_target_size.0) * 2.0;
4081 let cap_val = match cap {
4082 StrokeCap::Butt => 0.0,
4083 StrokeCap::Round => 1.0,
4084 StrokeCap::Square => 2.0,
4085 };
4086 batch.arcs.push(ArcInstance {
4087 xywh: ndc,
4088 start_angle: *start_angle,
4089 sweep_angle: *sweep_angle,
4090 stroke: *stroke_width,
4091 pad,
4092 color: color.to_linear(),
4093 sin_cos,
4094 cap: cap_val,
4095 });
4096 }
4097 SceneNode::Text {
4098 rect,
4099 text,
4100 color,
4101 size,
4102 font_family,
4103 text_align: _,
4104 font_weight,
4105 font_style,
4106 text_decoration,
4107 letter_spacing,
4108 line_height: _,
4109 extra_style,
4110 url: _,
4111 font_variation_settings,
4112 } => {
4113 flush_batch!(); let px = *size;
4116 let lh_ratio = rect.h / px;
4117 let fw = font_weight.0;
4118 let fs = if *font_style == FontStyle::Italic {
4119 1
4120 } else {
4121 0
4122 };
4123 let shaped = repose_text::shape_line(
4124 text.as_ref(),
4125 px,
4126 lh_ratio,
4127 *font_family,
4128 fw,
4129 fs,
4130 *letter_spacing,
4131 font_variation_settings.as_deref(),
4132 );
4133 let baseline_y = shaped.first().map(|g| rect.y + g.y);
4134
4135 let cos_a = current_transform.rotate.cos();
4136 let sin_a = current_transform.rotate.sin();
4137 let has_rotation = current_transform.rotate != 0.0;
4138
4139 let pivot_x = rect.x + rect.w * 0.5;
4141 let pivot_y = rect.y + rect.h * 0.5;
4142
4143 let make_glyph_instance =
4145 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 2]) {
4146 if has_rotation {
4147 let corners =
4148 [(gx, gy), (gx + gw, gy), (gx + gw, gy + gh), (gx, gy + gh)];
4149 let mut min_x = f32::MAX;
4150 let mut max_x = f32::MIN;
4151 let mut min_y = f32::MAX;
4152 let mut max_y = f32::MIN;
4153 for &(x, y) in &corners {
4154 let dx = x - pivot_x;
4155 let dy = y - pivot_y;
4156 let rx = pivot_x + dx * cos_a - dy * sin_a;
4157 let ry = pivot_y + dx * sin_a + dy * cos_a;
4158 min_x = min_x.min(rx);
4159 max_x = max_x.max(rx);
4160 min_y = min_y.min(ry);
4161 max_y = max_y.max(ry);
4162 }
4163 let bb_w = max_x - min_x;
4164 let bb_h = max_y - min_y;
4165 let ndc_tl = to_ndc(
4166 min_x,
4167 min_y,
4168 bb_w,
4169 bb_h,
4170 current_target_size.0,
4171 current_target_size.1,
4172 );
4173 let ndc = [
4174 ndc_tl[0] + ndc_tl[2] * 0.5,
4175 ndc_tl[1] + ndc_tl[3] * 0.5,
4176 ndc_tl[2],
4177 ndc_tl[3],
4178 ];
4179 (ndc, [cos_a, sin_a])
4180 } else {
4181 let (sx, sy) = if current_transform.scale_x == 1.0
4183 && current_transform.scale_y == 1.0
4184 {
4185 (gx.round(), gy.round())
4186 } else {
4187 (gx, gy)
4188 };
4189 rect_to_instance_ndc(
4190 repose_core::Rect {
4191 x: sx,
4192 y: sy,
4193 w: gw,
4194 h: gh,
4195 },
4196 current_transform,
4197 current_target_size.0,
4198 current_target_size.1,
4199 )
4200 }
4201 };
4202
4203 let baseline_shift_y: f32 = px * extra_style.baseline_shift.0;
4204
4205 let (
4206 is_stroke,
4207 stroke_width,
4208 stroke_cap,
4209 stroke_join,
4210 stroke_miter,
4211 stroke_path_effect,
4212 ) = match &extra_style.draw_style {
4213 repose_core::DrawStyle::Stroke {
4214 width,
4215 cap,
4216 join,
4217 miter,
4218 path_effect,
4219 } => (true, *width, *cap, *join, *miter, path_effect.clone()),
4220 _ => (
4221 false,
4222 0.0,
4223 repose_core::StrokeCap::Butt,
4224 repose_core::StrokeJoin::Miter,
4225 4.0,
4226 None,
4227 ),
4228 };
4229 let stroke_tess_key = if is_stroke {
4230 Some(slug::StrokeTessKey::new(
4231 stroke_width,
4232 stroke_cap,
4233 stroke_join,
4234 stroke_miter,
4235 &stroke_path_effect,
4236 ))
4237 } else {
4238 None
4239 };
4240
4241 for sg in shaped {
4242 let gx = rect.x + sg.x + sg.bearing_x;
4243 let gy = rect.y + sg.y - sg.bearing_y + baseline_shift_y;
4244
4245 if self.slug_enabled {
4247 let ck = repose_text::lookup_cache_key(sg.key, sg.px);
4248 if let Some(ref ck) = ck {
4249 let need_tessellate = self.slug_cache.get(ck).map_or(true, |g| {
4251 if is_stroke {
4252 let key = stroke_tess_key.as_ref().unwrap();
4253 !g.stroke_variants.contains_key(key)
4254 } else {
4255 g.fill_vertices.is_none()
4256 }
4257 });
4258 if need_tessellate {
4259 if let Some((ck2, commands)) =
4260 repose_text::lookup_and_extract_outline(sg.key, sg.px)
4261 {
4262 let font_size_px = f32::from_bits(ck2.font_size_bits);
4263 if is_stroke {
4264 self.slug_cache.get_or_insert_stroke(
4265 ck2,
4266 font_size_px,
4267 &commands,
4268 stroke_width,
4269 stroke_cap,
4270 stroke_join,
4271 stroke_miter,
4272 &stroke_path_effect,
4273 );
4274 } else {
4275 self.slug_cache.get_or_insert(
4276 ck2,
4277 font_size_px,
4278 &commands,
4279 );
4280 }
4281 }
4282 } else {
4283 self.slug_cache.touch(ck);
4284 }
4285 }
4286 if let Some(entry) = ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
4287 {
4288 let ox = rect.x + sg.x;
4289 let oy = rect.y + sg.y + baseline_shift_y;
4290 let scx = current_transform.scale_x;
4291 let scy = current_transform.scale_y;
4292 let ttx = current_transform.translate_x;
4293 let tty = current_transform.translate_y;
4294
4295 let tf = |x: f32, y: f32| -> (f32, f32) {
4296 if has_rotation {
4297 let dx = x - pivot_x;
4298 let dy = y - pivot_y;
4299 let rx = pivot_x + dx * cos_a - dy * sin_a;
4300 let ry = pivot_y + dx * sin_a + dy * cos_a;
4301 (rx, ry)
4302 } else {
4303 (x * scx + ttx, y * scy + tty)
4304 }
4305 };
4306
4307 let tw = current_target_size.0;
4308 let th = current_target_size.1;
4309
4310 let verts = if is_stroke {
4311 let key = stroke_tess_key.as_ref().unwrap();
4312 entry
4313 .stroke_variants
4314 .get(key)
4315 .map(|v| v.as_slice())
4316 .unwrap_or(&[])
4317 } else {
4318 entry.fill_vertices.as_deref().unwrap_or(&[])
4319 };
4320
4321 for &v in verts {
4322 let (sx, sy) = tf(ox + v[0] * px, oy - v[1] * px);
4323 let ndc_x = sx / tw * 2.0 - 1.0;
4324 let ndc_y = -(sy / th) * 2.0 + 1.0;
4325 slug_verts_local.push(slug::TessVertex {
4326 ndc_pos: [ndc_x, ndc_y],
4327 color: color.to_linear(),
4328 });
4329 }
4330
4331 if is_stroke {
4332 continue;
4334 }
4335 continue;
4336 }
4337 }
4338
4339 if is_stroke {
4341 continue;
4342 }
4343
4344 if let Some(info) = self.upload_glyph_color(sg.key, sg.px) {
4346 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
4347 batch.colors.push(GlyphInstance {
4348 xywh: ndc,
4349 uv: [info.u0, info.v1, info.u1, info.v0],
4350 color: color.to_linear(),
4351 sin_cos,
4352 });
4353 } else if let Some(info) = self.upload_glyph_mask(sg.key, sg.px) {
4354 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
4355 batch.masks.push(GlyphInstance {
4356 xywh: ndc,
4357 uv: [info.u0, info.v1, info.u1, info.v0],
4358 color: color.to_linear(),
4359 sin_cos,
4360 });
4361 }
4362 }
4363
4364 if !slug_verts_local.is_empty() {
4366 let bytes = bytemuck::cast_slice(&slug_verts_local);
4367 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
4368 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
4369 current_pass.cmds.push(Cmd::GlyphsVector {
4370 off,
4371 cnt: slug_verts_local.len() as u32,
4372 });
4373 slug_verts_local.clear();
4374 }
4375
4376 if (text_decoration.underline || text_decoration.strikethrough)
4378 && let Some(baseline_y) = baseline_y
4379 {
4380 flush_batch!();
4381 current_prim = Some("rect");
4382 let deco_color = text_decoration.color.unwrap_or(*color);
4383 let thickness = (px * 0.07).max(1.0);
4384
4385 if text_decoration.underline {
4386 let dy = baseline_y + px * 0.1;
4387 let (ndc, sin_cos) = rect_to_instance_ndc(
4388 repose_core::Rect {
4389 x: rect.x,
4390 y: dy,
4391 w: rect.w,
4392 h: thickness,
4393 },
4394 current_transform,
4395 current_target_size.0,
4396 current_target_size.1,
4397 );
4398 batch.rects.push(RectInstance {
4399 xywh: ndc,
4400 radii: [0.0; 4],
4401 brush_type: 0,
4402 _pad: [0.0; 3],
4403 color0: deco_color.to_linear(),
4404 color1: [0.0; 4],
4405 grad_start: [0.0; 2],
4406 grad_end: [0.0; 2],
4407 sin_cos,
4408 });
4409 }
4410 if text_decoration.strikethrough {
4411 let sy = baseline_y - px * 0.3;
4412 let (ndc, sin_cos) = rect_to_instance_ndc(
4413 repose_core::Rect {
4414 x: rect.x,
4415 y: sy,
4416 w: rect.w,
4417 h: thickness,
4418 },
4419 current_transform,
4420 current_target_size.0,
4421 current_target_size.1,
4422 );
4423 batch.rects.push(RectInstance {
4424 xywh: ndc,
4425 radii: [0.0; 4],
4426 brush_type: 0,
4427 _pad: [0.0; 3],
4428 color0: deco_color.to_linear(),
4429 color1: [0.0; 4],
4430 grad_start: [0.0; 2],
4431 grad_end: [0.0; 2],
4432 sin_cos,
4433 });
4434 }
4435 }
4436 }
4437 SceneNode::Image {
4438 rect,
4439 handle,
4440 tint,
4441 fit,
4442 } => {
4443 flush_batch!();
4444
4445 let (img_w, img_h, is_nv12) = if let Some(t) = self.images.get_mut(handle) {
4447 match t {
4448 ImageTex::Rgba {
4449 w,
4450 h,
4451 last_used_frame,
4452 ..
4453 } => {
4454 *last_used_frame = self.frame_index;
4455 (*w, *h, false)
4456 }
4457 ImageTex::Nv12 {
4458 w,
4459 h,
4460 last_used_frame,
4461 ..
4462 } => {
4463 *last_used_frame = self.frame_index;
4464 (*w, *h, true)
4465 }
4466 }
4467 } else {
4468 log::warn!("Image handle {} not found", handle);
4469 continue;
4470 };
4471
4472 let src_w = img_w as f32;
4473 let src_h = img_h as f32;
4474 let transformed = current_transform.apply_to_rect(*rect);
4475 let dst_w = transformed.w.max(0.0);
4476 let dst_h = transformed.h.max(0.0);
4477 if dst_w <= 0.0 || dst_h <= 0.0 {
4478 continue;
4479 }
4480
4481 let (xywh_ndc, uv_rect) = match fit {
4482 repose_core::view::ImageFit::Contain => {
4483 let scale = (dst_w / src_w).min(dst_h / src_h);
4484 let w = src_w * scale;
4485 let h = src_h * scale;
4486 let x = transformed.x + (dst_w - w) * 0.5;
4487 let y = transformed.y + (dst_h - h) * 0.5;
4488 (
4489 to_ndc(x, y, w, h, current_target_size.0, current_target_size.1),
4490 [0.0, 1.0, 1.0, 0.0],
4491 )
4492 }
4493 repose_core::view::ImageFit::Cover => {
4494 let scale = (dst_w / src_w).max(dst_h / src_h);
4495 let content_w = src_w * scale;
4496 let content_h = src_h * scale;
4497 let overflow_x = (content_w - dst_w) * 0.5;
4498 let overflow_y = (content_h - dst_h) * 0.5;
4499 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
4500 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
4501 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
4502 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
4503 (
4504 to_ndc(
4505 transformed.x,
4506 transformed.y,
4507 dst_w,
4508 dst_h,
4509 current_target_size.0,
4510 current_target_size.1,
4511 ),
4512 [u0, 1.0 - v1, u1, 1.0 - v0],
4513 )
4514 }
4515 repose_core::view::ImageFit::FitWidth => {
4516 let scale = dst_w / src_w;
4517 let w = dst_w;
4518 let h = src_h * scale;
4519 let y = transformed.y + (dst_h - h) * 0.5;
4520 (
4521 to_ndc(
4522 transformed.x,
4523 y,
4524 w,
4525 h,
4526 current_target_size.0,
4527 current_target_size.1,
4528 ),
4529 [0.0, 1.0, 1.0, 0.0],
4530 )
4531 }
4532 repose_core::view::ImageFit::FitHeight => {
4533 let scale = dst_h / src_h;
4534 let w = src_w * scale;
4535 let h = dst_h;
4536 let x = transformed.x + (dst_w - w) * 0.5;
4537 (
4538 to_ndc(
4539 x,
4540 transformed.y,
4541 w,
4542 h,
4543 current_target_size.0,
4544 current_target_size.1,
4545 ),
4546 [0.0, 1.0, 1.0, 0.0],
4547 )
4548 }
4549 _ => ([0.0; 4], [0.0; 4]),
4550 };
4551
4552 let ndc_center = [
4554 xywh_ndc[0] + xywh_ndc[2] * 0.5,
4555 xywh_ndc[1] + xywh_ndc[3] * 0.5,
4556 xywh_ndc[2],
4557 xywh_ndc[3],
4558 ];
4559
4560 if is_nv12 {
4561 let uv_x_offset = if let Some(ImageTex::Nv12 { w, color_info, .. }) =
4562 self.images.get(handle)
4563 {
4564 match color_info.chroma_siting {
4565 ChromaSiting::Center | ChromaSiting::TopLeft => 0.0,
4566 ChromaSiting::Left => -1.0 / *w as f32,
4567 }
4568 } else {
4569 0.0
4570 };
4571
4572 let inst = Nv12Instance {
4573 xywh: ndc_center,
4574 uv: uv_rect,
4575 color: tint.to_linear(),
4576 uv_x_offset,
4577 sin_cos: [1.0, 0.0],
4578 _pad: [0.0],
4579 };
4580 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
4581 {
4582 current_pass.cmds.push(Cmd::ImageNv12 {
4583 off,
4584 cnt: 1,
4585 handle: *handle,
4586 });
4587 }
4588 } else {
4589 let inst = GlyphInstance {
4591 xywh: ndc_center,
4592 uv: uv_rect,
4593 color: tint.to_linear(),
4594 sin_cos: [1.0, 0.0],
4595 };
4596 if let Some((off, _)) =
4597 self.glyph_color.upload(&self.device, &self.queue, &[inst])
4598 {
4599 current_pass.cmds.push(Cmd::ImageRgba {
4600 off,
4601 cnt: 1,
4602 handle: *handle,
4603 });
4604 }
4605 }
4606 }
4607 SceneNode::PushClip { rect, radius, op } => {
4608 flush_batch!(); let is_diff = matches!(op, repose_core::ClipOp::Difference);
4611
4612 let t_identity = Transform::identity();
4613 let current_transform = transform_stack.last().unwrap_or(&t_identity);
4614 let transformed = current_transform.apply_to_rect(*rect);
4615
4616 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
4617 let next_scissor = if is_diff {
4618 top
4619 } else {
4620 intersect(top, transformed)
4621 };
4622 scissor_stack.push(next_scissor);
4623 let scissor = to_scissor(
4624 &next_scissor,
4625 current_target_size.0 as u32,
4626 current_target_size.1 as u32,
4627 );
4628
4629 let clip_ndc_tl = to_ndc(
4630 transformed.x,
4631 transformed.y,
4632 transformed.w,
4633 transformed.h,
4634 current_target_size.0,
4635 current_target_size.1,
4636 );
4637 let inst = ClipInstance {
4638 xywh: [
4639 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
4640 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
4641 clip_ndc_tl[2],
4642 clip_ndc_tl[3],
4643 ],
4644 radii: *radius,
4645 sin_cos: [1.0, 0.0],
4646 };
4647 let bytes = bytemuck::bytes_of(&inst);
4648 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
4649 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
4650
4651 let rounded = radius.iter().any(|&r| r > 0.5);
4652
4653 current_pass.cmds.push(Cmd::ClipPush {
4654 off,
4655 cnt: 1,
4656 scissor,
4657 difference: is_diff,
4658 rounded,
4659 });
4660 }
4661 SceneNode::PopClip => {
4662 flush_batch!();
4663
4664 if !scissor_stack.is_empty() {
4665 scissor_stack.pop();
4666 } else {
4667 log::warn!("PopClip with empty stack");
4668 }
4669
4670 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
4671 let scissor = to_scissor(
4672 &top,
4673 current_target_size.0 as u32,
4674 current_target_size.1 as u32,
4675 );
4676 current_pass.cmds.push(Cmd::ClipPop { scissor });
4677 }
4678 SceneNode::Shadow {
4679 rect,
4680 radius,
4681 elevation: _,
4682 color,
4683 } => {
4684 flush_if_prim_changed!("rect", &self.rects);
4685 let (ndc, sin_cos) = rect_to_instance_ndc(
4686 *rect,
4687 current_transform,
4688 current_target_size.0,
4689 current_target_size.1,
4690 );
4691 let (brush_type, color0, _color1, _grad_start, _grad_end) =
4692 brush_to_instance_fields(&Brush::Solid(*color));
4693 batch.rects.push(RectInstance {
4694 xywh: ndc,
4695 radii: *radius,
4696 brush_type,
4697 _pad: [0.0; 3],
4698 color0,
4699 color1: [0.0; 4],
4700 grad_start: [0.0; 2],
4701 grad_end: [0.0; 2],
4702 sin_cos,
4703 });
4704 }
4705 SceneNode::PushTransform { transform } => {
4706 flush_batch!(); let combined = current_transform.combine(transform);
4708 transform_stack.push(combined);
4709 }
4710 SceneNode::PopTransform => {
4711 flush_batch!(); transform_stack.pop();
4713 }
4714 SceneNode::BeginLayer {
4715 rect,
4716 layer_id,
4717 alpha,
4718 blur_radius_x,
4719 blur_radius_y,
4720 rectangle_edge: _,
4721 } => {
4722 flush_batch!();
4723 let w = (rect.w.round().max(1.0)) as u32;
4726 let h = (rect.h.round().max(1.0)) as u32;
4727 let prev_target = current_pass.target;
4729 let prev_scissor = current_pass.initial_scissor;
4730 let saved = std::mem::replace(
4731 &mut current_pass,
4732 Pass {
4733 target: PassTarget::Layer(*layer_id),
4734 initial_scissor: (0, 0, w, h),
4735 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
4736 cmds: Vec::new(),
4737 },
4738 );
4739 passes.push(saved);
4740 target_stack.push(prev_target);
4741 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
4745 current_target_size = (w as f32, h as f32);
4746 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
4747 if *blur_radius_x > 0.0 || *blur_radius_y > 0.0 {
4749 layer_blurs.push((*layer_id, *blur_radius_x, *blur_radius_y));
4750 }
4751 }
4752 SceneNode::EndLayer { layer_id } => {
4753 flush_batch!();
4754 let saved = std::mem::replace(
4756 &mut current_pass,
4757 Pass {
4758 target: target_stack.pop().unwrap_or(PassTarget::Surface),
4759 initial_scissor: (0, 0, self.output_width, self.output_height),
4760 clear_color: None, cmds: Vec::new(),
4762 },
4763 );
4764 passes.push(saved);
4765 current_target_size = (fb_w, fb_h);
4766 if let Some((_, layer_alpha, _)) = layer_alphas
4768 .iter()
4769 .find(|(id, _, _)| id == layer_id)
4770 .copied()
4771 {
4772 let layer = self.layer_pool.get(layer_id).expect("layer target");
4773 let ndc_tl = to_ndc(
4774 layer.rect_px.0,
4775 layer.rect_px.1,
4776 layer.rect_px.2,
4777 layer.rect_px.3,
4778 fb_w,
4779 fb_h,
4780 );
4781 let uv_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
4782 let uv_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
4783 let blur_px_val = layer_blurs
4785 .iter()
4786 .find(|(id, _, _)| id == layer_id)
4787 .map(|(_, bx, by)| (*bx, *by));
4788 if let Some((blur_x, blur_y)) =
4789 blur_px_val.filter(|(bx, by)| *bx > 0.0 || *by > 0.0)
4790 {
4791 let bw_uv = (blur_x * 1.5) / layer.width.max(1) as f32;
4793 let bh_uv = (blur_y * 1.5) / layer.height.max(1) as f32;
4794 let inst = BlurInstance {
4795 xywh: [
4796 ndc_tl[0] + ndc_tl[2] * 0.5,
4797 ndc_tl[1] + ndc_tl[3] * 0.5,
4798 ndc_tl[2],
4799 ndc_tl[3],
4800 ],
4801 uv: [0.0, 0.0, uv_u1, uv_v1],
4802 color: [1.0, 1.0, 1.0, layer_alpha],
4803 blur_uv: [bw_uv, bh_uv],
4804 sin_cos: [1.0, 0.0],
4805 };
4806 self.blur_ring.grow_to_fit(
4807 &self.device,
4808 std::mem::size_of::<BlurInstance>() as u64,
4809 );
4810 let bytes = bytemuck::bytes_of(&inst);
4811 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
4812 current_pass.cmds.push(Cmd::CompositeBlur {
4813 off,
4814 cnt: 1,
4815 layer_id: *layer_id,
4816 });
4817 } else {
4818 let inst = GlyphInstance {
4820 xywh: [
4821 ndc_tl[0] + ndc_tl[2] * 0.5,
4822 ndc_tl[1] + ndc_tl[3] * 0.5,
4823 ndc_tl[2],
4824 ndc_tl[3],
4825 ],
4826 uv: [0.0, uv_v1, uv_u1, 0.0],
4827 color: [1.0, 1.0, 1.0, layer_alpha],
4828 sin_cos: [1.0, 0.0],
4829 };
4830 if let Some((off, cnt)) =
4831 self.glyph_color.upload(&self.device, &self.queue, &[inst])
4832 {
4833 current_pass.cmds.push(Cmd::CompositeLayer {
4834 off,
4835 cnt,
4836 layer_id: *layer_id,
4837 alpha: layer_alpha,
4838 });
4839 }
4840 }
4841 }
4842 }
4843 SceneNode::CompositeShadow {
4844 layer_id,
4845 blur_px,
4846 offset_px,
4847 color,
4848 } => {
4849 flush_batch!();
4850 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
4851 let sx = layer.rect_px.0 + offset_px.0;
4853 let sy = layer.rect_px.1 + offset_px.1;
4854 let sw = layer.rect_px.2;
4855 let sh = layer.rect_px.3;
4856 let bw_uv = (blur_px * 1.5) / layer.width.max(1) as f32;
4859 let bh_uv = (blur_px * 1.5) / layer.height.max(1) as f32;
4860 let shadow_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
4861 let shadow_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
4862 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
4863 let inst = BlurInstance {
4864 xywh: [
4865 ndc_tl[0] + ndc_tl[2] * 0.5,
4866 ndc_tl[1] + ndc_tl[3] * 0.5,
4867 ndc_tl[2],
4868 ndc_tl[3],
4869 ],
4870 uv: [0.0, 0.0, shadow_u1, shadow_v1],
4871 color: [
4872 color.0 as f32 / 255.0,
4873 color.1 as f32 / 255.0,
4874 color.2 as f32 / 255.0,
4875 color.3 as f32 / 255.0,
4876 ],
4877 blur_uv: [bw_uv, bh_uv],
4878 sin_cos: [1.0, 0.0],
4879 };
4880 self.blur_ring
4881 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
4882 let bytes = bytemuck::bytes_of(&inst);
4883 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
4884 current_pass.cmds.push(Cmd::CompositeShadow {
4885 off,
4886 cnt: 1,
4887 layer_id: *layer_id,
4888 });
4889 }
4890 }
4891 SceneNode::VectorMesh {
4892 mesh,
4893 transform,
4894 paint,
4895 clip: _,
4896 blend: _,
4897 } => {
4898 flush_batch!();
4899 let t_identity = Transform::identity();
4900 let current_transform = transform_stack.last().unwrap_or(&t_identity);
4901 self.emit_vector_mesh(
4902 current_transform,
4903 mesh,
4904 *transform,
4905 paint,
4906 &mut current_pass.cmds,
4907 );
4908 }
4909 SceneNode::VectorOverlay { meshes } => {
4910 flush_batch!();
4911 for m in meshes.iter() {
4912 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(m);
4913 let uoff = self.alloc_mesh_uniform(MeshUniform::identity());
4914 current_pass.cmds.push(Cmd::VectorOverlay {
4915 voff,
4916 vcnt,
4917 ioff,
4918 icnt,
4919 uoff,
4920 });
4921 }
4922 }
4923 SceneNode::PushVectorClip { mesh } => {
4924 flush_batch!();
4925 let t_identity = Transform::identity();
4926 let current_transform = transform_stack.last().unwrap_or(&t_identity);
4927 let affine =
4928 combine_mesh_affine(current_transform, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
4929 let aabb = mesh_aabb(mesh, affine);
4930 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
4931 let next = intersect(top, aabb);
4932 scissor_stack.push(next);
4933 let scissor = to_scissor(
4934 &next,
4935 current_target_size.0 as u32,
4936 current_target_size.1 as u32,
4937 );
4938 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
4939 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(
4940 affine,
4941 &repose_core::PaintDesc::Solid,
4942 ));
4943 current_pass.cmds.push(Cmd::VectorClipPush {
4944 voff,
4945 vcnt,
4946 ioff,
4947 icnt,
4948 uoff,
4949 scissor,
4950 });
4951 self.mesh_clip_stack.push((voff, vcnt, ioff, icnt, uoff));
4952 }
4953 SceneNode::PopVectorClip => {
4954 flush_batch!();
4955 if !scissor_stack.is_empty() {
4956 scissor_stack.pop();
4957 } else {
4958 log::warn!("PopVectorClip with empty scissor stack");
4959 }
4960 if let Some((voff, vcnt, ioff, icnt, uoff)) = self.mesh_clip_stack.pop() {
4961 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
4962 let scissor = to_scissor(
4963 &top,
4964 current_target_size.0 as u32,
4965 current_target_size.1 as u32,
4966 );
4967 current_pass.cmds.push(Cmd::VectorClipPop {
4968 voff,
4969 vcnt,
4970 ioff,
4971 icnt,
4972 uoff,
4973 scissor,
4974 });
4975 } else {
4976 log::warn!("PopVectorClip with empty clip stack");
4977 }
4978 }
4979 _ => {}
4980 }
4981 }
4982
4983 flush_batch!();
4984
4985 passes.push(current_pass);
4987
4988 let globals_bytes = std::mem::size_of::<Globals>() as u64;
4989 let globals_staging = self.device.create_buffer(&wgpu::BufferDescriptor {
4990 label: Some("globals staging"),
4991 size: (passes.len().max(1) as u64) * globals_bytes,
4992 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
4993 mapped_at_creation: false,
4994 });
4995 for (i, pass) in passes.iter().enumerate() {
4996 let (target_w, target_h) = match pass.target {
4997 PassTarget::Surface => (fb_w, fb_h),
4998 PassTarget::Layer(layer_id) => {
4999 let lt = self.layer_pool.get(&layer_id);
5000 (
5001 lt.map_or(fb_w, |l| l.width as f32),
5002 lt.map_or(fb_h, |l| l.height as f32),
5003 )
5004 }
5005 };
5006 self.queue.write_buffer(
5007 &globals_staging,
5008 (i as u64) * globals_bytes,
5009 bytemuck::bytes_of(&make_globals(target_w, target_h)),
5010 );
5011 }
5012
5013 let bind_mask = self.atlas_bind_group_mask();
5014 let bind_color = self.atlas_bind_group_color();
5015 let mut clip_depth: u32 = 0;
5016
5017 for (pass_index, pass) in std::mem::take(&mut passes).into_iter().enumerate() {
5018 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
5019 PassTarget::Surface => {
5020 let swap_view = target_view.clone();
5021 let use_ws = self.working_space && self.ws_view.is_some();
5022 let (color, resolve) = if use_ws {
5023 let ws_view = self.ws_view.as_ref().unwrap();
5024 if let Some(msaa_view) = &self.msaa_view {
5025 (msaa_view.clone(), Some(ws_view.clone()))
5027 } else {
5028 (ws_view.clone(), None)
5030 }
5031 } else if let Some(msaa_view) = &self.msaa_view {
5032 (msaa_view.clone(), Some(swap_view))
5033 } else {
5034 (swap_view, None)
5035 };
5036 (color, resolve, self.depth_stencil_view.clone(), false)
5037 }
5038 PassTarget::Layer(layer_id) => {
5039 if let Some(lt) = self.layer_pool.get(&layer_id) {
5040 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
5041 } else {
5042 log::warn!("missing layer target {layer_id}");
5043 continue;
5044 }
5045 }
5046 };
5047
5048 encoder.copy_buffer_to_buffer(
5049 &globals_staging,
5050 (pass_index as u64) * globals_bytes,
5051 &self.globals_buf,
5052 0,
5053 globals_bytes,
5054 );
5055
5056 if is_layer {
5057 clip_depth = 0;
5058 }
5059
5060 let pipes: &Pipelines = if is_layer {
5061 &self.layer_pipes
5062 } else {
5063 &self.surface_pipes
5064 };
5065
5066 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
5067 label: Some("pass"),
5068 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
5069 view: &color_view,
5070 resolve_target: resolve_target.as_ref(),
5071 ops: wgpu::Operations {
5072 load: match pass.clear_color {
5073 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
5074 r: c[0] as f64,
5075 g: c[1] as f64,
5076 b: c[2] as f64,
5077 a: c[3] as f64,
5078 }),
5079 None => wgpu::LoadOp::Load,
5080 },
5081 store: wgpu::StoreOp::Store,
5082 },
5083 depth_slice: None,
5084 })],
5085 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
5086 view: &depth_stencil_view,
5087 depth_ops: None,
5088 stencil_ops: Some(wgpu::Operations {
5089 load: if is_layer || pass.clear_color.is_some() {
5090 wgpu::LoadOp::Clear(0)
5091 } else {
5092 wgpu::LoadOp::Load
5093 },
5094 store: wgpu::StoreOp::Store,
5095 }),
5096 }),
5097 timestamp_writes: None,
5098 occlusion_query_set: None,
5099 multiview_mask: None,
5100 });
5101
5102 rpass.set_bind_group(0, &self.globals_bind, &[]);
5103 rpass.set_stencil_reference(clip_depth);
5104 rpass.set_scissor_rect(
5105 pass.initial_scissor.0,
5106 pass.initial_scissor.1,
5107 pass.initial_scissor.2,
5108 pass.initial_scissor.3,
5109 );
5110
5111 macro_rules! draw_simple {
5112 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
5113 rpass.set_pipeline($pipeline);
5114 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
5115 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
5116 rpass.draw(0..6, 0..$n);
5117 }};
5118 }
5119
5120 macro_rules! draw_with_bind {
5121 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
5122 rpass.set_pipeline($pipeline);
5123 rpass.set_bind_group(1, $bind, &[]);
5124 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
5125 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
5126 rpass.draw(0..6, 0..$n);
5127 }};
5128 }
5129
5130 macro_rules! draw_indexed_mesh {
5131 ($pipeline:expr, $uoff:ident, $voff:ident, $vcnt:ident, $ioff:ident, $icnt:ident) => {{
5132 rpass.set_pipeline($pipeline);
5133 rpass.set_bind_group(1, &self.mesh_bind, &[$uoff as u32]);
5134 let vbytes = ($vcnt as u64) * std::mem::size_of::<MeshVertex>() as u64;
5135 rpass.set_vertex_buffer(
5136 0,
5137 self.mesh_verts.buf.slice($voff..$voff + vbytes),
5138 );
5139 let ibytes = ($icnt as u64) * std::mem::size_of::<u32>() as u64;
5140 rpass.set_index_buffer(
5141 self.mesh_indices.buf.slice($ioff..$ioff + ibytes),
5142 wgpu::IndexFormat::Uint32,
5143 );
5144 rpass.draw_indexed(0..$icnt, 0, 0..1);
5145 }};
5146 }
5147
5148 for cmd in pass.cmds {
5149 match cmd {
5150 Cmd::ClipPush {
5151 off,
5152 cnt: n,
5153 scissor,
5154 difference,
5155 rounded,
5156 } => {
5157 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5158 rpass.set_stencil_reference(clip_depth);
5159
5160 if difference {
5161 rpass.set_pipeline(&pipes.clip_dec);
5162 } else if self.msaa_samples > 1 && !is_layer && rounded {
5163 rpass.set_pipeline(&pipes.clip_a2c);
5164 } else {
5165 rpass.set_pipeline(&pipes.clip_bin);
5166 }
5167
5168 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
5169 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
5170 rpass.draw(0..6, 0..n);
5171
5172 if !difference {
5173 clip_depth = (clip_depth + 1).min(255);
5174 rpass.set_stencil_reference(clip_depth);
5175 }
5176 }
5177
5178 Cmd::ClipPop { scissor } => {
5179 clip_depth = clip_depth.saturating_sub(1);
5180 rpass.set_stencil_reference(clip_depth);
5181 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5182 }
5183
5184 Cmd::Rect { off, cnt: n } => {
5185 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
5186 }
5187
5188 Cmd::Border { off, cnt: n } => {
5189 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
5190 }
5191
5192 Cmd::GlyphsMask { off, cnt: n } => {
5193 draw_with_bind!(
5194 &pipes.text_mask,
5195 self.glyph_mask.ring,
5196 GlyphInstance,
5197 &bind_mask,
5198 off,
5199 n
5200 );
5201 }
5202
5203 Cmd::GlyphsColor { off, cnt: n } => {
5204 draw_with_bind!(
5205 &pipes.text_color,
5206 self.glyph_color.ring,
5207 GlyphInstance,
5208 &bind_color,
5209 off,
5210 n
5211 );
5212 }
5213
5214 Cmd::GlyphsVector { off, cnt: n } => {
5215 if let Some(ref slug_pipe) = pipes.slug.as_ref() {
5216 rpass.set_pipeline(slug_pipe);
5217 let bytes = (n as u64) * std::mem::size_of::<slug::TessVertex>() as u64;
5218 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
5219 rpass.draw(0..n, 0..1);
5220 }
5221 }
5222
5223 Cmd::ImageRgba {
5224 off,
5225 cnt: n,
5226 handle,
5227 } => {
5228 if let Some(ImageTex::Rgba { bind, .. }) = self.images.get(&handle) {
5229 draw_with_bind!(
5230 &pipes.image_rgba,
5231 self.glyph_color.ring,
5232 GlyphInstance,
5233 bind,
5234 off,
5235 n
5236 );
5237 }
5238 }
5239
5240 Cmd::ImageNv12 {
5241 off,
5242 cnt: n,
5243 handle,
5244 } => {
5245 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
5246 draw_with_bind!(
5247 &pipes.image_nv12,
5248 self.nv12.ring,
5249 Nv12Instance,
5250 bind,
5251 off,
5252 n
5253 );
5254 }
5255 }
5256
5257 Cmd::Ellipse { off, cnt: n } => {
5258 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
5259 }
5260
5261 Cmd::EllipseBorder { off, cnt: n } => {
5262 draw_simple!(
5263 &pipes.ellipse_borders,
5264 self.ellipse_borders.ring,
5265 EllipseBorderInstance,
5266 off,
5267 n
5268 );
5269 }
5270
5271 Cmd::Arc { off, cnt: n } => {
5272 draw_simple!(&pipes.arcs, self.arcs.ring, ArcInstance, off, n);
5273 }
5274
5275 Cmd::PushTransform(_) => {}
5276 Cmd::PopTransform => {}
5277 Cmd::CompositeLayer {
5278 off,
5279 cnt: n,
5280 layer_id,
5281 alpha: _,
5282 } => {
5283 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5284 draw_with_bind!(
5285 &pipes.image_rgba,
5286 self.glyph_color.ring,
5287 GlyphInstance,
5288 <.bind,
5289 off,
5290 n
5291 );
5292 }
5293 }
5294 Cmd::CompositeShadow {
5295 off,
5296 cnt: n,
5297 layer_id,
5298 } => {
5299 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5300 draw_with_bind!(
5301 &pipes.blur,
5302 self.blur_ring,
5303 BlurInstance,
5304 <.bind_linear,
5305 off,
5306 n
5307 );
5308 }
5309 }
5310 Cmd::CompositeBlur {
5311 off,
5312 cnt: n,
5313 layer_id,
5314 } => {
5315 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5316 draw_with_bind!(
5317 &pipes.blur_content,
5318 self.blur_ring,
5319 BlurInstance,
5320 <.bind_linear,
5321 off,
5322 n
5323 );
5324 }
5325 }
5326
5327 Cmd::VectorMesh { voff, vcnt, ioff, icnt, uoff } => {
5328 draw_indexed_mesh!(&pipes.mesh, uoff, voff, vcnt, ioff, icnt);
5329 }
5330
5331 Cmd::VectorOverlay { voff, vcnt, ioff, icnt, uoff } => {
5332 draw_indexed_mesh!(&pipes.mesh_overlay, uoff, voff, vcnt, ioff, icnt);
5333 }
5334
5335 Cmd::VectorClipPush { voff, vcnt, ioff, icnt, uoff, scissor } => {
5336 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5337 rpass.set_stencil_reference(clip_depth);
5338 draw_indexed_mesh!(&pipes.mesh_clip_inc, uoff, voff, vcnt, ioff, icnt);
5339 clip_depth = (clip_depth + 1).min(255);
5340 rpass.set_stencil_reference(clip_depth);
5341 }
5342
5343 Cmd::VectorClipPop { voff, vcnt, ioff, icnt, uoff, scissor } => {
5344 rpass.set_stencil_reference(clip_depth);
5348 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5349 draw_indexed_mesh!(&pipes.mesh_clip_dec, uoff, voff, vcnt, ioff, icnt);
5350 clip_depth = clip_depth.saturating_sub(1);
5351 rpass.set_stencil_reference(clip_depth);
5352 }
5353 }
5354 }
5355 }
5356
5357 if self.working_space {
5359 if let (Some(_ws_view), Some(ws_bind), Some(display_pipeline)) =
5360 (&self.ws_view, &self.ws_bind, &self.display_pipeline)
5361 {
5362 let swap_view = target_view.clone();
5363 let mut display_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
5364 label: Some("display transform"),
5365 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
5366 view: &swap_view,
5367 resolve_target: None,
5368 ops: wgpu::Operations {
5369 load: wgpu::LoadOp::Load,
5370 store: wgpu::StoreOp::Store,
5371 },
5372 depth_slice: None,
5373 })],
5374 depth_stencil_attachment: None,
5375 timestamp_writes: None,
5376 occlusion_query_set: None,
5377 multiview_mask: None,
5378 });
5379 display_pass.set_pipeline(display_pipeline);
5380 display_pass.set_bind_group(1, ws_bind, &[]);
5381 display_pass.draw(0..3, 0..1);
5382 }
5383 }
5384
5385
5386 self.evict_unused_images();
5388 }
5389
5390 pub fn render_to_view(
5394 &mut self,
5395 scene: &Scene,
5396 encoder: &mut wgpu::CommandEncoder,
5397 target_view: &wgpu::TextureView,
5398 width: u32,
5399 height: u32,
5400 clear_color: Option<[f64; 4]>,
5401 ) {
5402 self.resize(width, height);
5403
5404 self.frame_index = self.frame_index.wrapping_add(1);
5405 self.slug_cache.next_frame();
5406
5407 if width == 0 || height == 0 {
5408 return;
5409 }
5410
5411 self.render_scene_to_encoder(scene, encoder, target_view, clear_color);
5412 }
5413}
5414
5415
5416fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
5417 let x0 = a.x.max(b.x);
5418 let y0 = a.y.max(b.y);
5419 let x1 = (a.x + a.w).min(b.x + b.w);
5420 let y1 = (a.y + a.h).min(b.y + b.h);
5421 repose_core::Rect {
5422 x: x0,
5423 y: y0,
5424 w: (x1 - x0).max(0.0),
5425 h: (y1 - y0).max(0.0),
5426 }
5427}