1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::num::NonZero;
4#[cfg(feature = "winit-surface")]
5use std::panic::{AssertUnwindSafe, catch_unwind};
6use std::sync::Arc;
7
8use repose_core::color::{ChromaSiting, ColorInfo, PixelFormat};
9use repose_core::request_frame;
10use repose_core::{
11 Brush, FontStyle, GlyphRasterConfig, PresentModePref, RenderBackend, Scene, SceneNode,
12 StrokeCap, Transform,
13};
14use wgpu::Instance;
15
16mod slug;
17
18mod commands;
19pub use commands::apply_render_commands;
20
21pub mod offscreen;
22
23mod callback;
24pub use callback::{Callback, CallbackResources, ScreenDescriptor, WgpuCallback};
25
26#[derive(Clone)]
27struct UploadRing {
28 buf: wgpu::Buffer,
29 cap: u64,
30 head: u64,
31 usage: wgpu::BufferUsages,
32}
33
34impl UploadRing {
35 fn new(device: &wgpu::Device, label: &str, cap: u64, usage: wgpu::BufferUsages) -> Self {
36 let buf = device.create_buffer(&wgpu::BufferDescriptor {
37 label: Some(label),
38 size: cap,
39 usage,
40 mapped_at_creation: false,
41 });
42 Self {
43 buf,
44 cap,
45 head: 0,
46 usage,
47 }
48 }
49
50 fn reset(&mut self) {
51 self.head = 0;
52 }
53
54 fn grow_to_fit(&mut self, device: &wgpu::Device, needed: u64) {
55 let start = (self.head + 3) & !3;
56 let aligned_needed = (needed + 3) & !3;
57 if start + needed <= self.cap {
59 return;
60 }
61 let required = start + needed;
62 let mut new_cap = required.next_power_of_two().max(self.cap * 2).max(256);
63 new_cap = (new_cap + 3) & !3;
64 if new_cap < aligned_needed {
65 new_cap = aligned_needed.next_power_of_two();
66 }
67 self.buf = device.create_buffer(&wgpu::BufferDescriptor {
68 label: Some("upload ring (grown)"),
69 size: new_cap,
70 usage: self.usage,
71 mapped_at_creation: false,
72 });
73 self.cap = new_cap;
74 if start + needed > self.cap {
75 self.head = 0;
76 }
77 }
78
79 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> (u64, u64) {
80 let len = bytes.len() as u64;
81 let start = (self.head + 3) & !3; let end = start + len;
83 if end > self.cap {
84 log::error!(
86 "UploadRing overflow: start={start} len={len} cap={} - growing",
87 self.cap
88 );
89 if len > self.cap {
90 return (0, 0);
93 }
94 let wrapped_start = 0;
96 let wrapped_end = len;
97 if wrapped_end <= self.cap {
98 queue.write_buffer(&self.buf, wrapped_start, bytes);
99 self.head = wrapped_end;
100 return (wrapped_start, len);
101 }
102 return (0, 0);
103 }
104 queue.write_buffer(&self.buf, start, bytes);
105 self.head = end;
106 (start, len)
107 }
108}
109
110struct InstancedPipe<I: bytemuck::Pod> {
111 ring: UploadRing,
112 _marker: std::marker::PhantomData<I>,
113}
114
115impl<I: bytemuck::Pod> InstancedPipe<I> {
116 fn new(ring: UploadRing) -> Self {
117 Self {
118 ring,
119 _marker: std::marker::PhantomData,
120 }
121 }
122
123 fn upload(
124 &mut self,
125 device: &wgpu::Device,
126 queue: &wgpu::Queue,
127 data: &[I],
128 ) -> Option<(u64, u32)> {
129 if data.is_empty() {
130 return None;
131 }
132 let bytes = bytemuck::cast_slice(data);
133 self.ring.grow_to_fit(device, bytes.len() as u64);
134 let (off, wrote) = self.ring.alloc_write(queue, bytes);
135 debug_assert_eq!(wrote as usize, bytes.len());
136 Some((off, data.len() as u32))
137 }
138
139 fn reset(&mut self) {
140 self.ring.reset();
141 }
142}
143
144#[repr(C)]
145#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
146struct Globals {
147 ndc_to_px: [f32; 2],
148 _pad: [f32; 2],
149}
150
151fn make_globals(target_w: f32, target_h: f32) -> Globals {
152 Globals {
153 ndc_to_px: [target_w * 0.5, target_h * 0.5],
154 _pad: [0.0, 0.0],
155 }
156}
157
158pub struct WgpuSceneRenderer {
159 pub device: wgpu::Device,
160 pub queue: wgpu::Queue,
161 pub output_format: wgpu::TextureFormat,
162 pub output_width: u32,
163 pub output_height: u32,
164 pub pixels_per_point: f32,
166
167 surface_pipes: Pipelines,
170 layer_pipes: Pipelines,
171
172 rects: InstancedPipe<RectInstance>,
174 borders: InstancedPipe<BorderInstance>,
175 ellipses: InstancedPipe<EllipseInstance>,
176 ellipse_borders: InstancedPipe<EllipseBorderInstance>,
177 arcs: InstancedPipe<ArcInstance>,
178 glyph_mask: InstancedPipe<GlyphInstance>,
179 glyph_color: InstancedPipe<GlyphInstance>,
180
181 image_bind_layout_rgba: wgpu::BindGroupLayout,
183 image_bind_layout_nv12: wgpu::BindGroupLayout,
184 image_sampler: wgpu::Sampler,
185 layer_sampler: wgpu::Sampler,
186 layer_sampler_linear: wgpu::Sampler,
187
188 blur_ring: UploadRing,
190
191 text_bind_layout: wgpu::BindGroupLayout,
192
193 clip_ring: UploadRing,
195
196 projective_ring: UploadRing,
199
200 slug_enabled: bool,
202 slug_ring: UploadRing,
203 slug_cache: slug::GlyphSlugCache,
204
205 nv12: InstancedPipe<Nv12Instance>,
207
208 mesh_verts: UploadRing,
210 mesh_indices: UploadRing,
211 mesh_uniform_buf: wgpu::Buffer,
212 mesh_bind_layout: wgpu::BindGroupLayout,
213 mesh_bind: wgpu::BindGroup,
214 mesh_uniform_head: u64,
215 mesh_clip_stack: Vec<(u64, u32, u64, u32, u64, bool)>,
219
220 flatten_layer_ids: Vec<u32>,
224
225 msaa_samples: u32,
226
227 depth_stencil_tex: wgpu::Texture,
229 depth_stencil_view: wgpu::TextureView,
230
231 msaa_tex: Option<wgpu::Texture>,
233 msaa_view: Option<wgpu::TextureView>,
234
235 globals_buf: wgpu::Buffer,
236 globals_bind: wgpu::BindGroup,
237
238 atlas_mask: AtlasA8,
240 atlas_color: AtlasRGBA,
241
242 next_image_handle: u64,
244 images: HashMap<u64, ImageTex>,
245 retained: HashMap<u64, RetainedImage>,
246
247 next_coverage_handle: u64,
250 coverages: HashMap<u64, CoverageTex>,
251
252 frame_index: u64,
254 image_bytes_total: u64,
255 image_evict_after_frames: u64,
256 image_budget_bytes: u64,
257
258 layer_pool: HashMap<u32, LayerTarget>,
261
262 working_space: bool,
266 ws_tex: Option<wgpu::Texture>,
267 ws_view: Option<wgpu::TextureView>,
268 ws_bind: Option<wgpu::BindGroup>,
269 display_pipeline: Option<wgpu::RenderPipeline>,
270 display_layout: Option<wgpu::BindGroupLayout>,
271
272 pub callback_resources: CallbackResources,
273}
274
275pub struct WgpuSurfaceBackend {
276 pub surface: Option<wgpu::Surface<'static>>,
277 pub surface_config: Option<wgpu::SurfaceConfiguration>,
278 pub renderer: WgpuSceneRenderer,
279}
280
281impl std::ops::Deref for WgpuSurfaceBackend {
282 type Target = WgpuSceneRenderer;
283 fn deref(&self) -> &Self::Target {
284 &self.renderer
285 }
286}
287impl std::ops::DerefMut for WgpuSurfaceBackend {
288 fn deref_mut(&mut self) -> &mut Self::Target {
289 &mut self.renderer
290 }
291}
292
293#[cfg(feature = "winit-surface")]
294pub type WgpuBackend = WgpuSurfaceBackend;
295
296impl Drop for WgpuSceneRenderer {
297 fn drop(&mut self) {
298 let _ = self.device.poll(wgpu::PollType::Poll);
299 #[cfg(not(target_arch = "wasm32"))]
300 {
301 let _ = self.device.poll(wgpu::PollType::Wait {
302 submission_index: None,
303 timeout: Some(std::time::Duration::from_millis(100)),
304 });
305 }
306 }
307}
308
309#[derive(Clone)]
310struct LayerTarget {
311 view: wgpu::TextureView,
312 bind: wgpu::BindGroup,
313 bind_linear: wgpu::BindGroup,
314 depth_stencil_view: wgpu::TextureView,
315 width: u32,
316 height: u32,
317 rect_px: (f32, f32, f32, f32),
318}
319
320#[derive(Clone, Copy)]
322enum PassTarget {
323 Surface,
324 Layer(u32),
325}
326
327struct Pipelines {
332 rects: wgpu::RenderPipeline,
333 borders: wgpu::RenderPipeline,
334 ellipses: wgpu::RenderPipeline,
335 ellipse_borders: wgpu::RenderPipeline,
336 arcs: wgpu::RenderPipeline,
337 text_mask: wgpu::RenderPipeline,
338 text_color: wgpu::RenderPipeline,
339 image_rgba: wgpu::RenderPipeline,
340 coverage: wgpu::RenderPipeline,
344 image_nv12: wgpu::RenderPipeline,
345 blur: wgpu::RenderPipeline,
346 blur_content: wgpu::RenderPipeline,
347 clip_a2c: wgpu::RenderPipeline,
348 clip_bin: wgpu::RenderPipeline,
349 clip_dec: wgpu::RenderPipeline,
350 slug: Option<wgpu::RenderPipeline>,
351 mesh: wgpu::RenderPipeline,
355 mesh_overlay: wgpu::RenderPipeline,
358 mesh_clip_inc: wgpu::RenderPipeline,
360 mesh_clip_dec: wgpu::RenderPipeline,
362 projective_layer: wgpu::RenderPipeline,
366}
367
368impl Pipelines {
369 fn create(
370 device: &wgpu::Device,
371 format: wgpu::TextureFormat,
372 sample_count: u32,
373 globals_layout: &wgpu::BindGroupLayout,
374 text_bind_layout: &wgpu::BindGroupLayout,
375 image_bind_layout_nv12: &wgpu::BindGroupLayout,
376 clip_pipeline_layout: &wgpu::PipelineLayout,
377 stencil_for_content: &wgpu::DepthStencilState,
378 stencil_for_clip_inc: &wgpu::DepthStencilState,
379 stencil_for_clip_dec: &wgpu::DepthStencilState,
380 clip_color_target: &wgpu::ColorTargetState,
381 clip_vertex_layout: &wgpu::VertexBufferLayout,
382 mesh_bind_layout: &wgpu::BindGroupLayout,
383 ) -> Self {
384 let msaa_state = wgpu::MultisampleState {
385 count: sample_count,
386 mask: !0,
387 alpha_to_coverage_enabled: false,
388 };
389
390 macro_rules! make_content_pipeline {
391 ($name:ident, $shader:literal, $inst_type:ty, $attrs:expr) => {
392 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
393 label: Some(concat!($shader, ".wgsl")),
394 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(concat!(
395 "shaders/", $shader, ".wgsl"
396 )))),
397 });
398 let pipeline_layout =
399 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
400 label: Some(concat!($shader, " pipeline layout")),
401 bind_group_layouts: &[Some(globals_layout)],
402 immediate_size: 0,
403 });
404 let $name = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
405 label: Some(concat!($shader, " pipeline")),
406 layout: Some(&pipeline_layout),
407 vertex: wgpu::VertexState {
408 module: &shader_module,
409 entry_point: Some("vs_main"),
410 buffers: &[Some(wgpu::VertexBufferLayout {
411 array_stride: std::mem::size_of::<$inst_type>() as u64,
412 step_mode: wgpu::VertexStepMode::Instance,
413 attributes: $attrs,
414 })],
415 compilation_options: wgpu::PipelineCompilationOptions::default(),
416 },
417 fragment: Some(wgpu::FragmentState {
418 module: &shader_module,
419 entry_point: Some("fs_main"),
420 targets: &[Some(wgpu::ColorTargetState {
421 format,
422 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
423 write_mask: wgpu::ColorWrites::ALL,
424 })],
425 compilation_options: wgpu::PipelineCompilationOptions::default(),
426 }),
427 primitive: wgpu::PrimitiveState::default(),
428 depth_stencil: Some(stencil_for_content.clone()),
429 multisample: msaa_state,
430 multiview_mask: None,
431 cache: None,
432 });
433 };
434 }
435
436 let rect_attrs: &[wgpu::VertexAttribute] = &[
437 wgpu::VertexAttribute {
438 shader_location: 0,
439 offset: 0,
440 format: wgpu::VertexFormat::Float32x4,
441 },
442 wgpu::VertexAttribute {
443 shader_location: 1,
444 offset: 16,
445 format: wgpu::VertexFormat::Float32x4,
446 },
447 wgpu::VertexAttribute {
448 shader_location: 2,
449 offset: 32,
450 format: wgpu::VertexFormat::Uint32,
451 },
452 wgpu::VertexAttribute {
453 shader_location: 3,
454 offset: 48,
455 format: wgpu::VertexFormat::Float32x4,
456 },
457 wgpu::VertexAttribute {
458 shader_location: 4,
459 offset: 64,
460 format: wgpu::VertexFormat::Float32x4,
461 },
462 wgpu::VertexAttribute {
463 shader_location: 5,
464 offset: 80,
465 format: wgpu::VertexFormat::Float32x2,
466 },
467 wgpu::VertexAttribute {
468 shader_location: 6,
469 offset: 88,
470 format: wgpu::VertexFormat::Float32x2,
471 },
472 wgpu::VertexAttribute {
473 shader_location: 7,
474 offset: 96,
475 format: wgpu::VertexFormat::Float32x4,
476 },
477 ];
478 let border_attrs: &[wgpu::VertexAttribute] = &[
479 wgpu::VertexAttribute {
480 shader_location: 0,
481 offset: 0,
482 format: wgpu::VertexFormat::Float32x4,
483 },
484 wgpu::VertexAttribute {
485 shader_location: 1,
486 offset: 16,
487 format: wgpu::VertexFormat::Float32x4,
488 },
489 wgpu::VertexAttribute {
490 shader_location: 2,
491 offset: 32,
492 format: wgpu::VertexFormat::Float32,
493 },
494 wgpu::VertexAttribute {
495 shader_location: 3,
496 offset: 36,
497 format: wgpu::VertexFormat::Float32x4,
498 },
499 wgpu::VertexAttribute {
500 shader_location: 4,
501 offset: 52,
502 format: wgpu::VertexFormat::Float32x4,
503 },
504 ];
505 let ellipse_attrs: &[wgpu::VertexAttribute] = &[
506 wgpu::VertexAttribute {
507 shader_location: 0,
508 offset: 0,
509 format: wgpu::VertexFormat::Float32x4,
510 },
511 wgpu::VertexAttribute {
512 shader_location: 1,
513 offset: 16,
514 format: wgpu::VertexFormat::Float32x4,
515 },
516 wgpu::VertexAttribute {
517 shader_location: 2,
518 offset: 32,
519 format: wgpu::VertexFormat::Float32x4,
520 },
521 ];
522 let ellipse_border_attrs: &[wgpu::VertexAttribute] = &[
523 wgpu::VertexAttribute {
524 shader_location: 0,
525 offset: 0,
526 format: wgpu::VertexFormat::Float32x4,
527 },
528 wgpu::VertexAttribute {
529 shader_location: 1,
530 offset: 16,
531 format: wgpu::VertexFormat::Float32,
532 },
533 wgpu::VertexAttribute {
534 shader_location: 2,
535 offset: 20,
536 format: wgpu::VertexFormat::Float32,
537 },
538 wgpu::VertexAttribute {
539 shader_location: 3,
540 offset: 24,
541 format: wgpu::VertexFormat::Float32x4,
542 },
543 wgpu::VertexAttribute {
544 shader_location: 4,
545 offset: 40,
546 format: wgpu::VertexFormat::Float32x4,
547 },
548 ];
549
550 make_content_pipeline!(rects, "rect", RectInstance, rect_attrs);
551 make_content_pipeline!(borders, "border", BorderInstance, border_attrs);
552 make_content_pipeline!(ellipses, "ellipse", EllipseInstance, ellipse_attrs);
553 make_content_pipeline!(
554 ellipse_borders,
555 "ellipse_border",
556 EllipseBorderInstance,
557 ellipse_border_attrs
558 );
559
560 let arc_attrs: &[wgpu::VertexAttribute] = &[
561 wgpu::VertexAttribute {
562 shader_location: 0,
563 offset: 0,
564 format: wgpu::VertexFormat::Float32x4,
565 },
566 wgpu::VertexAttribute {
567 shader_location: 1,
568 offset: 16,
569 format: wgpu::VertexFormat::Float32,
570 },
571 wgpu::VertexAttribute {
572 shader_location: 2,
573 offset: 20,
574 format: wgpu::VertexFormat::Float32,
575 },
576 wgpu::VertexAttribute {
577 shader_location: 3,
578 offset: 24,
579 format: wgpu::VertexFormat::Float32,
580 },
581 wgpu::VertexAttribute {
582 shader_location: 4,
583 offset: 28,
584 format: wgpu::VertexFormat::Float32,
585 },
586 wgpu::VertexAttribute {
587 shader_location: 5,
588 offset: 32,
589 format: wgpu::VertexFormat::Float32x4,
590 },
591 wgpu::VertexAttribute {
592 shader_location: 6,
593 offset: 48,
594 format: wgpu::VertexFormat::Float32x4,
595 },
596 wgpu::VertexAttribute {
597 shader_location: 7,
598 offset: 64,
599 format: wgpu::VertexFormat::Float32,
600 },
601 ];
602
603 make_content_pipeline!(arcs, "arc", ArcInstance, arc_attrs);
604
605 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
607 label: Some("text.wgsl"),
608 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
609 });
610 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
612 label: Some("text_color.wgsl"),
613 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
614 "shaders/text_color.wgsl"
615 ))),
616 });
617 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
618 label: Some("text pipeline layout"),
619 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
620 immediate_size: 0,
621 });
622 let glyph_vertex = wgpu::VertexBufferLayout {
623 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
624 step_mode: wgpu::VertexStepMode::Instance,
625 attributes: &[
626 wgpu::VertexAttribute {
627 shader_location: 0,
628 offset: 0,
629 format: wgpu::VertexFormat::Float32x4,
630 },
631 wgpu::VertexAttribute {
632 shader_location: 1,
633 offset: 16,
634 format: wgpu::VertexFormat::Float32x4,
635 },
636 wgpu::VertexAttribute {
637 shader_location: 2,
638 offset: 32,
639 format: wgpu::VertexFormat::Float32x4,
640 },
641 wgpu::VertexAttribute {
642 shader_location: 3,
643 offset: 48,
644 format: wgpu::VertexFormat::Float32x4,
645 },
646 ],
647 };
648 let text_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
649 label: Some("text pipeline (mask)"),
650 layout: Some(&text_pipeline_layout),
651 vertex: wgpu::VertexState {
652 module: &text_mask_shader,
653 entry_point: Some("vs_main"),
654 buffers: &[Some(glyph_vertex.clone())],
655 compilation_options: wgpu::PipelineCompilationOptions::default(),
656 },
657 fragment: Some(wgpu::FragmentState {
658 module: &text_mask_shader,
659 entry_point: Some("fs_main"),
660 targets: &[Some(wgpu::ColorTargetState {
661 format,
662 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
663 write_mask: wgpu::ColorWrites::ALL,
664 })],
665 compilation_options: wgpu::PipelineCompilationOptions::default(),
666 }),
667 primitive: wgpu::PrimitiveState::default(),
668 depth_stencil: Some(stencil_for_content.clone()),
669 multisample: msaa_state,
670 multiview_mask: None,
671 cache: None,
672 });
673 let text_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
674 label: Some("text pipeline (color)"),
675 layout: Some(&text_pipeline_layout),
676 vertex: wgpu::VertexState {
677 module: &text_color_shader,
678 entry_point: Some("vs_main"),
679 buffers: &[Some(glyph_vertex.clone())],
680 compilation_options: wgpu::PipelineCompilationOptions::default(),
681 },
682 fragment: Some(wgpu::FragmentState {
683 module: &text_color_shader,
684 entry_point: Some("fs_main"),
685 targets: &[Some(wgpu::ColorTargetState {
686 format,
687 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
688 write_mask: wgpu::ColorWrites::ALL,
689 })],
690 compilation_options: wgpu::PipelineCompilationOptions::default(),
691 }),
692 primitive: wgpu::PrimitiveState::default(),
693 depth_stencil: Some(stencil_for_content.clone()),
694 multisample: msaa_state,
695 multiview_mask: None,
696 cache: None,
697 });
698 let image_rgba = text_color.clone();
700
701 let coverage_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
705 label: Some("coverage.wgsl"),
706 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/coverage.wgsl"))),
707 });
708 let coverage = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
709 label: Some("coverage pipeline (tinted a8)"),
710 layout: Some(&text_pipeline_layout),
711 vertex: wgpu::VertexState {
712 module: &coverage_shader,
713 entry_point: Some("vs_main"),
714 buffers: &[Some(glyph_vertex.clone())],
715 compilation_options: wgpu::PipelineCompilationOptions::default(),
716 },
717 fragment: Some(wgpu::FragmentState {
718 module: &coverage_shader,
719 entry_point: Some("fs_main"),
720 targets: &[Some(wgpu::ColorTargetState {
721 format,
722 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
723 write_mask: wgpu::ColorWrites::ALL,
724 })],
725 compilation_options: wgpu::PipelineCompilationOptions::default(),
726 }),
727 primitive: wgpu::PrimitiveState::default(),
728 depth_stencil: Some(stencil_for_content.clone()),
729 multisample: msaa_state,
730 multiview_mask: None,
731 cache: None,
732 });
733
734 let blur_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
736 label: Some("blur_shadow.wgsl"),
737 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
738 "shaders/blur_shadow.wgsl"
739 ))),
740 });
741 let blur_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
742 label: Some("blur pipeline layout"),
743 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
744 immediate_size: 0,
745 });
746 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
747 label: Some("blur pipeline"),
748 layout: Some(&blur_pipeline_layout),
749 vertex: wgpu::VertexState {
750 module: &blur_shader,
751 entry_point: Some("vs_main"),
752 buffers: &[Some(wgpu::VertexBufferLayout {
753 array_stride: std::mem::size_of::<BlurInstance>() as u64,
754 step_mode: wgpu::VertexStepMode::Instance,
755 attributes: &[
756 wgpu::VertexAttribute {
757 shader_location: 0,
758 offset: 0,
759 format: wgpu::VertexFormat::Float32x4,
760 },
761 wgpu::VertexAttribute {
762 shader_location: 1,
763 offset: 16,
764 format: wgpu::VertexFormat::Float32x4,
765 },
766 wgpu::VertexAttribute {
767 shader_location: 2,
768 offset: 32,
769 format: wgpu::VertexFormat::Float32x4,
770 },
771 wgpu::VertexAttribute {
772 shader_location: 3,
773 offset: 48,
774 format: wgpu::VertexFormat::Float32x2,
775 },
776 wgpu::VertexAttribute {
777 shader_location: 4,
778 offset: 56,
779 format: wgpu::VertexFormat::Float32x4,
780 },
781 ],
782 })],
783 compilation_options: wgpu::PipelineCompilationOptions::default(),
784 },
785 fragment: Some(wgpu::FragmentState {
786 module: &blur_shader,
787 entry_point: Some("fs_main"),
788 targets: &[Some(wgpu::ColorTargetState {
789 format,
790 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
791 write_mask: wgpu::ColorWrites::ALL,
792 })],
793 compilation_options: wgpu::PipelineCompilationOptions::default(),
794 }),
795 primitive: wgpu::PrimitiveState::default(),
796 depth_stencil: Some(stencil_for_content.clone()),
797 multisample: msaa_state,
798 multiview_mask: None,
799 cache: None,
800 });
801
802 let blur_content_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
804 label: Some("blur_content.wgsl"),
805 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
806 "shaders/blur_content.wgsl"
807 ))),
808 });
809 let blur_content = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
810 label: Some("blur content pipeline"),
811 layout: Some(&blur_pipeline_layout),
812 vertex: wgpu::VertexState {
813 module: &blur_content_shader,
814 entry_point: Some("vs_main"),
815 buffers: &[Some(wgpu::VertexBufferLayout {
816 array_stride: std::mem::size_of::<BlurInstance>() as u64,
817 step_mode: wgpu::VertexStepMode::Instance,
818 attributes: &[
819 wgpu::VertexAttribute {
820 shader_location: 0,
821 offset: 0,
822 format: wgpu::VertexFormat::Float32x4,
823 },
824 wgpu::VertexAttribute {
825 shader_location: 1,
826 offset: 16,
827 format: wgpu::VertexFormat::Float32x4,
828 },
829 wgpu::VertexAttribute {
830 shader_location: 2,
831 offset: 32,
832 format: wgpu::VertexFormat::Float32x4,
833 },
834 wgpu::VertexAttribute {
835 shader_location: 3,
836 offset: 48,
837 format: wgpu::VertexFormat::Float32x2,
838 },
839 wgpu::VertexAttribute {
840 shader_location: 4,
841 offset: 56,
842 format: wgpu::VertexFormat::Float32x4,
843 },
844 ],
845 })],
846 compilation_options: wgpu::PipelineCompilationOptions::default(),
847 },
848 fragment: Some(wgpu::FragmentState {
849 module: &blur_content_shader,
850 entry_point: Some("fs_main"),
851 targets: &[Some(wgpu::ColorTargetState {
852 format,
853 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
854 write_mask: wgpu::ColorWrites::ALL,
855 })],
856 compilation_options: wgpu::PipelineCompilationOptions::default(),
857 }),
858 primitive: wgpu::PrimitiveState::default(),
859 depth_stencil: Some(stencil_for_content.clone()),
860 multisample: msaa_state,
861 multiview_mask: None,
862 cache: None,
863 });
864
865 let image_nv12_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
867 label: Some("image_nv12.wgsl"),
868 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
869 "shaders/image_nv12.wgsl"
870 ))),
871 });
872 let image_nv12_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
873 label: Some("image nv12 pipeline layout"),
874 bind_group_layouts: &[Some(globals_layout), Some(image_bind_layout_nv12)],
875 immediate_size: 0,
876 });
877 let image_nv12 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
878 label: Some("image nv12 pipeline"),
879 layout: Some(&image_nv12_layout),
880 vertex: wgpu::VertexState {
881 module: &image_nv12_shader,
882 entry_point: Some("vs_main"),
883 buffers: &[Some(wgpu::VertexBufferLayout {
884 array_stride: std::mem::size_of::<Nv12Instance>() as u64,
885 step_mode: wgpu::VertexStepMode::Instance,
886 attributes: &[
887 wgpu::VertexAttribute {
888 shader_location: 0,
889 offset: 0,
890 format: wgpu::VertexFormat::Float32x4,
891 },
892 wgpu::VertexAttribute {
893 shader_location: 1,
894 offset: 16,
895 format: wgpu::VertexFormat::Float32x4,
896 },
897 wgpu::VertexAttribute {
898 shader_location: 2,
899 offset: 32,
900 format: wgpu::VertexFormat::Float32x4,
901 },
902 wgpu::VertexAttribute {
903 shader_location: 3,
904 offset: 48,
905 format: wgpu::VertexFormat::Float32,
906 },
907 wgpu::VertexAttribute {
908 shader_location: 4,
909 offset: 52,
910 format: wgpu::VertexFormat::Float32x4,
911 },
912 ],
913 })],
914 compilation_options: wgpu::PipelineCompilationOptions::default(),
915 },
916 fragment: Some(wgpu::FragmentState {
917 module: &image_nv12_shader,
918 entry_point: Some("fs_main"),
919 targets: &[Some(wgpu::ColorTargetState {
920 format,
921 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
922 write_mask: wgpu::ColorWrites::ALL,
923 })],
924 compilation_options: wgpu::PipelineCompilationOptions::default(),
925 }),
926 primitive: wgpu::PrimitiveState::default(),
927 depth_stencil: Some(stencil_for_content.clone()),
928 multisample: msaa_state,
929 multiview_mask: None,
930 cache: None,
931 });
932
933 let clip_shader_a2c = device.create_shader_module(wgpu::ShaderModuleDescriptor {
935 label: Some("clip_round_rect_a2c.wgsl"),
936 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
937 "shaders/clip_round_rect_a2c.wgsl"
938 ))),
939 });
940 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
941 label: Some("clip_round_rect_bin.wgsl"),
942 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
943 "shaders/clip_round_rect_bin.wgsl"
944 ))),
945 });
946 let clip_a2c = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
947 label: Some("clip pipeline (a2c)"),
948 layout: Some(clip_pipeline_layout),
949 vertex: wgpu::VertexState {
950 module: &clip_shader_a2c,
951 entry_point: Some("vs_main"),
952 buffers: &[Some(clip_vertex_layout.clone())],
953 compilation_options: wgpu::PipelineCompilationOptions::default(),
954 },
955 fragment: Some(wgpu::FragmentState {
956 module: &clip_shader_a2c,
957 entry_point: Some("fs_main"),
958 targets: &[Some(clip_color_target.clone())],
959 compilation_options: wgpu::PipelineCompilationOptions::default(),
960 }),
961 primitive: wgpu::PrimitiveState::default(),
962 depth_stencil: Some(stencil_for_clip_inc.clone()),
963 multisample: wgpu::MultisampleState {
964 count: sample_count,
965 mask: !0,
966 alpha_to_coverage_enabled: sample_count > 1,
967 },
968 multiview_mask: None,
969 cache: None,
970 });
971 let clip_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
972 label: Some("clip pipeline (bin)"),
973 layout: Some(clip_pipeline_layout),
974 vertex: wgpu::VertexState {
975 module: &clip_shader_bin,
976 entry_point: Some("vs_main"),
977 buffers: &[Some(clip_vertex_layout.clone())],
978 compilation_options: wgpu::PipelineCompilationOptions::default(),
979 },
980 fragment: Some(wgpu::FragmentState {
981 module: &clip_shader_bin,
982 entry_point: Some("fs_main"),
983 targets: &[Some(clip_color_target.clone())],
984 compilation_options: wgpu::PipelineCompilationOptions::default(),
985 }),
986 primitive: wgpu::PrimitiveState::default(),
987 depth_stencil: Some(stencil_for_clip_inc.clone()),
988 multisample: wgpu::MultisampleState {
989 count: sample_count,
990 mask: !0,
991 alpha_to_coverage_enabled: false,
992 },
993 multiview_mask: None,
994 cache: None,
995 });
996 let clip_dec = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
997 label: Some("clip pipeline (dec)"),
998 layout: Some(clip_pipeline_layout),
999 vertex: wgpu::VertexState {
1000 module: &clip_shader_bin,
1001 entry_point: Some("vs_main"),
1002 buffers: &[Some(clip_vertex_layout.clone())],
1003 compilation_options: wgpu::PipelineCompilationOptions::default(),
1004 },
1005 fragment: Some(wgpu::FragmentState {
1006 module: &clip_shader_bin,
1007 entry_point: Some("fs_main"),
1008 targets: &[Some(clip_color_target.clone())],
1009 compilation_options: wgpu::PipelineCompilationOptions::default(),
1010 }),
1011 primitive: wgpu::PrimitiveState::default(),
1012 depth_stencil: Some(stencil_for_clip_dec.clone()),
1013 multisample: wgpu::MultisampleState {
1014 count: sample_count,
1015 mask: !0,
1016 alpha_to_coverage_enabled: false,
1017 },
1018 multiview_mask: None,
1019 cache: None,
1020 });
1021
1022 let slug = Some(slug::create_pipeline(
1023 device,
1024 format,
1025 sample_count,
1026 stencil_for_content,
1027 ));
1028
1029 let mesh_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1031 label: Some("mesh.wgsl"),
1032 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/mesh.wgsl"))),
1033 });
1034 let mesh_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1035 label: Some("mesh pipeline layout"),
1036 bind_group_layouts: &[Some(globals_layout), Some(mesh_bind_layout)],
1037 immediate_size: 0,
1038 });
1039 let mesh_vertex_layout = wgpu::VertexBufferLayout {
1040 array_stride: std::mem::size_of::<MeshVertex>() as u64,
1041 step_mode: wgpu::VertexStepMode::Vertex,
1042 attributes: &[
1043 wgpu::VertexAttribute {
1044 shader_location: 0,
1045 offset: 0,
1046 format: wgpu::VertexFormat::Float32x2,
1047 },
1048 wgpu::VertexAttribute {
1049 shader_location: 1,
1050 offset: 8,
1051 format: wgpu::VertexFormat::Float32x4,
1052 },
1053 wgpu::VertexAttribute {
1054 shader_location: 2,
1055 offset: 24,
1056 format: wgpu::VertexFormat::Float32x2,
1057 },
1058 ],
1059 };
1060 let make_mesh_pipeline =
1061 |label: &str, depth: &wgpu::DepthStencilState, color: &wgpu::ColorTargetState| {
1062 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1063 label: Some(label),
1064 layout: Some(&mesh_pipeline_layout),
1065 vertex: wgpu::VertexState {
1066 module: &mesh_shader,
1067 entry_point: Some("vs_main"),
1068 buffers: &[Some(mesh_vertex_layout.clone())],
1069 compilation_options: wgpu::PipelineCompilationOptions::default(),
1070 },
1071 fragment: Some(wgpu::FragmentState {
1072 module: &mesh_shader,
1073 entry_point: Some("fs_main"),
1074 targets: &[Some(color.clone())],
1075 compilation_options: wgpu::PipelineCompilationOptions::default(),
1076 }),
1077 primitive: wgpu::PrimitiveState {
1078 topology: wgpu::PrimitiveTopology::TriangleList,
1079 ..Default::default()
1080 },
1081 depth_stencil: Some(depth.clone()),
1082 multisample: msaa_state,
1083 multiview_mask: None,
1084 cache: None,
1085 })
1086 };
1087 let mesh_color_target = wgpu::ColorTargetState {
1088 format,
1089 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1090 write_mask: wgpu::ColorWrites::ALL,
1091 };
1092 let mut stencil_for_mesh = stencil_for_content.clone();
1093 stencil_for_mesh.stencil.front.compare = wgpu::CompareFunction::Equal;
1094 stencil_for_mesh.stencil.back.compare = wgpu::CompareFunction::Equal;
1095 let mesh = make_mesh_pipeline("mesh pipeline", &stencil_for_mesh, &mesh_color_target);
1096 let mesh_overlay = make_mesh_pipeline(
1097 "mesh overlay pipeline",
1098 stencil_for_content,
1099 &mesh_color_target,
1100 );
1101 let mesh_clip_inc = make_mesh_pipeline(
1102 "mesh clip (inc) pipeline",
1103 stencil_for_clip_inc,
1104 clip_color_target,
1105 );
1106 let mesh_clip_dec = make_mesh_pipeline(
1107 "mesh clip (dec) pipeline",
1108 stencil_for_clip_dec,
1109 clip_color_target,
1110 );
1111
1112 let projective_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1117 label: Some("projective_layer.wgsl"),
1118 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
1119 "shaders/projective_layer.wgsl"
1120 ))),
1121 });
1122 let projective_pipeline_layout =
1123 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1124 label: Some("projective layer pipeline layout"),
1125 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
1126 immediate_size: 0,
1127 });
1128 let projective_vertex_layout = wgpu::VertexBufferLayout {
1129 array_stride: std::mem::size_of::<ProjectiveInstance>() as u64,
1130 step_mode: wgpu::VertexStepMode::Instance,
1131 attributes: &[
1132 wgpu::VertexAttribute {
1133 shader_location: 0,
1134 offset: 0,
1135 format: wgpu::VertexFormat::Float32x2,
1136 },
1137 wgpu::VertexAttribute {
1138 shader_location: 1,
1139 offset: 8,
1140 format: wgpu::VertexFormat::Float32x2,
1141 },
1142 wgpu::VertexAttribute {
1143 shader_location: 2,
1144 offset: 16,
1145 format: wgpu::VertexFormat::Float32x2,
1146 },
1147 wgpu::VertexAttribute {
1148 shader_location: 3,
1149 offset: 24,
1150 format: wgpu::VertexFormat::Float32x2,
1151 },
1152 wgpu::VertexAttribute {
1153 shader_location: 4,
1154 offset: 32,
1155 format: wgpu::VertexFormat::Float32x4,
1156 },
1157 wgpu::VertexAttribute {
1158 shader_location: 5,
1159 offset: 48,
1160 format: wgpu::VertexFormat::Float32x4,
1161 },
1162 wgpu::VertexAttribute {
1163 shader_location: 6,
1164 offset: 64,
1165 format: wgpu::VertexFormat::Float32,
1166 },
1167 ],
1168 };
1169 let projective_layer = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1170 label: Some("projective layer composite pipeline"),
1171 layout: Some(&projective_pipeline_layout),
1172 vertex: wgpu::VertexState {
1173 module: &projective_shader,
1174 entry_point: Some("vs_main"),
1175 buffers: &[Some(projective_vertex_layout)],
1176 compilation_options: wgpu::PipelineCompilationOptions::default(),
1177 },
1178 fragment: Some(wgpu::FragmentState {
1179 module: &projective_shader,
1180 entry_point: Some("fs_main"),
1181 targets: &[Some(wgpu::ColorTargetState {
1182 format,
1183 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1184 write_mask: wgpu::ColorWrites::ALL,
1185 })],
1186 compilation_options: wgpu::PipelineCompilationOptions::default(),
1187 }),
1188 primitive: wgpu::PrimitiveState::default(),
1189 depth_stencil: Some(stencil_for_content.clone()),
1190 multisample: msaa_state,
1191 multiview_mask: None,
1192 cache: None,
1193 });
1194
1195 Self {
1196 rects,
1197 borders,
1198 ellipses,
1199 ellipse_borders,
1200 arcs,
1201 text_mask,
1202 text_color,
1203 image_rgba,
1204 image_nv12,
1205 coverage,
1206 blur,
1207 blur_content,
1208 clip_a2c,
1209 clip_bin,
1210 clip_dec,
1211 slug,
1212 mesh,
1213 mesh_overlay,
1214 mesh_clip_inc,
1215 mesh_clip_dec,
1216 projective_layer,
1217 }
1218 }
1219}
1220
1221struct Pass {
1223 target: PassTarget,
1224 initial_scissor: (u32, u32, u32, u32),
1226 clear_color: Option<[f32; 4]>,
1229 cmds: Vec<Cmd>,
1230}
1231
1232struct FlattenRecord {
1236 stack_len: usize,
1239 layer_id: u32,
1240 map: [f32; 9],
1243 layer_rect: repose_core::Rect,
1245 saved_scissor: Vec<repose_core::Rect>,
1246 saved_root: repose_core::Rect,
1247 saved_size: (f32, f32),
1248}
1249
1250const FLATTEN_ID_BASE: u32 = 0xF000_0000;
1254
1255#[allow(non_snake_case)]
1256enum Cmd {
1257 ClipPush {
1258 off: u64,
1259 cnt: u32,
1260 scissor: (u32, u32, u32, u32),
1261 difference: bool,
1262 rounded: bool,
1263 },
1264 ClipPop {
1265 off: u64,
1266 cnt: u32,
1267 scissor: (u32, u32, u32, u32),
1268 difference: bool,
1269 },
1270 Rect {
1271 off: u64,
1272 cnt: u32,
1273 },
1274 Border {
1275 off: u64,
1276 cnt: u32,
1277 },
1278 Ellipse {
1279 off: u64,
1280 cnt: u32,
1281 },
1282 EllipseBorder {
1283 off: u64,
1284 cnt: u32,
1285 },
1286 Arc {
1287 off: u64,
1288 cnt: u32,
1289 },
1290 GlyphsMask {
1291 off: u64,
1292 cnt: u32,
1293 },
1294 GlyphsColor {
1295 off: u64,
1296 cnt: u32,
1297 },
1298 GlyphsVector {
1299 off: u64,
1300 cnt: u32,
1301 },
1302 ImageRgba {
1303 off: u64,
1304 cnt: u32,
1305 handle: u64,
1306 },
1307 Coverage {
1311 off: u64,
1312 cnt: u32,
1313 handle: u64,
1314 },
1315 ImageNv12 {
1316 off: u64,
1317 cnt: u32,
1318 handle: u64,
1319 },
1320 CompositeLayer {
1324 off: u64,
1325 cnt: u32,
1326 layer_id: u32,
1327 },
1328 CompositeShadow {
1332 off: u64,
1333 cnt: u32,
1334 layer_id: u32,
1335 },
1336 CompositeBlur {
1339 off: u64,
1340 cnt: u32,
1341 layer_id: u32,
1342 },
1343 CompositeProjective {
1348 off: u64,
1349 cnt: u32,
1350 layer_id: u32,
1351 },
1352 VectorMesh {
1354 voff: u64,
1355 vcnt: u32,
1356 ioff: u64,
1357 icnt: u32,
1358 uoff: u64,
1359 },
1360 VectorOverlay {
1362 voff: u64,
1363 vcnt: u32,
1364 ioff: u64,
1365 icnt: u32,
1366 uoff: u64,
1367 },
1368 VectorClipPush {
1373 voff: u64,
1374 vcnt: u32,
1375 ioff: u64,
1376 icnt: u32,
1377 uoff: u64,
1378 scissor: (u32, u32, u32, u32),
1379 difference: bool,
1380 },
1381 VectorClipPop {
1383 voff: u64,
1384 vcnt: u32,
1385 ioff: u64,
1386 icnt: u32,
1387 uoff: u64,
1388 scissor: (u32, u32, u32, u32),
1389 difference: bool,
1390 },
1391 Callback {
1392 rect: repose_core::Rect,
1393 payload: repose_core::PaintCallbackPayload,
1394 },
1395}
1396
1397struct CoverageTex {
1402 #[allow(dead_code)]
1404 tex: wgpu::Texture,
1405 bind: wgpu::BindGroup,
1406 w: u32,
1407 h: u32,
1408 last_used_frame: u64,
1409 bytes: u64,
1410}
1411
1412enum ImageTex {
1413 Rgba {
1414 tex: wgpu::Texture,
1415 bind: wgpu::BindGroup,
1416 w: u32,
1417 h: u32,
1418 format: wgpu::TextureFormat,
1419 last_used_frame: u64,
1420 bytes: u64,
1421 },
1422 User {
1424 bind: wgpu::BindGroup,
1425 w: u32,
1426 h: u32,
1427 last_used_frame: u64,
1428 bytes: u64,
1429 },
1430 Nv12 {
1431 tex_y: wgpu::Texture,
1432 tex_uv: wgpu::Texture,
1433 bind: wgpu::BindGroup,
1434 yuv_buf: wgpu::Buffer,
1435 w: u32,
1436 h: u32,
1437 color_info: ColorInfo,
1438 last_used_frame: u64,
1439 bytes: u64,
1440 },
1441}
1442
1443#[derive(Clone)]
1444struct RetainedImage {
1445 w: u32,
1446 h: u32,
1447 format: wgpu::TextureFormat,
1448 rgba: Vec<u8>,
1449}
1450
1451struct AtlasA8 {
1452 tex: wgpu::Texture,
1453 view: wgpu::TextureView,
1454 sampler: wgpu::Sampler,
1455 size: u32,
1456 next_x: u32,
1457 next_y: u32,
1458 row_h: u32,
1459 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
1460}
1461
1462struct AtlasRGBA {
1463 tex: wgpu::Texture,
1464 view: wgpu::TextureView,
1465 sampler: wgpu::Sampler,
1466 size: u32,
1467 next_x: u32,
1468 next_y: u32,
1469 row_h: u32,
1470 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
1471}
1472
1473#[derive(Clone, Copy)]
1474struct GlyphInfo {
1475 u0: f32,
1476 v0: f32,
1477 u1: f32,
1478 v1: f32,
1479 w: f32,
1480 h: f32,
1481}
1482
1483#[repr(C)]
1484#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1485struct RectInstance {
1486 xywh: [f32; 4],
1487 radii: [f32; 4],
1488 brush_type: u32,
1489 _pad: [f32; 3],
1490 color0: [f32; 4],
1491 color1: [f32; 4],
1492 grad_start: [f32; 2],
1493 grad_end: [f32; 2],
1494 fwd_mat: [f32; 4],
1495}
1496
1497#[repr(C)]
1498#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1499struct BorderInstance {
1500 xywh: [f32; 4],
1501 radii: [f32; 4],
1502 stroke: f32,
1503 color: [f32; 4],
1504 fwd_mat: [f32; 4],
1505}
1506
1507#[repr(C)]
1508#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1509struct EllipseInstance {
1510 xywh: [f32; 4],
1511 color: [f32; 4],
1512 fwd_mat: [f32; 4],
1513}
1514
1515#[repr(C)]
1516#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1517struct EllipseBorderInstance {
1518 xywh: [f32; 4],
1519 stroke: f32,
1520 pad: f32,
1521 color: [f32; 4],
1522 fwd_mat: [f32; 4],
1523}
1524
1525#[repr(C)]
1526#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1527struct ArcInstance {
1528 xywh: [f32; 4],
1529 start_angle: f32,
1530 sweep_angle: f32,
1531 stroke: f32,
1532 pad: f32,
1533 color: [f32; 4],
1534 fwd_mat: [f32; 4],
1535 cap: f32, }
1537
1538#[repr(C)]
1539#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1540struct GlyphInstance {
1541 xywh: [f32; 4],
1542 uv: [f32; 4],
1543 color: [f32; 4],
1544 fwd_mat: [f32; 4],
1545}
1546
1547#[repr(C)]
1548#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1549struct BlurInstance {
1550 xywh: [f32; 4],
1551 uv: [f32; 4],
1552 color: [f32; 4],
1553 blur_uv: [f32; 2],
1554 fwd_mat: [f32; 4],
1555}
1556
1557#[repr(C)]
1563#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1564struct ProjectiveInstance {
1565 c0: [f32; 2],
1566 c1: [f32; 2],
1567 c2: [f32; 2],
1568 c3: [f32; 2],
1569 uv: [f32; 4],
1570 w: [f32; 4],
1571 alpha: f32,
1572 _pad: [f32; 3],
1573}
1574
1575#[repr(C)]
1578#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1579struct YuvTransformRaw {
1580 row0: [f32; 4],
1581 row1: [f32; 4],
1582 row2: [f32; 4],
1583 b: [f32; 4],
1584}
1585
1586#[repr(C)]
1587#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1588struct Nv12Instance {
1589 xywh: [f32; 4],
1590 uv: [f32; 4],
1591 color: [f32; 4], uv_x_offset: f32,
1593 fwd_mat: [f32; 4],
1594 _pad: [f32; 1],
1595}
1596
1597#[repr(C)]
1598#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1599struct ClipInstance {
1600 xywh: [f32; 4],
1601 radii: [f32; 4],
1602 fwd_mat: [f32; 4],
1603}
1604
1605#[repr(C)]
1606#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1607struct MeshVertex {
1608 pos: [f32; 2],
1609 color: [f32; 4],
1610 uv: [f32; 2],
1611}
1612
1613#[repr(C)]
1614#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1615struct MeshUniform {
1616 m0: [f32; 4],
1617 m1: [f32; 4],
1618 paint: [u32; 4],
1619 color0: [f32; 4],
1620 color1: [f32; 4],
1621 grad_start: [f32; 2],
1622 _p3: [f32; 2],
1623 grad_end: [f32; 2],
1624 _p4: [f32; 2],
1625}
1626
1627const MESH_UNIFORM_SLOT: u64 = 256;
1629const MESH_UNIFORM_CAP: u64 = 4 * 1024 * 1024;
1630
1631impl MeshUniform {
1632 fn identity() -> Self {
1633 Self {
1634 m0: [1.0, 0.0, 0.0, 0.0],
1635 m1: [0.0, 1.0, 0.0, 0.0],
1636 paint: [0; 4],
1637 color0: [0.0; 4],
1638 color1: [0.0; 4],
1639 grad_start: [0.0; 2],
1640 _p3: [0.0; 2],
1641 grad_end: [0.0; 2],
1642 _p4: [0.0; 2],
1643 }
1644 }
1645}
1646
1647fn mesh_uniform_from_paint(affine: [f32; 6], paint: &repose_core::PaintDesc) -> MeshUniform {
1648 let (paint_type, color0, color1, grad_start, grad_end) = match paint {
1649 repose_core::PaintDesc::Solid => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
1650 repose_core::PaintDesc::Linear {
1651 start,
1652 end,
1653 start_color,
1654 end_color,
1655 } => (
1656 1u32,
1657 start_color.to_linear(),
1658 end_color.to_linear(),
1659 [start.x, start.y],
1660 [end.x, end.y],
1661 ),
1662 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
1664 };
1665 MeshUniform {
1666 m0: [affine[0], affine[1], affine[2], 0.0],
1667 m1: [affine[3], affine[4], affine[5], 0.0],
1668 paint: [paint_type, 0, 0, 0],
1669 color0,
1670 color1,
1671 grad_start,
1672 _p3: [0.0; 2],
1673 grad_end,
1674 _p4: [0.0; 2],
1675 }
1676}
1677
1678fn combine_mesh_affine(current: &Transform, mesh: [f32; 6]) -> [f32; 6] {
1679 let cm = current.linear();
1680 let (cm00, cm01, cm10, cm11) = (cm[0], cm[1], cm[2], cm[3]);
1681 let mm00 = mesh[0];
1682 let mm01 = mesh[1];
1683 let mm10 = mesh[2];
1684 let mm11 = mesh[3];
1685 let mtx = mesh[4];
1686 let mty = mesh[5];
1687 let r00 = cm00 * mm00 + cm01 * mm10;
1688 let r01 = cm00 * mm01 + cm01 * mm11;
1689 let r10 = cm10 * mm00 + cm11 * mm10;
1690 let r11 = cm10 * mm01 + cm11 * mm11;
1691 let tx = cm00 * mtx + cm01 * mty + current.translate_x;
1692 let ty = cm10 * mtx + cm11 * mty + current.translate_y;
1693 [r00, r01, tx, r10, r11, ty]
1696}
1697
1698fn mesh_aabb(mesh: &repose_core::VectorMeshData, affine: [f32; 6]) -> repose_core::Rect {
1699 let mut min_x = f32::MAX;
1700 let mut min_y = f32::MAX;
1701 let mut max_x = f32::MIN;
1702 let mut max_y = f32::MIN;
1703 for v in mesh.vertices.iter() {
1704 let x = affine[0] * v.pos[0] + affine[1] * v.pos[1] + affine[2];
1705 let y = affine[3] * v.pos[0] + affine[4] * v.pos[1] + affine[5];
1706 min_x = min_x.min(x);
1707 min_y = min_y.min(y);
1708 max_x = max_x.max(x);
1709 max_y = max_y.max(y);
1710 }
1711 let w = (max_x - min_x).max(0.0);
1712 let h = (max_y - min_y).max(0.0);
1713 if !min_x.is_finite() || !min_y.is_finite() {
1714 return repose_core::Rect {
1715 x: 0.0,
1716 y: 0.0,
1717 w: 0.0,
1718 h: 0.0,
1719 };
1720 }
1721 repose_core::Rect {
1722 x: min_x,
1723 y: min_y,
1724 w,
1725 h,
1726 }
1727}
1728
1729fn swash_to_a8_coverage(content: repose_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
1730 match content {
1731 repose_text::SwashContent::Mask => Some(data.to_vec()),
1732 repose_text::SwashContent::SubpixelMask => {
1733 let mut out = Vec::with_capacity(data.len() / 4);
1734 for px in data.as_chunks::<4>().0 {
1735 let r = px[0];
1736 let g = px[1];
1737 let b = px[2];
1738 out.push(r.max(g).max(b));
1739 }
1740 Some(out)
1741 }
1742 repose_text::SwashContent::Color => None,
1743 }
1744}
1745
1746impl WgpuSceneRenderer {
1747 pub fn from_device(
1748 device: wgpu::Device,
1749 queue: wgpu::Queue,
1750 output_format: wgpu::TextureFormat,
1751 msaa_samples: u32,
1752 ) -> Self {
1753 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1754 label: Some("globals layout"),
1755 entries: &[wgpu::BindGroupLayoutEntry {
1756 binding: 0,
1757 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1758 ty: wgpu::BindingType::Buffer {
1759 ty: wgpu::BufferBindingType::Uniform,
1760 has_dynamic_offset: false,
1761 min_binding_size: None,
1762 },
1763 count: None,
1764 }],
1765 });
1766
1767 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
1768 label: Some("globals buf"),
1769 size: std::mem::size_of::<Globals>() as u64,
1770 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1771 mapped_at_creation: false,
1772 });
1773
1774 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1775 label: Some("globals bind"),
1776 layout: &globals_layout,
1777 entries: &[wgpu::BindGroupEntry {
1778 binding: 0,
1779 resource: globals_buf.as_entire_binding(),
1780 }],
1781 });
1782
1783 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
1784
1785 let stencil_for_content = wgpu::DepthStencilState {
1786 format: ds_format,
1787 depth_write_enabled: Some(false),
1788 depth_compare: Some(wgpu::CompareFunction::Always),
1789 stencil: wgpu::StencilState {
1790 front: wgpu::StencilFaceState {
1791 compare: wgpu::CompareFunction::Equal,
1798 fail_op: wgpu::StencilOperation::Keep,
1799 depth_fail_op: wgpu::StencilOperation::Keep,
1800 pass_op: wgpu::StencilOperation::Keep,
1801 },
1802 back: wgpu::StencilFaceState {
1803 compare: wgpu::CompareFunction::Equal,
1804 fail_op: wgpu::StencilOperation::Keep,
1805 depth_fail_op: wgpu::StencilOperation::Keep,
1806 pass_op: wgpu::StencilOperation::Keep,
1807 },
1808 read_mask: 0xFF,
1809 write_mask: 0x00,
1810 },
1811 bias: wgpu::DepthBiasState::default(),
1812 };
1813
1814 let stencil_for_clip_inc = wgpu::DepthStencilState {
1815 format: ds_format,
1816 depth_write_enabled: Some(false),
1817 depth_compare: Some(wgpu::CompareFunction::Always),
1818 stencil: wgpu::StencilState {
1819 front: wgpu::StencilFaceState {
1820 compare: wgpu::CompareFunction::Equal,
1821 fail_op: wgpu::StencilOperation::Keep,
1822 depth_fail_op: wgpu::StencilOperation::Keep,
1823 pass_op: wgpu::StencilOperation::IncrementClamp,
1824 },
1825 back: wgpu::StencilFaceState {
1826 compare: wgpu::CompareFunction::Equal,
1827 fail_op: wgpu::StencilOperation::Keep,
1828 depth_fail_op: wgpu::StencilOperation::Keep,
1829 pass_op: wgpu::StencilOperation::IncrementClamp,
1830 },
1831 read_mask: 0xFF,
1832 write_mask: 0xFF,
1833 },
1834 bias: wgpu::DepthBiasState::default(),
1835 };
1836
1837 let stencil_for_clip_dec = wgpu::DepthStencilState {
1838 format: ds_format,
1839 depth_write_enabled: Some(false),
1840 depth_compare: Some(wgpu::CompareFunction::Always),
1841 stencil: wgpu::StencilState {
1842 front: wgpu::StencilFaceState {
1843 compare: wgpu::CompareFunction::Equal,
1844 fail_op: wgpu::StencilOperation::Keep,
1845 depth_fail_op: wgpu::StencilOperation::Keep,
1846 pass_op: wgpu::StencilOperation::DecrementClamp,
1847 },
1848 back: wgpu::StencilFaceState {
1849 compare: wgpu::CompareFunction::Equal,
1850 fail_op: wgpu::StencilOperation::Keep,
1851 depth_fail_op: wgpu::StencilOperation::Keep,
1852 pass_op: wgpu::StencilOperation::DecrementClamp,
1853 },
1854 read_mask: 0xFF,
1855 write_mask: 0xFF,
1856 },
1857 bias: wgpu::DepthBiasState::default(),
1858 };
1859
1860 let _multisample_state = wgpu::MultisampleState {
1861 count: msaa_samples,
1862 mask: !0,
1863 alpha_to_coverage_enabled: false,
1864 };
1865
1866 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1870 label: Some("image/text sampler"),
1871 address_mode_u: wgpu::AddressMode::ClampToEdge,
1872 address_mode_v: wgpu::AddressMode::ClampToEdge,
1873 mag_filter: wgpu::FilterMode::Linear,
1874 min_filter: wgpu::FilterMode::Linear,
1875 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1876 ..Default::default()
1877 });
1878
1879 let layer_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1881 label: Some("layer nearest sampler"),
1882 address_mode_u: wgpu::AddressMode::ClampToEdge,
1883 address_mode_v: wgpu::AddressMode::ClampToEdge,
1884 mag_filter: wgpu::FilterMode::Nearest,
1885 min_filter: wgpu::FilterMode::Nearest,
1886 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
1887 ..Default::default()
1888 });
1889
1890 let layer_sampler_linear = device.create_sampler(&wgpu::SamplerDescriptor {
1893 label: Some("layer linear sampler"),
1894 address_mode_u: wgpu::AddressMode::ClampToEdge,
1895 address_mode_v: wgpu::AddressMode::ClampToEdge,
1896 mag_filter: wgpu::FilterMode::Linear,
1897 min_filter: wgpu::FilterMode::Linear,
1898 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1899 ..Default::default()
1900 });
1901
1902 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1904 label: Some("text/rgba bind layout"),
1905 entries: &[
1906 wgpu::BindGroupLayoutEntry {
1907 binding: 0,
1908 visibility: wgpu::ShaderStages::FRAGMENT,
1909 ty: wgpu::BindingType::Texture {
1910 multisampled: false,
1911 view_dimension: wgpu::TextureViewDimension::D2,
1912 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1913 },
1914 count: None,
1915 },
1916 wgpu::BindGroupLayoutEntry {
1917 binding: 1,
1918 visibility: wgpu::ShaderStages::FRAGMENT,
1919 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1920 count: None,
1921 },
1922 ],
1923 });
1924 let image_bind_layout_rgba = text_bind_layout.clone();
1926
1927 let image_bind_layout_nv12 =
1929 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1930 label: Some("image bind layout nv12"),
1931 entries: &[
1932 wgpu::BindGroupLayoutEntry {
1934 binding: 0,
1935 visibility: wgpu::ShaderStages::FRAGMENT,
1936 ty: wgpu::BindingType::Texture {
1937 multisampled: false,
1938 view_dimension: wgpu::TextureViewDimension::D2,
1939 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1940 },
1941 count: None,
1942 },
1943 wgpu::BindGroupLayoutEntry {
1945 binding: 1,
1946 visibility: wgpu::ShaderStages::FRAGMENT,
1947 ty: wgpu::BindingType::Texture {
1948 multisampled: false,
1949 view_dimension: wgpu::TextureViewDimension::D2,
1950 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1951 },
1952 count: None,
1953 },
1954 wgpu::BindGroupLayoutEntry {
1956 binding: 2,
1957 visibility: wgpu::ShaderStages::FRAGMENT,
1958 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1959 count: None,
1960 },
1961 wgpu::BindGroupLayoutEntry {
1963 binding: 3,
1964 visibility: wgpu::ShaderStages::FRAGMENT,
1965 ty: wgpu::BindingType::Buffer {
1966 ty: wgpu::BufferBindingType::Uniform,
1967 has_dynamic_offset: false,
1968 min_binding_size: None,
1969 },
1970 count: None,
1971 },
1972 ],
1973 });
1974
1975 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1977 label: Some("clip pipeline layout"),
1978 bind_group_layouts: &[Some(&globals_layout)],
1979 immediate_size: 0,
1980 });
1981 let clip_vertex_layout = wgpu::VertexBufferLayout {
1982 array_stride: std::mem::size_of::<ClipInstance>() as u64,
1983 step_mode: wgpu::VertexStepMode::Instance,
1984 attributes: &[
1985 wgpu::VertexAttribute {
1986 shader_location: 0,
1987 offset: 0,
1988 format: wgpu::VertexFormat::Float32x4,
1989 },
1990 wgpu::VertexAttribute {
1991 shader_location: 1,
1992 offset: 16,
1993 format: wgpu::VertexFormat::Float32x4,
1994 },
1995 wgpu::VertexAttribute {
1996 shader_location: 2,
1997 offset: 32,
1998 format: wgpu::VertexFormat::Float32x4,
1999 },
2000 ],
2001 };
2002 let clip_color_target = wgpu::ColorTargetState {
2003 format: output_format,
2004 blend: None,
2005 write_mask: wgpu::ColorWrites::empty(),
2006 };
2007
2008 let mesh_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
2010 label: Some("mesh uniform layout"),
2011 entries: &[wgpu::BindGroupLayoutEntry {
2012 binding: 0,
2013 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
2014 ty: wgpu::BindingType::Buffer {
2015 ty: wgpu::BufferBindingType::Uniform,
2016 has_dynamic_offset: true,
2017 min_binding_size: NonZero::new(MESH_UNIFORM_SLOT),
2018 },
2019 count: None,
2020 }],
2021 });
2022 let mesh_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
2023 label: Some("mesh uniform buffer"),
2024 size: MESH_UNIFORM_CAP,
2025 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2026 mapped_at_creation: false,
2027 });
2028 let mesh_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
2029 label: Some("mesh uniform bind"),
2030 layout: &mesh_bind_layout,
2031 entries: &[wgpu::BindGroupEntry {
2032 binding: 0,
2033 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2034 buffer: &mesh_uniform_buf,
2035 offset: 0,
2036 size: NonZero::new(MESH_UNIFORM_SLOT),
2037 }),
2038 }],
2039 });
2040
2041 let surface_pipes = Pipelines::create(
2044 &device,
2045 output_format,
2046 msaa_samples,
2047 &globals_layout,
2048 &text_bind_layout,
2049 &image_bind_layout_nv12,
2050 &clip_pipeline_layout,
2051 &stencil_for_content,
2052 &stencil_for_clip_inc,
2053 &stencil_for_clip_dec,
2054 &clip_color_target,
2055 &clip_vertex_layout,
2056 &mesh_bind_layout,
2057 );
2058 let layer_pipes = Pipelines::create(
2059 &device,
2060 output_format,
2061 1,
2062 &globals_layout,
2063 &text_bind_layout,
2064 &image_bind_layout_nv12,
2065 &clip_pipeline_layout,
2066 &stencil_for_content,
2067 &stencil_for_clip_inc,
2068 &stencil_for_clip_dec,
2069 &clip_color_target,
2070 &clip_vertex_layout,
2071 &mesh_bind_layout,
2072 );
2073
2074 let slug_enabled = true;
2076
2077 let blur_ring = UploadRing::new(
2079 &device,
2080 "blur ring",
2081 1024 * 1024,
2082 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2083 );
2084
2085 let atlas_mask = init_atlas_mask(&device);
2087 let atlas_color = init_atlas_color(&device);
2088
2089 let ring_rect = UploadRing::new(
2091 &device,
2092 "ring rect",
2093 1 << 20,
2094 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2095 );
2096 let ring_border = UploadRing::new(
2097 &device,
2098 "ring border",
2099 1 << 20,
2100 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2101 );
2102 let ring_ellipse = UploadRing::new(
2103 &device,
2104 "ring ellipse",
2105 1 << 20,
2106 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2107 );
2108 let ring_ellipse_border = UploadRing::new(
2109 &device,
2110 "ring ellipse border",
2111 1 << 20,
2112 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2113 );
2114 let ring_arc = UploadRing::new(
2115 &device,
2116 "ring arc",
2117 1 << 20,
2118 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2119 );
2120 let ring_glyph_mask = UploadRing::new(
2121 &device,
2122 "ring glyph mask",
2123 1 << 20,
2124 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2125 );
2126 let ring_glyph_color = UploadRing::new(
2127 &device,
2128 "ring glyph color",
2129 1 << 20,
2130 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2131 );
2132 let ring_slug = UploadRing::new(
2133 &device,
2134 "ring slug",
2135 1 << 22,
2136 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2137 );
2138 let ring_clip = UploadRing::new(
2139 &device,
2140 "ring clip",
2141 1 << 16,
2142 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2143 );
2144 let ring_projective = UploadRing::new(
2145 &device,
2146 "ring projective",
2147 1 << 16,
2148 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2149 );
2150 let ring_nv12 = UploadRing::new(
2151 &device,
2152 "ring nv12",
2153 1 << 20,
2154 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2155 );
2156 let ring_mesh_verts = UploadRing::new(
2157 &device,
2158 "ring mesh verts",
2159 1 << 22,
2160 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2161 );
2162 let ring_mesh_indices = UploadRing::new(
2163 &device,
2164 "ring mesh indices",
2165 1 << 22,
2166 wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
2167 );
2168
2169 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
2171 label: Some("temp ds"),
2172 size: wgpu::Extent3d {
2173 width: 1,
2174 height: 1,
2175 depth_or_array_layers: 1,
2176 },
2177 mip_level_count: 1,
2178 sample_count: 1,
2179 dimension: wgpu::TextureDimension::D2,
2180 format: wgpu::TextureFormat::Depth24PlusStencil8,
2181 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
2182 view_formats: &[],
2183 });
2184 let depth_stencil_view =
2185 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
2186
2187 let mut renderer = WgpuSceneRenderer {
2188 device,
2189 queue,
2190 output_format,
2191 output_width: 0,
2192 output_height: 0,
2193 pixels_per_point: 1.0,
2194
2195 surface_pipes,
2196 layer_pipes,
2197
2198 rects: InstancedPipe::new(ring_rect),
2199 borders: InstancedPipe::new(ring_border),
2200 ellipses: InstancedPipe::new(ring_ellipse),
2201 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
2202 arcs: InstancedPipe::new(ring_arc),
2203 glyph_mask: InstancedPipe::new(ring_glyph_mask),
2204 glyph_color: InstancedPipe::new(ring_glyph_color),
2205
2206 text_bind_layout,
2207
2208 image_bind_layout_rgba,
2209 image_bind_layout_nv12,
2210 image_sampler,
2211 layer_sampler,
2212 layer_sampler_linear,
2213
2214 blur_ring,
2215
2216 slug_enabled,
2217 slug_ring: ring_slug,
2218 slug_cache: slug::GlyphSlugCache::new(),
2219
2220 clip_ring: ring_clip,
2221
2222 nv12: InstancedPipe::new(ring_nv12),
2223
2224 mesh_verts: ring_mesh_verts,
2225 mesh_indices: ring_mesh_indices,
2226 mesh_uniform_buf,
2227 mesh_bind_layout,
2228 mesh_bind,
2229 mesh_uniform_head: 0,
2230 mesh_clip_stack: Vec::new(),
2231
2232 projective_ring: ring_projective,
2233 flatten_layer_ids: Vec::new(),
2234
2235 msaa_samples,
2236 depth_stencil_tex,
2237 depth_stencil_view,
2238 msaa_tex: None,
2239 msaa_view: None,
2240 globals_bind,
2241 globals_buf,
2242
2243 atlas_mask,
2244 atlas_color,
2245
2246 next_image_handle: 1,
2247 images: HashMap::new(),
2248 retained: HashMap::new(),
2249
2250 next_coverage_handle: 1,
2251 coverages: HashMap::new(),
2252
2253 frame_index: 0,
2254 image_bytes_total: 0,
2255 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
2258
2259 working_space: false,
2260 ws_tex: None,
2261 ws_view: None,
2262 ws_bind: None,
2263 display_pipeline: None,
2264 display_layout: None,
2265
2266 callback_resources: CallbackResources::default(),
2267 };
2268
2269 renderer.recreate_msaa_and_depth_stencil();
2270 renderer
2271 }
2272}
2273
2274impl WgpuSurfaceBackend {
2275 #[cfg(feature = "winit-surface")]
2276 pub async fn new_async(
2277 window: Arc<winit::window::Window>,
2278 ) -> anyhow::Result<WgpuSurfaceBackend> {
2279 Self::new_async_with_options(window, 4, PresentModePref::Auto).await
2280 }
2281
2282 #[cfg(feature = "winit-surface")]
2285 pub async fn new_async_with_msaa(
2286 window: Arc<winit::window::Window>,
2287 msaa_samples: u32,
2288 ) -> anyhow::Result<WgpuSurfaceBackend> {
2289 Self::new_async_with_options(window, msaa_samples, PresentModePref::Auto).await
2290 }
2291
2292 #[cfg(feature = "winit-surface")]
2295 pub async fn new_async_with_options(
2296 window: Arc<winit::window::Window>,
2297 msaa_samples: u32,
2298 present_mode: PresentModePref,
2299 ) -> anyhow::Result<WgpuSurfaceBackend> {
2300 let instance: Instance = if cfg!(target_arch = "wasm32") {
2301 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
2302 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
2303 wgpu::util::new_instance_with_webgpu_detection(desc).await
2304 } else {
2305 wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle())
2306 };
2307
2308 let surface = instance.create_surface(window.clone())?;
2309
2310 let adapter = instance
2311 .request_adapter(&wgpu::RequestAdapterOptions {
2312 power_preference: wgpu::PowerPreference::HighPerformance,
2313 compatible_surface: Some(&surface),
2314 force_fallback_adapter: false,
2315 apply_limit_buckets: false,
2316 })
2317 .await
2318 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
2319
2320 let limits = adapter.limits();
2321
2322 #[cfg(target_os = "linux")]
2323 let features = {
2324 let af = adapter.features();
2325 let mut f = wgpu::Features::empty();
2326 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD) {
2327 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD;
2328 }
2329 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF) {
2330 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF;
2331 }
2332 f
2333 };
2334 #[cfg(not(target_os = "linux"))]
2335 let features = wgpu::Features::empty();
2336
2337 let (device, queue) = adapter
2338 .request_device(&wgpu::DeviceDescriptor {
2339 label: Some("repose-rs device"),
2340 required_features: features,
2341 required_limits: limits,
2342 experimental_features: wgpu::ExperimentalFeatures::disabled(),
2343 memory_hints: wgpu::MemoryHints::default(),
2344 trace: wgpu::Trace::Off,
2345 })
2346 .await
2347 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
2348
2349 let size = window.inner_size();
2350
2351 let caps = surface.get_capabilities(&adapter);
2352
2353 let (format, view_format) = if cfg!(target_arch = "wasm32")
2354 && adapter
2355 .get_downlevel_capabilities()
2356 .flags
2357 .contains(wgpu::DownlevelFlags::SURFACE_VIEW_FORMATS)
2358 {
2359 let non_srgb = caps
2360 .formats
2361 .iter()
2362 .copied()
2363 .find(|f| !f.is_srgb())
2364 .unwrap_or(caps.formats[0]);
2365 (non_srgb, Some(non_srgb.add_srgb_suffix()))
2366 } else if cfg!(target_arch = "wasm32") {
2367 let fmt = caps
2368 .formats
2369 .iter()
2370 .copied()
2371 .find(|f| f.is_srgb())
2372 .unwrap_or(caps.formats[0]);
2373 (fmt, None)
2374 } else {
2375 let fmt = caps
2376 .formats
2377 .iter()
2378 .copied()
2379 .find(|f| f.is_srgb())
2380 .unwrap_or(caps.formats[0]);
2381 (fmt, None)
2382 };
2383
2384 let present_mode = pick_present_mode(&caps, present_mode);
2385 let alpha_mode = caps.alpha_modes[0];
2386
2387 let render_format = view_format.unwrap_or(format);
2388 let msaa_samples = pick_surface_msaa(&adapter, format, msaa_samples);
2389 let renderer = WgpuSceneRenderer::from_device(device, queue, render_format, msaa_samples);
2390
2391 let view_formats = view_format.into_iter().collect::<Vec<_>>();
2392
2393 let config = wgpu::SurfaceConfiguration {
2394 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
2395 format,
2396 width: size.width.max(1),
2397 height: size.height.max(1),
2398 present_mode,
2399 alpha_mode,
2400 color_space: wgpu::SurfaceColorSpace::Auto,
2401 view_formats,
2402 desired_maximum_frame_latency: 1,
2403 };
2404 surface.configure(&renderer.device, &config);
2405
2406 Ok(WgpuSurfaceBackend {
2407 surface: Some(surface),
2408 surface_config: Some(config),
2409 renderer,
2410 })
2411 }
2412
2413 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2414 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2415 pollster::block_on(Self::new_async(window))
2416 }
2417
2418 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2419 pub fn new_with_msaa(
2420 window: Arc<winit::window::Window>,
2421 msaa_samples: u32,
2422 ) -> anyhow::Result<WgpuSurfaceBackend> {
2423 pollster::block_on(Self::new_async_with_msaa(window, msaa_samples))
2424 }
2425
2426 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2427 pub fn new_with_options(
2428 window: Arc<winit::window::Window>,
2429 msaa_samples: u32,
2430 present_mode: PresentModePref,
2431 ) -> anyhow::Result<WgpuSurfaceBackend> {
2432 pollster::block_on(Self::new_async_with_options(
2433 window,
2434 msaa_samples,
2435 present_mode,
2436 ))
2437 }
2438
2439 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2440 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2441 anyhow::bail!("Use WgpuSurfaceBackend::new_async(window).await on wasm32")
2442 }
2443
2444 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2445 pub fn new_with_msaa(
2446 _window: Arc<winit::window::Window>,
2447 _msaa_samples: u32,
2448 ) -> anyhow::Result<WgpuSurfaceBackend> {
2449 anyhow::bail!("Use WgpuSurfaceBackend::new_async_with_msaa(window, msaa).await on wasm32")
2450 }
2451
2452 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2453 pub fn new_with_options(
2454 _window: Arc<winit::window::Window>,
2455 _msaa_samples: u32,
2456 _present_mode: PresentModePref,
2457 ) -> anyhow::Result<WgpuSurfaceBackend> {
2458 anyhow::bail!(
2459 "Use WgpuSurfaceBackend::new_async_with_options(window, msaa, mode).await on wasm32"
2460 )
2461 }
2462}
2463
2464fn pick_present_mode(caps: &wgpu::SurfaceCapabilities, pref: PresentModePref) -> wgpu::PresentMode {
2467 let auto = || {
2468 caps.present_modes
2469 .iter()
2470 .copied()
2471 .find(|m| *m == wgpu::PresentMode::Fifo)
2472 .or_else(|| {
2473 caps.present_modes
2474 .iter()
2475 .copied()
2476 .find(|m| *m == wgpu::PresentMode::Mailbox)
2477 })
2478 .unwrap_or(wgpu::PresentMode::Immediate)
2479 };
2480 match pref {
2481 PresentModePref::Auto => auto(),
2482 PresentModePref::Fifo if caps.present_modes.contains(&wgpu::PresentMode::Fifo) => {
2483 wgpu::PresentMode::Fifo
2484 }
2485 PresentModePref::Mailbox if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) => {
2486 wgpu::PresentMode::Mailbox
2487 }
2488 PresentModePref::Immediate
2489 if caps.present_modes.contains(&wgpu::PresentMode::Immediate) =>
2490 {
2491 wgpu::PresentMode::Immediate
2492 }
2493 _ => auto(),
2494 }
2495}
2496
2497pub fn pick_surface_msaa(
2500 adapter: &wgpu::Adapter,
2501 format: wgpu::TextureFormat,
2502 requested: u32,
2503) -> u32 {
2504 let requested = requested.max(1);
2505 let color_feat = adapter.get_texture_format_features(format);
2506 let depth_feat = adapter.get_texture_format_features(wgpu::TextureFormat::Depth24PlusStencil8);
2507 let supported = |n: u32| {
2508 color_feat.flags.sample_count_supported(n)
2509 && color_feat
2510 .flags
2511 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
2512 && depth_feat.flags.sample_count_supported(n)
2513 };
2514 let mut candidates = vec![requested];
2515 for n in [8, 4, 2, 1] {
2516 if n < requested {
2517 candidates.push(n);
2518 }
2519 }
2520 let chosen = candidates.into_iter().find(|&n| supported(n)).unwrap_or(1);
2521 if chosen != requested {
2522 log::info!("requested MSAA x{requested}, using x{chosen}");
2523 }
2524 chosen
2525}
2526
2527impl WgpuSceneRenderer {
2528 pub fn set_image_from_bytes(
2531 &mut self,
2532 handle: u64,
2533 data: &[u8],
2534 srgb: bool,
2535 ) -> anyhow::Result<()> {
2536 let img = image::load_from_memory(data)?;
2537 let rgba = img.to_rgba8();
2538 let (w, h) = rgba.dimensions();
2539 self.set_image_rgba8(handle, w, h, &rgba, srgb)
2540 }
2541
2542 pub fn set_image_rgba8(
2543 &mut self,
2544 handle: u64,
2545 w: u32,
2546 h: u32,
2547 rgba: &[u8],
2548 srgb: bool,
2549 ) -> anyhow::Result<()> {
2550 let expected = (w as usize) * (h as usize) * 4;
2551 if rgba.len() < expected {
2552 return Err(anyhow::anyhow!(
2553 "RGBA buffer too small: {} < {}",
2554 rgba.len(),
2555 expected
2556 ));
2557 }
2558
2559 let format = if srgb {
2560 wgpu::TextureFormat::Rgba8UnormSrgb
2561 } else {
2562 wgpu::TextureFormat::Rgba8Unorm
2563 };
2564
2565 let needs_recreate = match self.images.get(&handle) {
2566 Some(ImageTex::Rgba {
2567 w: cw,
2568 h: ch,
2569 format: cf,
2570 ..
2571 }) => *cw != w || *ch != h || *cf != format,
2572 _ => true,
2573 };
2574
2575 if needs_recreate {
2576 self.remove_image(handle);
2577
2578 let (tex, bind) = self.create_rgba_tex(w, h, format);
2579 let bytes = (w as u64) * (h as u64) * 4;
2580 self.image_bytes_total += bytes;
2581
2582 self.images.insert(
2583 handle,
2584 ImageTex::Rgba {
2585 tex,
2586 bind,
2587 w,
2588 h,
2589 format,
2590 last_used_frame: self.frame_index,
2591 bytes,
2592 },
2593 );
2594 }
2595
2596 self.retained.insert(
2597 handle,
2598 RetainedImage {
2599 w,
2600 h,
2601 format,
2602 rgba: rgba[..expected].to_vec(),
2603 },
2604 );
2605
2606 let tex = match self.images.get(&handle) {
2607 Some(ImageTex::Rgba { tex, .. }) => tex,
2608 _ => unreachable!(),
2609 };
2610
2611 self.queue.write_texture(
2612 wgpu::TexelCopyTextureInfo {
2613 texture: tex,
2614 mip_level: 0,
2615 origin: wgpu::Origin3d::ZERO,
2616 aspect: wgpu::TextureAspect::All,
2617 },
2618 &rgba[..expected],
2619 wgpu::TexelCopyBufferLayout {
2620 offset: 0,
2621 bytes_per_row: Some(4 * w),
2622 rows_per_image: Some(h),
2623 },
2624 wgpu::Extent3d {
2625 width: w,
2626 height: h,
2627 depth_or_array_layers: 1,
2628 },
2629 );
2630
2631 self.evict_budget_excess();
2633
2634 Ok(())
2635 }
2636
2637 fn create_rgba_tex(
2640 &self,
2641 w: u32,
2642 h: u32,
2643 format: wgpu::TextureFormat,
2644 ) -> (wgpu::Texture, wgpu::BindGroup) {
2645 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2646 label: Some("user image rgba"),
2647 size: wgpu::Extent3d {
2648 width: w,
2649 height: h,
2650 depth_or_array_layers: 1,
2651 },
2652 mip_level_count: 1,
2653 sample_count: 1,
2654 dimension: wgpu::TextureDimension::D2,
2655 format,
2656 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2657 view_formats: &[],
2658 });
2659 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
2660
2661 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2662 label: Some("image bind rgba"),
2663 layout: &self.image_bind_layout_rgba,
2664 entries: &[
2665 wgpu::BindGroupEntry {
2666 binding: 0,
2667 resource: wgpu::BindingResource::TextureView(&view),
2668 },
2669 wgpu::BindGroupEntry {
2670 binding: 1,
2671 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2672 },
2673 ],
2674 });
2675
2676 (tex, bind)
2677 }
2678
2679 pub fn register_native_texture(
2681 &mut self,
2682 view: &wgpu::TextureView,
2683 width: u32,
2684 height: u32,
2685 ) -> u64 {
2686 let handle = self.next_image_handle;
2687 self.next_image_handle += 1;
2688 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2689 label: Some("user native image"),
2690 layout: &self.image_bind_layout_rgba,
2691 entries: &[
2692 wgpu::BindGroupEntry {
2693 binding: 0,
2694 resource: wgpu::BindingResource::TextureView(view),
2695 },
2696 wgpu::BindGroupEntry {
2697 binding: 1,
2698 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2699 },
2700 ],
2701 });
2702 self.images.insert(
2703 handle,
2704 ImageTex::User {
2705 bind,
2706 w: width,
2707 h: height,
2708 last_used_frame: self.frame_index,
2709 bytes: 0,
2710 },
2711 );
2712 handle
2713 }
2714
2715 pub fn register_native_texture_with_sampler(
2717 &mut self,
2718 view: &wgpu::TextureView,
2719 sampler_desc: wgpu::SamplerDescriptor<'_>,
2720 width: u32,
2721 height: u32,
2722 ) -> u64 {
2723 let handle = self.next_image_handle;
2724 self.next_image_handle += 1;
2725 let sampler = self.device.create_sampler(&sampler_desc);
2726 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2727 label: Some("user native image sampleropts"),
2728 layout: &self.image_bind_layout_rgba,
2729 entries: &[
2730 wgpu::BindGroupEntry {
2731 binding: 0,
2732 resource: wgpu::BindingResource::TextureView(view),
2733 },
2734 wgpu::BindGroupEntry {
2735 binding: 1,
2736 resource: wgpu::BindingResource::Sampler(&sampler),
2737 },
2738 ],
2739 });
2740 self.images.insert(
2741 handle,
2742 ImageTex::User {
2743 bind,
2744 w: width,
2745 h: height,
2746 last_used_frame: self.frame_index,
2747 bytes: 0,
2748 },
2749 );
2750 handle
2751 }
2752
2753 pub fn update_native_texture(&mut self, handle: u64, view: &wgpu::TextureView) {
2755 let Some(entry) = self.images.get_mut(&handle) else {
2756 log::warn!("update_native_texture: handle {handle} not found");
2757 return;
2758 };
2759 let w = match entry {
2760 ImageTex::User { w, .. } => *w,
2761 ImageTex::Rgba { w, .. } => *w,
2762 _ => {
2763 log::warn!("update_native_texture: handle {handle} is not rgba/user");
2764 return;
2765 }
2766 };
2767 let h = match entry {
2768 ImageTex::User { h, .. } => *h,
2769 ImageTex::Rgba { h, .. } => *h,
2770 _ => 0,
2771 };
2772 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2773 label: Some("user native image update"),
2774 layout: &self.image_bind_layout_rgba,
2775 entries: &[
2776 wgpu::BindGroupEntry {
2777 binding: 0,
2778 resource: wgpu::BindingResource::TextureView(view),
2779 },
2780 wgpu::BindGroupEntry {
2781 binding: 1,
2782 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2783 },
2784 ],
2785 });
2786 *entry = ImageTex::User {
2787 bind,
2788 w,
2789 h,
2790 last_used_frame: self.frame_index,
2791 bytes: 0,
2792 };
2793 }
2794
2795 pub fn set_image_nv12(
2796 &mut self,
2797 handle: u64,
2798 w: u32,
2799 h: u32,
2800 y: &[u8],
2801 uv: &[u8],
2802 color_info: ColorInfo,
2803 ) -> anyhow::Result<()> {
2804 let y_expected = (w as usize) * (h as usize);
2805 let uv_w = w.div_ceil(2);
2806 let uv_h = h.div_ceil(2);
2807 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
2808
2809 if y.len() < y_expected {
2810 return Err(anyhow::anyhow!("Y plane too small"));
2811 }
2812 if uv.len() < uv_expected {
2813 return Err(anyhow::anyhow!("UV plane too small"));
2814 }
2815
2816 let needs_recreate = match self.images.get(&handle) {
2817 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
2818 _ => true,
2819 };
2820
2821 let yuv = color_info.to_yuv_transform();
2823 let yuv_raw = YuvTransformRaw {
2824 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2825 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2826 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2827 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
2828 };
2829
2830 if needs_recreate {
2831 self.remove_image(handle);
2832
2833 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
2834 label: Some("nv12 Y"),
2835 size: wgpu::Extent3d {
2836 width: w,
2837 height: h,
2838 depth_or_array_layers: 1,
2839 },
2840 mip_level_count: 1,
2841 sample_count: 1,
2842 dimension: wgpu::TextureDimension::D2,
2843 format: wgpu::TextureFormat::R8Unorm,
2844 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2845 view_formats: &[],
2846 });
2847 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
2848
2849 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
2850 label: Some("nv12 UV"),
2851 size: wgpu::Extent3d {
2852 width: uv_w,
2853 height: uv_h,
2854 depth_or_array_layers: 1,
2855 },
2856 mip_level_count: 1,
2857 sample_count: 1,
2858 dimension: wgpu::TextureDimension::D2,
2859 format: wgpu::TextureFormat::Rg8Unorm,
2860 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2861 view_formats: &[],
2862 });
2863 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
2864
2865 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2867 label: Some("nv12 yuv transform"),
2868 size: std::mem::size_of::<YuvTransformRaw>() as u64,
2869 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2870 mapped_at_creation: false,
2871 });
2872
2873 self.queue
2875 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2876
2877 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2878 label: Some("nv12 bind"),
2879 layout: &self.image_bind_layout_nv12,
2880 entries: &[
2881 wgpu::BindGroupEntry {
2882 binding: 0,
2883 resource: wgpu::BindingResource::TextureView(&view_y),
2884 },
2885 wgpu::BindGroupEntry {
2886 binding: 1,
2887 resource: wgpu::BindingResource::TextureView(&view_uv),
2888 },
2889 wgpu::BindGroupEntry {
2890 binding: 2,
2891 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2892 },
2893 wgpu::BindGroupEntry {
2894 binding: 3,
2895 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2896 buffer: &yuv_buf,
2897 offset: 0,
2898 size: None,
2899 }),
2900 },
2901 ],
2902 });
2903
2904 let bytes = (w as u64) * (h as u64)
2905 + (uv_w as u64) * (uv_h as u64) * 2
2906 + std::mem::size_of::<YuvTransformRaw>() as u64;
2907 self.image_bytes_total += bytes;
2908
2909 self.images.insert(
2910 handle,
2911 ImageTex::Nv12 {
2912 tex_y,
2913 tex_uv,
2914 bind,
2915 yuv_buf,
2916 w,
2917 h,
2918 color_info,
2919 last_used_frame: self.frame_index,
2920 bytes,
2921 },
2922 );
2923 } else {
2924 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
2926 self.queue
2927 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2928 }
2929 }
2930
2931 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
2932 Some(ImageTex::Nv12 {
2933 tex_y,
2934 tex_uv,
2935 bind,
2936 ..
2937 }) => (tex_y, tex_uv, bind),
2938 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
2939 };
2940
2941 self.queue.write_texture(
2942 wgpu::TexelCopyTextureInfo {
2943 texture: tex_y,
2944 mip_level: 0,
2945 origin: wgpu::Origin3d::ZERO,
2946 aspect: wgpu::TextureAspect::All,
2947 },
2948 &y[..y_expected],
2949 wgpu::TexelCopyBufferLayout {
2950 offset: 0,
2951 bytes_per_row: Some(w),
2952 rows_per_image: Some(h),
2953 },
2954 wgpu::Extent3d {
2955 width: w,
2956 height: h,
2957 depth_or_array_layers: 1,
2958 },
2959 );
2960
2961 self.queue.write_texture(
2962 wgpu::TexelCopyTextureInfo {
2963 texture: tex_uv,
2964 mip_level: 0,
2965 origin: wgpu::Origin3d::ZERO,
2966 aspect: wgpu::TextureAspect::All,
2967 },
2968 &uv[..uv_expected],
2969 wgpu::TexelCopyBufferLayout {
2970 offset: 0,
2971 bytes_per_row: Some(2 * uv_w),
2972 rows_per_image: Some(uv_h),
2973 },
2974 wgpu::Extent3d {
2975 width: uv_w,
2976 height: uv_h,
2977 depth_or_array_layers: 1,
2978 },
2979 );
2980
2981 self.evict_budget_excess();
2982 Ok(())
2983 }
2984
2985 pub fn set_image_planes(
2986 &mut self,
2987 handle: u64,
2988 w: u32,
2989 h: u32,
2990 pixel_format: PixelFormat,
2991 planes: &[&[u8]],
2992 color_info: ColorInfo,
2993 ) -> anyhow::Result<()> {
2994 match pixel_format {
2995 PixelFormat::Nv12 => {
2996 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
2997 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
2998 self.set_image_nv12(handle, w, h, y, uv, color_info)
2999 }
3000 PixelFormat::P010 => {
3001 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
3002 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
3003 self.set_image_p010(handle, w, h, y, uv, color_info)
3004 }
3005 PixelFormat::I420 | PixelFormat::I444 => Err(anyhow::anyhow!(
3006 "I420/I444 not implemented and unlikely -> cheap to convert to NV12 (better for the GPU too)"
3007 )),
3008 PixelFormat::Rgba => {
3009 let rgba = planes
3010 .first()
3011 .ok_or(anyhow::anyhow!("missing RGBA plane"))?;
3012 self.set_image_rgba8(handle, w, h, rgba, false)
3013 }
3014 }
3015 }
3016
3017 fn set_image_p010(
3018 &mut self,
3019 handle: u64,
3020 w: u32,
3021 h: u32,
3022 y: &[u8],
3023 uv: &[u8],
3024 color_info: ColorInfo,
3025 ) -> anyhow::Result<()> {
3026 let uv_w = w.div_ceil(2);
3027 let uv_h = h.div_ceil(2);
3028
3029 let y_expected = (w as usize) * 2;
3030 let uv_expected = (uv_w as usize) * (uv_h as usize) * 4;
3031
3032 if y.len() < y_expected {
3033 return Err(anyhow::anyhow!("P010 Y plane too small"));
3034 }
3035 if uv.len() < uv_expected {
3036 return Err(anyhow::anyhow!("P010 UV plane too small"));
3037 }
3038
3039 let needs_recreate = match self.images.get(&handle) {
3043 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
3044 _ => true,
3045 };
3046
3047 let yuv = color_info.to_yuv_transform();
3048 let yuv_raw = YuvTransformRaw {
3049 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
3050 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
3051 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
3052 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
3053 };
3054
3055 if needs_recreate {
3056 self.remove_image(handle);
3057
3058 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
3059 label: Some("p010 Y"),
3060 size: wgpu::Extent3d {
3061 width: w,
3062 height: h,
3063 depth_or_array_layers: 1,
3064 },
3065 mip_level_count: 1,
3066 sample_count: 1,
3067 dimension: wgpu::TextureDimension::D2,
3068 format: wgpu::TextureFormat::R16Unorm,
3069 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3070 view_formats: &[],
3071 });
3072 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
3073
3074 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
3075 label: Some("p010 UV"),
3076 size: wgpu::Extent3d {
3077 width: uv_w,
3078 height: uv_h,
3079 depth_or_array_layers: 1,
3080 },
3081 mip_level_count: 1,
3082 sample_count: 1,
3083 dimension: wgpu::TextureDimension::D2,
3084 format: wgpu::TextureFormat::Rg16Unorm,
3085 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3086 view_formats: &[],
3087 });
3088 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
3089
3090 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3091 label: Some("p010 yuv transform"),
3092 size: std::mem::size_of::<YuvTransformRaw>() as u64,
3093 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3094 mapped_at_creation: false,
3095 });
3096 self.queue
3097 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3098
3099 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3100 label: Some("p010 bind"),
3101 layout: &self.image_bind_layout_nv12,
3102 entries: &[
3103 wgpu::BindGroupEntry {
3104 binding: 0,
3105 resource: wgpu::BindingResource::TextureView(&view_y),
3106 },
3107 wgpu::BindGroupEntry {
3108 binding: 1,
3109 resource: wgpu::BindingResource::TextureView(&view_uv),
3110 },
3111 wgpu::BindGroupEntry {
3112 binding: 2,
3113 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3114 },
3115 wgpu::BindGroupEntry {
3116 binding: 3,
3117 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3118 buffer: &yuv_buf,
3119 offset: 0,
3120 size: None,
3121 }),
3122 },
3123 ],
3124 });
3125
3126 let bytes = (w as u64) * 2
3127 + (uv_w as u64) * (uv_h as u64) * 4
3128 + std::mem::size_of::<YuvTransformRaw>() as u64;
3129 self.image_bytes_total += bytes;
3130
3131 self.images.insert(
3132 handle,
3133 ImageTex::Nv12 {
3134 tex_y,
3135 tex_uv,
3136 bind,
3137 yuv_buf,
3138 w,
3139 h,
3140 color_info,
3141 last_used_frame: self.frame_index,
3142 bytes,
3143 },
3144 );
3145 } else {
3146 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
3147 self.queue
3148 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3149 }
3150 }
3151
3152 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
3153 Some(ImageTex::Nv12 {
3154 tex_y,
3155 tex_uv,
3156 bind,
3157 ..
3158 }) => (tex_y, tex_uv, bind),
3159 _ => return Err(anyhow::anyhow!("Handle is not P010/NV12")),
3160 };
3161
3162 self.queue.write_texture(
3163 wgpu::TexelCopyTextureInfo {
3164 texture: tex_y,
3165 mip_level: 0,
3166 origin: wgpu::Origin3d::ZERO,
3167 aspect: wgpu::TextureAspect::All,
3168 },
3169 &y[..y_expected],
3170 wgpu::TexelCopyBufferLayout {
3171 offset: 0,
3172 bytes_per_row: Some(w * 2),
3173 rows_per_image: Some(h),
3174 },
3175 wgpu::Extent3d {
3176 width: w,
3177 height: h,
3178 depth_or_array_layers: 1,
3179 },
3180 );
3181 self.queue.write_texture(
3182 wgpu::TexelCopyTextureInfo {
3183 texture: tex_uv,
3184 mip_level: 0,
3185 origin: wgpu::Origin3d::ZERO,
3186 aspect: wgpu::TextureAspect::All,
3187 },
3188 &uv[..uv_expected],
3189 wgpu::TexelCopyBufferLayout {
3190 offset: 0,
3191 bytes_per_row: Some(uv_w * 4),
3192 rows_per_image: Some(uv_h),
3193 },
3194 wgpu::Extent3d {
3195 width: uv_w,
3196 height: uv_h,
3197 depth_or_array_layers: 1,
3198 },
3199 );
3200
3201 self.evict_budget_excess();
3202 Ok(())
3203 }
3204
3205 #[cfg(target_os = "linux")]
3206 pub fn set_image_dmabuf(
3207 &mut self,
3208 handle: u64,
3209 w: u32,
3210 h: u32,
3211 fds: Vec<std::os::unix::io::OwnedFd>,
3212 modifier: u64,
3213 strides: Vec<u32>,
3214 offsets: Vec<u64>,
3215 color_info: ColorInfo,
3216 ) -> anyhow::Result<()> {
3217 log::info!(
3218 "set_image_dmabuf handle={handle} {}x{} fds={} modifier=0x{modifier:x}",
3219 w,
3220 h,
3221 fds.len()
3222 );
3223
3224 self.remove_image(handle);
3225
3226 let yuv = color_info.to_yuv_transform();
3227 let yuv_raw = YuvTransformRaw {
3228 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
3229 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
3230 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
3231 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
3232 };
3233
3234 if fds.len() != 2 {
3235 return Err(anyhow::anyhow!(
3236 "unsupported fd count {} - need exactly 2 for separate Y/UV planes",
3237 fds.len()
3238 ));
3239 }
3240
3241 let uv_w = w.div_ceil(2);
3242 let uv_h = h.div_ceil(2);
3243
3244 let hal_y_desc = wgpu::hal::TextureDescriptor {
3245 label: Some("dmabuf y"),
3246 size: wgpu::Extent3d {
3247 width: w,
3248 height: h,
3249 depth_or_array_layers: 1,
3250 },
3251 mip_level_count: 1,
3252 sample_count: 1,
3253 dimension: wgpu::TextureDimension::D2,
3254 format: wgpu::TextureFormat::R8Unorm,
3255 usage: wgpu::wgt::TextureUses::RESOURCE,
3256 memory_flags: wgpu::hal::MemoryFlags::empty(),
3257 view_formats: vec![],
3258 };
3259 let hal_uv_desc = wgpu::hal::TextureDescriptor {
3260 label: Some("dmabuf uv"),
3261 size: wgpu::Extent3d {
3262 width: uv_w,
3263 height: uv_h,
3264 depth_or_array_layers: 1,
3265 },
3266 mip_level_count: 1,
3267 sample_count: 1,
3268 dimension: wgpu::TextureDimension::D2,
3269 format: wgpu::TextureFormat::Rg8Unorm,
3270 usage: wgpu::wgt::TextureUses::RESOURCE,
3271 memory_flags: wgpu::hal::MemoryFlags::empty(),
3272 view_formats: vec![],
3273 };
3274
3275 let wgpu_y_desc = wgpu::TextureDescriptor {
3276 label: Some("dmabuf y"),
3277 size: wgpu::Extent3d {
3278 width: w,
3279 height: h,
3280 depth_or_array_layers: 1,
3281 },
3282 mip_level_count: 1,
3283 sample_count: 1,
3284 dimension: wgpu::TextureDimension::D2,
3285 format: wgpu::TextureFormat::R8Unorm,
3286 usage: wgpu::TextureUsages::TEXTURE_BINDING,
3287 view_formats: &[],
3288 };
3289 let wgpu_uv_desc = wgpu::TextureDescriptor {
3290 label: Some("dmabuf uv"),
3291 size: wgpu::Extent3d {
3292 width: uv_w,
3293 height: uv_h,
3294 depth_or_array_layers: 1,
3295 },
3296 mip_level_count: 1,
3297 sample_count: 1,
3298 dimension: wgpu::TextureDimension::D2,
3299 format: wgpu::TextureFormat::Rg8Unorm,
3300 usage: wgpu::TextureUsages::TEXTURE_BINDING,
3301 view_formats: &[],
3302 };
3303
3304 let (tex_y, view_y, tex_uv, view_uv) = unsafe {
3305 let hal_guard = self
3306 .device
3307 .as_hal::<wgpu::hal::vulkan::Api>()
3308 .ok_or_else(|| {
3309 log::warn!("as_hal::<vulkan::Api> returned None");
3310 anyhow::anyhow!("Device is not Vulkan")
3311 })?;
3312
3313 let mut fds = fds;
3314 let uv_fd = fds.remove(1);
3315 let y_fd = fds.remove(0);
3316
3317 let yt = hal_guard
3318 .texture_from_dmabuf_fd(y_fd, &hal_y_desc, modifier, strides[0] as u64, offsets[0])
3319 .map_err(|e| anyhow::anyhow!("import Y dmabuf: {e:?}"))?;
3320 log::info!("imported Y dmabuf OK");
3321
3322 let uvt = hal_guard
3323 .texture_from_dmabuf_fd(
3324 uv_fd,
3325 &hal_uv_desc,
3326 modifier,
3327 strides[1] as u64,
3328 offsets[1],
3329 )
3330 .map_err(|e| anyhow::anyhow!("import UV dmabuf: {e:?}"))?;
3331 log::info!("imported UV dmabuf OK");
3332
3333 drop(hal_guard);
3334
3335 let tex_y = self
3336 .device
3337 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
3338 yt,
3339 &wgpu_y_desc,
3340 wgpu::wgt::TextureUses::UNINITIALIZED,
3341 );
3342 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
3343
3344 let tex_uv = self
3345 .device
3346 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
3347 uvt,
3348 &wgpu_uv_desc,
3349 wgpu::wgt::TextureUses::UNINITIALIZED,
3350 );
3351 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
3352
3353 (tex_y, view_y, tex_uv, view_uv)
3354 };
3355
3356 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3357 label: Some("dmabuf yuv transform"),
3358 size: std::mem::size_of::<YuvTransformRaw>() as u64,
3359 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3360 mapped_at_creation: false,
3361 });
3362 self.queue
3363 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3364
3365 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3366 label: Some("dmabuf nv12 bind"),
3367 layout: &self.image_bind_layout_nv12,
3368 entries: &[
3369 wgpu::BindGroupEntry {
3370 binding: 0,
3371 resource: wgpu::BindingResource::TextureView(&view_y),
3372 },
3373 wgpu::BindGroupEntry {
3374 binding: 1,
3375 resource: wgpu::BindingResource::TextureView(&view_uv),
3376 },
3377 wgpu::BindGroupEntry {
3378 binding: 2,
3379 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3380 },
3381 wgpu::BindGroupEntry {
3382 binding: 3,
3383 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3384 buffer: &yuv_buf,
3385 offset: 0,
3386 size: None,
3387 }),
3388 },
3389 ],
3390 });
3391
3392 let bytes = (w as u64) * (h as u64)
3393 + (uv_w as u64) * (uv_h as u64) * 2
3394 + std::mem::size_of::<YuvTransformRaw>() as u64;
3395
3396 self.images.insert(
3397 handle,
3398 ImageTex::Nv12 {
3399 tex_y,
3400 tex_uv,
3401 bind,
3402 yuv_buf,
3403 w,
3404 h,
3405 color_info,
3406 last_used_frame: self.frame_index,
3407 bytes,
3408 },
3409 );
3410
3411 self.evict_budget_excess();
3412 Ok(())
3413 }
3414
3415 pub fn remove_image(&mut self, handle: u64) {
3416 if let Some(img) = self.images.remove(&handle) {
3417 let b = match &img {
3418 ImageTex::Rgba { bytes, .. } => *bytes,
3419 ImageTex::Nv12 { bytes, .. } => *bytes,
3420 ImageTex::User { bytes, .. } => *bytes,
3421 };
3422 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
3423 }
3424 self.retained.remove(&handle);
3425 }
3426
3427 fn evict_image_gpu(&mut self, handle: u64) -> u64 {
3428 let Some(img) = self.images.remove(&handle) else {
3429 return 0;
3430 };
3431 let b = match &img {
3432 ImageTex::Rgba { bytes, .. } => *bytes,
3433 ImageTex::Nv12 { bytes, .. } => *bytes,
3434 ImageTex::User { bytes, .. } => *bytes,
3435 };
3436 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
3437 b
3438 }
3439
3440 fn revive_retained_image(&mut self, handle: u64) -> bool {
3441 if self.images.contains_key(&handle) {
3442 return true;
3443 }
3444 let Some(r) = self.retained.get(&handle).cloned() else {
3445 return false;
3446 };
3447 let (tex, bind) = self.create_rgba_tex(r.w, r.h, r.format);
3448
3449 self.queue.write_texture(
3450 wgpu::TexelCopyTextureInfo {
3451 texture: &tex,
3452 mip_level: 0,
3453 origin: wgpu::Origin3d::ZERO,
3454 aspect: wgpu::TextureAspect::All,
3455 },
3456 &r.rgba,
3457 wgpu::TexelCopyBufferLayout {
3458 offset: 0,
3459 bytes_per_row: Some(4 * r.w),
3460 rows_per_image: Some(r.h),
3461 },
3462 wgpu::Extent3d {
3463 width: r.w,
3464 height: r.h,
3465 depth_or_array_layers: 1,
3466 },
3467 );
3468
3469 let bytes = (r.w as u64) * (r.h as u64) * 4;
3470 self.image_bytes_total += bytes;
3471 self.images.insert(
3472 handle,
3473 ImageTex::Rgba {
3474 tex,
3475 bind,
3476 w: r.w,
3477 h: r.h,
3478 format: r.format,
3479 last_used_frame: self.frame_index,
3480 bytes,
3481 },
3482 );
3483 true
3484 }
3485
3486 fn resolve_image_for_draw(&mut self, handle: u64) -> Option<(u32, u32, bool)> {
3487 if let Some(t) = self.images.get_mut(&handle) {
3488 return match t {
3489 ImageTex::Rgba {
3490 w,
3491 h,
3492 last_used_frame,
3493 ..
3494 } => {
3495 *last_used_frame = self.frame_index;
3496 Some((*w, *h, false))
3497 }
3498 ImageTex::User {
3499 w,
3500 h,
3501 last_used_frame,
3502 ..
3503 } => {
3504 *last_used_frame = self.frame_index;
3505 Some((*w, *h, false))
3506 }
3507 ImageTex::Nv12 {
3508 w,
3509 h,
3510 last_used_frame,
3511 ..
3512 } => {
3513 *last_used_frame = self.frame_index;
3514 Some((*w, *h, true))
3515 }
3516 };
3517 }
3518 if self.revive_retained_image(handle)
3519 && let Some(ImageTex::Rgba {
3520 w,
3521 h,
3522 last_used_frame,
3523 ..
3524 }) = self.images.get_mut(&handle)
3525 {
3526 *last_used_frame = self.frame_index;
3527 return Some((*w, *h, false));
3528 }
3529 None
3530 }
3531
3532 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
3534 let handle = self.next_image_handle;
3535 self.next_image_handle += 1;
3536 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
3537 log::error!("Failed to register image: {e}");
3538 }
3539 handle
3540 }
3541
3542 pub fn register_image_rgba8(&mut self, w: u32, h: u32, rgba: &[u8], srgb: bool) -> u64 {
3547 let handle = self.next_image_handle;
3548 self.next_image_handle += 1;
3549 if let Err(e) = self.set_image_rgba8(handle, w, h, rgba, srgb) {
3550 log::error!("Failed to register image: {e}");
3551 }
3552 handle
3553 }
3554
3555 pub fn register_coverage_a8(&mut self, w: u32, h: u32, coverage: &[u8]) -> u64 {
3561 let expected = (w as usize) * (h as usize);
3562 if coverage.len() < expected || w == 0 || h == 0 {
3563 log::error!("Coverage buffer too small: {} < {expected}", coverage.len());
3564 return 0;
3565 }
3566 let handle = self.next_coverage_handle;
3567 self.next_coverage_handle += 1;
3568
3569 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3570 label: Some("coverage tile a8"),
3571 size: wgpu::Extent3d {
3572 width: w,
3573 height: h,
3574 depth_or_array_layers: 1,
3575 },
3576 mip_level_count: 1,
3577 sample_count: 1,
3578 dimension: wgpu::TextureDimension::D2,
3579 format: wgpu::TextureFormat::R8Unorm,
3580 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3581 view_formats: &[],
3582 });
3583 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3584 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3585 label: Some("coverage bind a8"),
3586 layout: &self.image_bind_layout_rgba,
3587 entries: &[
3588 wgpu::BindGroupEntry {
3589 binding: 0,
3590 resource: wgpu::BindingResource::TextureView(&view),
3591 },
3592 wgpu::BindGroupEntry {
3593 binding: 1,
3594 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3595 },
3596 ],
3597 });
3598 self.queue.write_texture(
3599 wgpu::TexelCopyTextureInfo {
3600 texture: &tex,
3601 mip_level: 0,
3602 origin: wgpu::Origin3d::ZERO,
3603 aspect: wgpu::TextureAspect::All,
3604 },
3605 &coverage[..expected],
3606 wgpu::TexelCopyBufferLayout {
3607 offset: 0,
3608 bytes_per_row: Some(w),
3609 rows_per_image: Some(h),
3610 },
3611 wgpu::Extent3d {
3612 width: w,
3613 height: h,
3614 depth_or_array_layers: 1,
3615 },
3616 );
3617 let bytes = (w as u64) * (h as u64);
3618 self.image_bytes_total += bytes;
3619 self.coverages.insert(
3620 handle,
3621 CoverageTex {
3622 tex,
3623 bind,
3624 w,
3625 h,
3626 last_used_frame: self.frame_index,
3627 bytes,
3628 },
3629 );
3630 self.evict_budget_excess();
3631 handle
3632 }
3633
3634 pub fn remove_coverage(&mut self, handle: u64) {
3636 if let Some(tile) = self.coverages.remove(&handle) {
3637 self.image_bytes_total = self.image_bytes_total.saturating_sub(tile.bytes);
3638 }
3639 }
3640
3641 pub fn coverage_dimensions(&mut self, handle: u64) -> Option<(u32, u32)> {
3644 if let Some(tile) = self.coverages.get_mut(&handle) {
3645 tile.last_used_frame = self.frame_index;
3646 return Some((tile.w, tile.h));
3647 }
3648 None
3649 }
3650
3651 fn evict_unused_images(&mut self) {
3652 let now = self.frame_index;
3653 let evict_after = self.image_evict_after_frames;
3654
3655 let mut to_evict = Vec::new();
3658 for (h, t) in self.images.iter() {
3659 let last = match t {
3660 ImageTex::Rgba {
3661 last_used_frame, ..
3662 } => *last_used_frame,
3663 ImageTex::User {
3664 last_used_frame, ..
3665 } => *last_used_frame,
3666 ImageTex::Nv12 {
3667 last_used_frame, ..
3668 } => *last_used_frame,
3669 };
3670 if now.saturating_sub(last) > evict_after {
3671 to_evict.push(*h);
3672 }
3673 }
3674 for h in to_evict {
3675 if self.retained.contains_key(&h) {
3676 self.evict_image_gpu(h);
3677 } else {
3678 self.remove_image(h);
3679 }
3680 }
3681
3682 let mut stale = Vec::new();
3684 for (h, t) in self.coverages.iter() {
3685 if now.saturating_sub(t.last_used_frame) > evict_after {
3686 stale.push(*h);
3687 }
3688 }
3689 for h in stale {
3690 self.remove_coverage(h);
3691 }
3692
3693 self.evict_budget_excess();
3694 }
3695
3696 fn evict_budget_excess(&mut self) {
3697 if self.image_bytes_total <= self.image_budget_bytes {
3698 return;
3699 }
3700 let mut candidates: Vec<(u64, u64, u64)> = self
3702 .images
3703 .iter()
3704 .map(|(h, t)| {
3705 let (last, bytes) = match t {
3706 ImageTex::Rgba {
3707 last_used_frame,
3708 bytes,
3709 ..
3710 } => (*last_used_frame, *bytes),
3711 ImageTex::User {
3712 last_used_frame,
3713 bytes,
3714 ..
3715 } => (*last_used_frame, *bytes),
3716 ImageTex::Nv12 {
3717 last_used_frame,
3718 bytes,
3719 ..
3720 } => (*last_used_frame, *bytes),
3721 };
3722 (*h, last, bytes)
3723 })
3724 .collect();
3725
3726 candidates.sort_by_key(|k| k.1);
3728
3729 let now = self.frame_index;
3730 for (h, last, _bytes) in candidates {
3731 if self.image_bytes_total <= self.image_budget_bytes {
3732 break;
3733 }
3734 if last == now {
3736 continue;
3737 }
3738 if self.retained.contains_key(&h) {
3739 self.evict_image_gpu(h);
3740 } else {
3741 self.remove_image(h);
3742 }
3743 }
3744 }
3745
3746 pub fn set_pixels_per_point(&mut self, ppp: f32) {
3748 self.pixels_per_point = ppp.clamp(0.5, 8.0);
3749 }
3750
3751 pub fn set_working_space(&mut self, enabled: bool) {
3755 if enabled == self.working_space {
3756 return;
3757 }
3758 self.working_space = enabled;
3759 if enabled {
3760 self.ensure_display_pipeline();
3761 self.recreate_working_space_texture();
3762 } else {
3763 self.ws_tex = None;
3764 self.ws_view = None;
3765 self.ws_bind = None;
3766 }
3767 }
3768
3769 fn ensure_display_pipeline(&mut self) {
3770 if self.display_pipeline.is_some() {
3771 return;
3772 }
3773
3774 let layout = self
3775 .device
3776 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3777 label: Some("display transform layout"),
3778 entries: &[
3779 wgpu::BindGroupLayoutEntry {
3780 binding: 0,
3781 visibility: wgpu::ShaderStages::FRAGMENT,
3782 ty: wgpu::BindingType::Texture {
3783 multisampled: false,
3784 view_dimension: wgpu::TextureViewDimension::D2,
3785 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3786 },
3787 count: None,
3788 },
3789 wgpu::BindGroupLayoutEntry {
3790 binding: 1,
3791 visibility: wgpu::ShaderStages::FRAGMENT,
3792 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3793 count: None,
3794 },
3795 ],
3796 });
3797 self.display_layout = Some(layout);
3798
3799 let shader = self
3800 .device
3801 .create_shader_module(wgpu::ShaderModuleDescriptor {
3802 label: Some("display_transform.wgsl"),
3803 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
3804 "shaders/display_transform.wgsl"
3805 ))),
3806 });
3807
3808 let pipeline_layout = self
3809 .device
3810 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3811 label: Some("display transform pipeline layout"),
3812 bind_group_layouts: &[None, self.display_layout.as_ref()],
3813 immediate_size: 0,
3814 });
3815
3816 let pipeline = self
3817 .device
3818 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3819 label: Some("display transform pipeline"),
3820 layout: Some(&pipeline_layout),
3821 vertex: wgpu::VertexState {
3822 module: &shader,
3823 entry_point: Some("vs_main"),
3824 buffers: &[],
3825 compilation_options: wgpu::PipelineCompilationOptions::default(),
3826 },
3827 fragment: Some(wgpu::FragmentState {
3828 module: &shader,
3829 entry_point: Some("fs_main"),
3830 targets: &[Some(wgpu::ColorTargetState {
3831 format: self.output_format,
3832 blend: None,
3833 write_mask: wgpu::ColorWrites::ALL,
3834 })],
3835 compilation_options: wgpu::PipelineCompilationOptions::default(),
3836 }),
3837 primitive: wgpu::PrimitiveState::default(),
3838 depth_stencil: None,
3839 multisample: wgpu::MultisampleState::default(),
3840 multiview_mask: None,
3841 cache: None,
3842 });
3843 self.display_pipeline = Some(pipeline);
3844 }
3845
3846 pub fn resize(&mut self, width: u32, height: u32) {
3851 self.output_width = width;
3852 self.output_height = height;
3853 self.recreate_msaa_and_depth_stencil();
3854 self.recreate_working_space_texture();
3855 }
3856
3857 fn recreate_working_space_texture(&mut self) {
3858 if !self.working_space {
3859 return;
3860 }
3861 let w = self.output_width.max(1);
3862 let h = self.output_height.max(1);
3863
3864 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3865 label: Some("working space"),
3866 size: wgpu::Extent3d {
3867 width: w,
3868 height: h,
3869 depth_or_array_layers: 1,
3870 },
3871 mip_level_count: 1,
3872 sample_count: 1,
3873 dimension: wgpu::TextureDimension::D2,
3874 format: wgpu::TextureFormat::Rgba16Float,
3875 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
3876 view_formats: &[],
3877 });
3878 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3879
3880 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3881 label: Some("working space bind"),
3882 layout: self.display_layout.as_ref().unwrap(),
3883 entries: &[
3884 wgpu::BindGroupEntry {
3885 binding: 0,
3886 resource: wgpu::BindingResource::TextureView(&view),
3887 },
3888 wgpu::BindGroupEntry {
3889 binding: 1,
3890 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3891 },
3892 ],
3893 });
3894
3895 self.ws_tex = Some(tex);
3896 self.ws_view = Some(view);
3897 self.ws_bind = Some(bind);
3898 }
3899
3900 fn recreate_msaa_and_depth_stencil(&mut self) {
3901 if self.msaa_samples > 1 {
3902 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3903 label: Some("msaa color"),
3904 size: wgpu::Extent3d {
3905 width: self.output_width.max(1),
3906 height: self.output_height.max(1),
3907 depth_or_array_layers: 1,
3908 },
3909 mip_level_count: 1,
3910 sample_count: self.msaa_samples,
3911 dimension: wgpu::TextureDimension::D2,
3912 format: self.output_format,
3913 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3914 view_formats: &[],
3915 });
3916 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3917 self.msaa_tex = Some(tex);
3918 self.msaa_view = Some(view);
3919 } else {
3920 self.msaa_tex = None;
3921 self.msaa_view = None;
3922 }
3923
3924 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
3925 label: Some("depth-stencil (stencil clips)"),
3926 size: wgpu::Extent3d {
3927 width: self.output_width.max(1),
3928 height: self.output_height.max(1),
3929 depth_or_array_layers: 1,
3930 },
3931 mip_level_count: 1,
3932 sample_count: self.msaa_samples,
3933 dimension: wgpu::TextureDimension::D2,
3934 format: wgpu::TextureFormat::Depth24PlusStencil8,
3935 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3936 view_formats: &[],
3937 });
3938 self.depth_stencil_view = self
3939 .depth_stencil_tex
3940 .create_view(&wgpu::TextureViewDescriptor::default());
3941 }
3942
3943 fn get_or_create_layer(
3944 &mut self,
3945 layer_id: u32,
3946 width: u32,
3947 height: u32,
3948 rect: repose_core::Rect,
3949 ) {
3950 let needs_alloc = match self.layer_pool.get(&layer_id) {
3951 Some(lt) => lt.width != width || lt.height != height,
3952 None => true,
3953 };
3954 if !needs_alloc {
3955 if let Some(lt) = self.layer_pool.get_mut(&layer_id) {
3956 lt.rect_px = (rect.x, rect.y, rect.w, rect.h);
3957 }
3958 return;
3959 }
3960 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3961 label: Some("graphics layer"),
3962 size: wgpu::Extent3d {
3963 width: width.max(1),
3964 height: height.max(1),
3965 depth_or_array_layers: 1,
3966 },
3967 mip_level_count: 1,
3968 sample_count: 1,
3969 dimension: wgpu::TextureDimension::D2,
3970 format: self.output_format,
3971 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
3972 view_formats: &[],
3973 });
3974 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3975 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3976 label: Some("layer bind"),
3977 layout: &self.image_bind_layout_rgba,
3978 entries: &[
3979 wgpu::BindGroupEntry {
3980 binding: 0,
3981 resource: wgpu::BindingResource::TextureView(&view),
3982 },
3983 wgpu::BindGroupEntry {
3984 binding: 1,
3985 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
3986 },
3987 ],
3988 });
3989 let bind_linear = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3990 label: Some("layer bind linear"),
3991 layout: &self.image_bind_layout_rgba,
3992 entries: &[
3993 wgpu::BindGroupEntry {
3994 binding: 0,
3995 resource: wgpu::BindingResource::TextureView(&view),
3996 },
3997 wgpu::BindGroupEntry {
3998 binding: 1,
3999 resource: wgpu::BindingResource::Sampler(&self.layer_sampler_linear),
4000 },
4001 ],
4002 });
4003 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
4004 label: Some("graphics layer depth-stencil"),
4005 size: wgpu::Extent3d {
4006 width: width.max(1),
4007 height: height.max(1),
4008 depth_or_array_layers: 1,
4009 },
4010 mip_level_count: 1,
4011 sample_count: 1,
4012 dimension: wgpu::TextureDimension::D2,
4013 format: wgpu::TextureFormat::Depth24PlusStencil8,
4014 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
4015 view_formats: &[],
4016 });
4017 let depth_stencil_view =
4018 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
4019 self.layer_pool.insert(
4020 layer_id,
4021 LayerTarget {
4022 view,
4023 bind,
4024 bind_linear,
4025 depth_stencil_view,
4026 width,
4027 height,
4028 rect_px: (rect.x, rect.y, rect.w, rect.h),
4029 },
4030 );
4031 }
4032
4033 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
4034 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4035 label: Some("atlas bind"),
4036 layout: &self.text_bind_layout,
4037 entries: &[
4038 wgpu::BindGroupEntry {
4039 binding: 0,
4040 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
4041 },
4042 wgpu::BindGroupEntry {
4043 binding: 1,
4044 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
4045 },
4046 ],
4047 })
4048 }
4049
4050 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
4051 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4052 label: Some("atlas bind color"),
4053 layout: &self.text_bind_layout,
4054 entries: &[
4055 wgpu::BindGroupEntry {
4056 binding: 0,
4057 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
4058 },
4059 wgpu::BindGroupEntry {
4060 binding: 1,
4061 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
4062 },
4063 ],
4064 })
4065 }
4066
4067 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
4068 let keyp = (key, px.to_bits());
4069 if let Some(info) = self.atlas_mask.map.get(&keyp) {
4070 return Some(*info);
4071 }
4072
4073 let gb = repose_text::rasterize(key, px)?;
4074 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
4075 return None;
4076 }
4077
4078 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
4079
4080 let w = gb.w.max(1);
4081 let h = gb.h.max(1);
4082
4083 if !self.alloc_space_mask(w, h) {
4084 self.grow_mask_and_rebuild();
4085 }
4086 if !self.alloc_space_mask(w, h) {
4087 return None;
4088 }
4089 let x = self.atlas_mask.next_x;
4090 let y = self.atlas_mask.next_y;
4091 self.atlas_mask.next_x += w + 1;
4092 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
4093
4094 let layout = wgpu::TexelCopyBufferLayout {
4095 offset: 0,
4096 bytes_per_row: Some(w),
4097 rows_per_image: Some(h),
4098 };
4099 let size = wgpu::Extent3d {
4100 width: w,
4101 height: h,
4102 depth_or_array_layers: 1,
4103 };
4104 self.queue.write_texture(
4105 wgpu::TexelCopyTextureInfoBase {
4106 texture: &self.atlas_mask.tex,
4107 mip_level: 0,
4108 origin: wgpu::Origin3d { x, y, z: 0 },
4109 aspect: wgpu::TextureAspect::All,
4110 },
4111 &coverage,
4112 layout,
4113 size,
4114 );
4115
4116 let info = GlyphInfo {
4117 u0: x as f32 / self.atlas_mask.size as f32,
4118 v0: y as f32 / self.atlas_mask.size as f32,
4119 u1: (x + w) as f32 / self.atlas_mask.size as f32,
4120 v1: (y + h) as f32 / self.atlas_mask.size as f32,
4121 w: w as f32,
4122 h: h as f32,
4123 };
4124 self.atlas_mask.map.insert(keyp, info);
4125 Some(info)
4126 }
4127
4128 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
4129 let keyp = (key, px.to_bits());
4130 if let Some(info) = self.atlas_color.map.get(&keyp) {
4131 return Some(*info);
4132 }
4133 let gb = repose_text::rasterize(key, px)?;
4134 if !matches!(gb.content, repose_text::SwashContent::Color) {
4135 return None;
4136 }
4137 let w = gb.w.max(1);
4138 let h = gb.h.max(1);
4139 if !self.alloc_space_color(w, h) {
4140 self.grow_color_and_rebuild();
4141 }
4142 if !self.alloc_space_color(w, h) {
4143 return None;
4144 }
4145 let x = self.atlas_color.next_x;
4146 let y = self.atlas_color.next_y;
4147 self.atlas_color.next_x += w + 1;
4148 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
4149
4150 let layout = wgpu::TexelCopyBufferLayout {
4151 offset: 0,
4152 bytes_per_row: Some(w * 4),
4153 rows_per_image: Some(h),
4154 };
4155 let size = wgpu::Extent3d {
4156 width: w,
4157 height: h,
4158 depth_or_array_layers: 1,
4159 };
4160 self.queue.write_texture(
4161 wgpu::TexelCopyTextureInfoBase {
4162 texture: &self.atlas_color.tex,
4163 mip_level: 0,
4164 origin: wgpu::Origin3d { x, y, z: 0 },
4165 aspect: wgpu::TextureAspect::All,
4166 },
4167 &gb.data,
4168 layout,
4169 size,
4170 );
4171 let info = GlyphInfo {
4172 u0: x as f32 / self.atlas_color.size as f32,
4173 v0: y as f32 / self.atlas_color.size as f32,
4174 u1: (x + w) as f32 / self.atlas_color.size as f32,
4175 v1: (y + h) as f32 / self.atlas_color.size as f32,
4176 w: w as f32,
4177 h: h as f32,
4178 };
4179 self.atlas_color.map.insert(keyp, info);
4180 Some(info)
4181 }
4182
4183 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
4184 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
4185 self.atlas_mask.next_x = 1;
4186 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
4187 self.atlas_mask.row_h = 0;
4188 }
4189 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
4190 return false;
4191 }
4192 true
4193 }
4194
4195 fn grow_mask_and_rebuild(&mut self) {
4196 let new_size = (self.atlas_mask.size * 2).min(4096);
4197 if new_size == self.atlas_mask.size {
4198 return;
4199 }
4200 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
4201 label: Some("glyph atlas A8 (grown)"),
4202 size: wgpu::Extent3d {
4203 width: new_size,
4204 height: new_size,
4205 depth_or_array_layers: 1,
4206 },
4207 mip_level_count: 1,
4208 sample_count: 1,
4209 dimension: wgpu::TextureDimension::D2,
4210 format: wgpu::TextureFormat::R8Unorm,
4211 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4212 view_formats: &[],
4213 });
4214 self.atlas_mask.tex = tex;
4215 self.atlas_mask.view = self
4216 .atlas_mask
4217 .tex
4218 .create_view(&wgpu::TextureViewDescriptor::default());
4219 self.atlas_mask.size = new_size;
4220 self.atlas_mask.next_x = 1;
4221 self.atlas_mask.next_y = 1;
4222 self.atlas_mask.row_h = 0;
4223 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
4224 self.atlas_mask.map.clear();
4225 for (k, px_bits) in keys {
4226 let _ = self.upload_glyph_mask(k, f32::from_bits(px_bits));
4227 }
4228 }
4229
4230 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
4231 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
4232 self.atlas_color.next_x = 1;
4233 self.atlas_color.next_y += self.atlas_color.row_h + 1;
4234 self.atlas_color.row_h = 0;
4235 }
4236 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
4237 return false;
4238 }
4239 true
4240 }
4241
4242 fn grow_color_and_rebuild(&mut self) {
4243 let new_size = (self.atlas_color.size * 2).min(4096);
4244 if new_size == self.atlas_color.size {
4245 return;
4246 }
4247 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
4248 label: Some("glyph atlas RGBA (grown)"),
4249 size: wgpu::Extent3d {
4250 width: new_size,
4251 height: new_size,
4252 depth_or_array_layers: 1,
4253 },
4254 mip_level_count: 1,
4255 sample_count: 1,
4256 dimension: wgpu::TextureDimension::D2,
4257 format: wgpu::TextureFormat::Rgba8UnormSrgb,
4258 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4259 view_formats: &[],
4260 });
4261 self.atlas_color.tex = tex;
4262 self.atlas_color.view = self
4263 .atlas_color
4264 .tex
4265 .create_view(&wgpu::TextureViewDescriptor::default());
4266 self.atlas_color.size = new_size;
4267 self.atlas_color.next_x = 1;
4268 self.atlas_color.next_y = 1;
4269 self.atlas_color.row_h = 0;
4270 let keys: Vec<(repose_text::GlyphKey, u32)> =
4271 self.atlas_color.map.keys().copied().collect();
4272 self.atlas_color.map.clear();
4273 for (k, px_bits) in keys {
4274 let _ = self.upload_glyph_color(k, f32::from_bits(px_bits));
4275 }
4276 }
4277}
4278
4279fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
4280 match brush {
4281 Brush::Solid(c) => (
4282 0u32,
4283 c.to_linear(),
4284 [0.0, 0.0, 0.0, 0.0],
4285 [0.0, 0.0],
4286 [0.0, 1.0],
4287 ),
4288 Brush::Linear {
4289 start,
4290 end,
4291 start_color,
4292 end_color,
4293 } => (
4294 1u32,
4295 start_color.to_linear(),
4296 end_color.to_linear(),
4297 [start.x, start.y],
4298 [end.x, end.y],
4299 ),
4300 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
4301 }
4302}
4303
4304fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
4305 match brush {
4306 Brush::Solid(c) => c.to_linear(),
4307 Brush::Linear { start_color, .. } => start_color.to_linear(),
4308 _ => [0.0; 4],
4309 }
4310}
4311
4312fn init_atlas_mask(device: &wgpu::Device) -> AtlasA8 {
4313 let size = 1024u32;
4314 let tex = device.create_texture(&wgpu::TextureDescriptor {
4315 label: Some("glyph atlas A8"),
4316 size: wgpu::Extent3d {
4317 width: size,
4318 height: size,
4319 depth_or_array_layers: 1,
4320 },
4321 mip_level_count: 1,
4322 sample_count: 1,
4323 dimension: wgpu::TextureDimension::D2,
4324 format: wgpu::TextureFormat::R8Unorm,
4325 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4326 view_formats: &[],
4327 });
4328 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4329 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4330 label: Some("glyph atlas sampler A8"),
4331 address_mode_u: wgpu::AddressMode::ClampToEdge,
4332 address_mode_v: wgpu::AddressMode::ClampToEdge,
4333 address_mode_w: wgpu::AddressMode::ClampToEdge,
4334 mag_filter: wgpu::FilterMode::Linear,
4335 min_filter: wgpu::FilterMode::Linear,
4336 mipmap_filter: wgpu::MipmapFilterMode::Linear,
4337 ..Default::default()
4338 });
4339
4340 AtlasA8 {
4341 tex,
4342 view,
4343 sampler,
4344 size,
4345 next_x: 1,
4346 next_y: 1,
4347 row_h: 0,
4348 map: HashMap::new(),
4349 }
4350}
4351
4352fn init_atlas_color(device: &wgpu::Device) -> AtlasRGBA {
4353 let size = 1024u32;
4354 let tex = device.create_texture(&wgpu::TextureDescriptor {
4355 label: Some("glyph atlas RGBA"),
4356 size: wgpu::Extent3d {
4357 width: size,
4358 height: size,
4359 depth_or_array_layers: 1,
4360 },
4361 mip_level_count: 1,
4362 sample_count: 1,
4363 dimension: wgpu::TextureDimension::D2,
4364 format: wgpu::TextureFormat::Rgba8UnormSrgb,
4365 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4366 view_formats: &[],
4367 });
4368 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4369 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4370 label: Some("glyph atlas sampler RGBA"),
4371 address_mode_u: wgpu::AddressMode::ClampToEdge,
4372 address_mode_v: wgpu::AddressMode::ClampToEdge,
4373 address_mode_w: wgpu::AddressMode::ClampToEdge,
4374 mag_filter: wgpu::FilterMode::Linear,
4375 min_filter: wgpu::FilterMode::Linear,
4376 mipmap_filter: wgpu::MipmapFilterMode::Linear,
4377 ..Default::default()
4378 });
4379 AtlasRGBA {
4380 tex,
4381 view,
4382 sampler,
4383 size,
4384 next_x: 1,
4385 next_y: 1,
4386 row_h: 0,
4387 map: HashMap::new(),
4388 }
4389}
4390
4391#[cfg(feature = "winit-surface")]
4392impl RenderBackend for WgpuSurfaceBackend {
4393 fn configure_surface(&mut self, width: u32, height: u32) {
4394 if width == 0 || height == 0 {
4395 return;
4396 }
4397 self.renderer.output_width = width;
4398 self.renderer.output_height = height;
4399 if let Some(ref mut config) = self.surface_config {
4400 config.width = width;
4401 config.height = height;
4402 }
4403 if let (Some(surface), Some(config)) = (self.surface.as_ref(), self.surface_config.as_ref())
4404 {
4405 surface.configure(&self.renderer.device, config);
4406 }
4407 self.renderer.recreate_msaa_and_depth_stencil();
4408 self.renderer.recreate_working_space_texture();
4409 }
4410
4411 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
4412 let surface = self.surface.as_ref().expect("WgpuSurfaceBackend::frame() requires a surface (use from_device + render_to_view instead)");
4413 let surface_config = self
4414 .surface_config
4415 .as_ref()
4416 .expect("surface_config required for frame()");
4417
4418 self.renderer.frame_index = self.renderer.frame_index.wrapping_add(1);
4419 self.renderer.slug_cache.next_frame();
4420
4421 if self.renderer.output_width == 0 || self.renderer.output_height == 0 {
4422 return;
4423 }
4424
4425 let mut retries = 0u32;
4426 const MAX_RETRIES: u32 = 4;
4427 let frame = loop {
4428 match surface.get_current_texture() {
4429 wgpu::CurrentSurfaceTexture::Success(f) => break f,
4430 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
4431 log::warn!("suboptimal surface; reconfiguring");
4432 surface.configure(&self.renderer.device, surface_config);
4433 break f;
4434 }
4435 wgpu::CurrentSurfaceTexture::Outdated => {
4436 retries += 1;
4437 if retries >= MAX_RETRIES {
4438 log::warn!(
4439 "surface outdated persisted after {MAX_RETRIES} retries; skipping frame"
4440 );
4441 return;
4442 }
4443 log::warn!("surface outdated; reconfiguring");
4444 surface.configure(&self.renderer.device, surface_config);
4445 }
4446 wgpu::CurrentSurfaceTexture::Lost => {
4447 retries += 1;
4448 if retries >= MAX_RETRIES {
4449 log::warn!(
4450 "surface lost persisted after {MAX_RETRIES} retries; skipping frame"
4451 );
4452 return;
4453 }
4454 log::warn!("surface lost; reconfiguring");
4455 surface.configure(&self.renderer.device, surface_config);
4456 }
4457 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
4458 request_frame();
4459 return;
4460 }
4461 wgpu::CurrentSurfaceTexture::Validation => {
4462 retries += 1;
4463 if retries >= MAX_RETRIES {
4464 log::warn!(
4465 "surface validation persisted after {MAX_RETRIES} retries; skipping frame"
4466 );
4467 return;
4468 }
4469 surface.configure(&self.renderer.device, surface_config);
4470 }
4471 }
4472 };
4473
4474 let swap_view = if let Some(view_format) = self
4475 .surface_config
4476 .as_ref()
4477 .and_then(|c| c.view_formats.iter().find(|f| f.is_srgb()).copied())
4478 {
4479 frame.texture.create_view(&wgpu::TextureViewDescriptor {
4480 format: Some(view_format),
4481 ..Default::default()
4482 })
4483 } else {
4484 frame
4485 .texture
4486 .create_view(&wgpu::TextureViewDescriptor::default())
4487 };
4488 let mut encoder =
4489 self.renderer
4490 .device
4491 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4492 label: Some("frame encoder"),
4493 });
4494
4495 let clear_color = Some([
4496 scene.clear_color.0 as f64 / 255.0,
4497 scene.clear_color.1 as f64 / 255.0,
4498 scene.clear_color.2 as f64 / 255.0,
4499 scene.clear_color.3 as f64 / 255.0,
4500 ]);
4501
4502 self.renderer
4503 .render_scene_to_encoder(scene, &mut encoder, &swap_view, clear_color);
4504
4505 {
4508 let _reset = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
4509 label: Some("webgl color_mask reset before present"),
4510 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
4511 view: &swap_view,
4512 resolve_target: None,
4513 ops: wgpu::Operations {
4514 load: wgpu::LoadOp::Load,
4515 store: wgpu::StoreOp::Store,
4516 },
4517 depth_slice: None,
4518 })],
4519 depth_stencil_attachment: None,
4520 timestamp_writes: None,
4521 occlusion_query_set: None,
4522 multiview_mask: None,
4523 });
4524 }
4525
4526 self.renderer
4527 .queue
4528 .submit(std::iter::once(encoder.finish()));
4529 if let Err(e) = catch_unwind(AssertUnwindSafe(|| self.renderer.queue.present(frame))) {
4530 log::warn!("queue.present panicked: {:?}", e);
4531 }
4532 }
4533}
4534
4535impl WgpuSceneRenderer {
4536 #[allow(clippy::too_many_arguments)]
4548 fn push_perspective_layer(
4549 &mut self,
4550 node: Transform,
4551 top: Transform,
4552 transform_stack: &mut Vec<Transform>,
4553 scissor_stack: &mut Vec<repose_core::Rect>,
4554 root_clip_rect: &mut repose_core::Rect,
4555 current_target_size: &mut (f32, f32),
4556 current_pass: &mut Pass,
4557 passes: &mut Vec<Pass>,
4558 target_stack: &mut Vec<PassTarget>,
4559 flatten_stack: &mut Vec<FlattenRecord>,
4560 id_head: &mut u32,
4561 ids_used: &mut Vec<u32>,
4562 ) {
4563 let map =
4567 Transform::compose_projective(&top.projective_matrix(), &node.projective_matrix());
4568 let scr = scissor_stack.last().copied().unwrap_or(*root_clip_rect);
4569 let w = scr.w.ceil().max(1.0);
4570 let h = scr.h.ceil().max(1.0);
4571 let layer_rect = repose_core::Rect {
4572 x: scr.x,
4573 y: scr.y,
4574 w,
4575 h,
4576 };
4577 let layer_id = *id_head;
4580 *id_head = id_head.wrapping_add(1);
4581 ids_used.push(layer_id);
4582
4583 let stack_len = transform_stack.len();
4584 transform_stack.push(top);
4588 transform_stack.push(Transform::translate(-layer_rect.x, -layer_rect.y));
4589
4590 let saved_scissor = std::mem::replace(
4591 scissor_stack,
4592 vec![repose_core::Rect {
4593 x: 0.0,
4594 y: 0.0,
4595 w,
4596 h,
4597 }],
4598 );
4599 let saved_root = std::mem::replace(
4600 root_clip_rect,
4601 repose_core::Rect {
4602 x: 0.0,
4603 y: 0.0,
4604 w,
4605 h,
4606 },
4607 );
4608 let saved_size = std::mem::replace(current_target_size, (w, h));
4609 let prev_target = current_pass.target;
4610 let saved = std::mem::replace(
4611 current_pass,
4612 Pass {
4613 target: PassTarget::Layer(layer_id),
4614 initial_scissor: (0, 0, w as u32, h as u32),
4615 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
4616 cmds: Vec::new(),
4617 },
4618 );
4619 passes.push(saved);
4620 target_stack.push(prev_target);
4621 self.get_or_create_layer(layer_id, w as u32, h as u32, layer_rect);
4622 *current_target_size = (w, h);
4623 flatten_stack.push(FlattenRecord {
4624 stack_len,
4625 layer_id,
4626 map,
4627 layer_rect,
4628 saved_scissor,
4629 saved_root,
4630 saved_size,
4631 });
4632 }
4633
4634 #[allow(clippy::too_many_arguments)]
4637 fn pop_perspective_layer(
4638 &mut self,
4639 rec: FlattenRecord,
4640 scissor_stack: &mut Vec<repose_core::Rect>,
4641 root_clip_rect: &mut repose_core::Rect,
4642 current_target_size: &mut (f32, f32),
4643 current_pass: &mut Pass,
4644 passes: &mut Vec<Pass>,
4645 target_stack: &mut Vec<PassTarget>,
4646 ) {
4647 *scissor_stack = rec.saved_scissor;
4648 *root_clip_rect = rec.saved_root;
4649 *current_target_size = rec.saved_size;
4650 let saved = std::mem::replace(
4651 current_pass,
4652 Pass {
4653 target: target_stack.pop().unwrap_or(PassTarget::Surface),
4654 initial_scissor: (0, 0, self.output_width, self.output_height),
4655 clear_color: None,
4656 cmds: Vec::new(),
4657 },
4658 );
4659 passes.push(saved);
4660
4661 let (tw, th) = rec.saved_size;
4665 let r = rec.layer_rect;
4666 let corners = [
4667 (r.x, r.y),
4668 (r.x + r.w, r.y),
4669 (r.x + r.w, r.y + r.h),
4670 (r.x, r.y + r.h),
4671 ];
4672 let mut ndc = [[0.0f32; 2]; 4];
4673 let mut ws = [1.0f32; 4];
4674 let mut all_behind = true;
4675 for (i, (x, y)) in corners.iter().enumerate() {
4676 let w_raw = rec.map[6] * x + rec.map[7] * y + rec.map[8];
4677 let w = if w_raw.abs() < 1e-6 {
4678 if w_raw < 0.0 { -1e-6 } else { 1e-6 }
4679 } else {
4680 w_raw
4681 };
4682 if w > 0.0 {
4683 all_behind = false;
4684 }
4685 let px = (rec.map[0] * x + rec.map[1] * y + rec.map[2]) / w;
4686 let py = (rec.map[3] * x + rec.map[4] * y + rec.map[5]) / w;
4687 ndc[i] = [px / tw * 2.0 - 1.0, 1.0 - py / th * 2.0];
4688 ws[i] = w;
4689 }
4690 if all_behind {
4691 return;
4694 }
4695 let layer = self.layer_pool.get(&rec.layer_id).expect("flatten layer");
4696 let uv_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
4697 let uv_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
4698 let inst = ProjectiveInstance {
4699 c0: ndc[0],
4700 c1: ndc[1],
4701 c2: ndc[2],
4702 c3: ndc[3],
4703 uv: [0.0, 0.0, uv_u1, uv_v1],
4704 w: ws,
4705 alpha: 1.0,
4706 _pad: [0.0; 3],
4707 };
4708 self.projective_ring.grow_to_fit(
4709 &self.device,
4710 std::mem::size_of::<ProjectiveInstance>() as u64,
4711 );
4712 let bytes = bytemuck::bytes_of(&inst);
4713 let (off, _) = self.projective_ring.alloc_write(&self.queue, bytes);
4714 current_pass.cmds.push(Cmd::CompositeProjective {
4715 off,
4716 cnt: 1,
4717 layer_id: rec.layer_id,
4718 });
4719 }
4720
4721 fn upload_mesh_geometry(&mut self, mesh: &repose_core::VectorMeshData) -> (u64, u32, u64, u32) {
4722 let verts: Vec<MeshVertex> = mesh
4723 .vertices
4724 .iter()
4725 .map(|v| MeshVertex {
4726 pos: v.pos,
4727 color: v.color,
4728 uv: v.uv,
4729 })
4730 .collect();
4731 let vbytes = bytemuck::cast_slice(&verts);
4732 self.mesh_verts
4733 .grow_to_fit(&self.device, vbytes.len() as u64);
4734 let (voff, _) = self.mesh_verts.alloc_write(&self.queue, vbytes);
4735 let ibytes = bytemuck::cast_slice(&mesh.indices);
4736 self.mesh_indices
4737 .grow_to_fit(&self.device, ibytes.len() as u64);
4738 let (ioff, _) = self.mesh_indices.alloc_write(&self.queue, ibytes);
4739 (voff, verts.len() as u32, ioff, mesh.indices.len() as u32)
4740 }
4741
4742 fn alloc_mesh_uniform(&mut self, u: MeshUniform) -> u64 {
4743 if self.mesh_uniform_head + MESH_UNIFORM_SLOT > MESH_UNIFORM_CAP {
4744 log::warn!("mesh uniform buffer overflow; regenerating");
4745 self.recreate_mesh_uniform_buffer();
4746 }
4747 let slot = self.mesh_uniform_head;
4748 self.queue
4749 .write_buffer(&self.mesh_uniform_buf, slot, bytemuck::bytes_of(&u));
4750 self.mesh_uniform_head = slot + MESH_UNIFORM_SLOT;
4751 slot
4752 }
4753
4754 fn recreate_mesh_uniform_buffer(&mut self) {
4755 let new_cap = self.mesh_uniform_head + MESH_UNIFORM_SLOT;
4756 self.mesh_uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4757 label: Some("mesh uniform buffer"),
4758 size: new_cap,
4759 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4760 mapped_at_creation: false,
4761 });
4762 self.mesh_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4763 label: Some("mesh uniform bind"),
4764 layout: &self.mesh_bind_layout,
4765 entries: &[wgpu::BindGroupEntry {
4766 binding: 0,
4767 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
4768 buffer: &self.mesh_uniform_buf,
4769 offset: 0,
4770 size: NonZero::new(MESH_UNIFORM_SLOT),
4771 }),
4772 }],
4773 });
4774 self.mesh_uniform_head = 0;
4775 }
4776
4777 #[allow(clippy::too_many_arguments)]
4778 fn emit_vector_mesh(
4779 &mut self,
4780 current_transform: &Transform,
4781 mesh: &repose_core::VectorMeshData,
4782 transform: [f32; 6],
4783 paint: &repose_core::PaintDesc,
4784 cmds: &mut Vec<Cmd>,
4785 ) {
4786 let affine = combine_mesh_affine(current_transform, transform);
4787 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
4788 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(affine, paint));
4789 cmds.push(Cmd::VectorMesh {
4790 voff,
4791 vcnt,
4792 ioff,
4793 icnt,
4794 uoff,
4795 });
4796 }
4797
4798 pub fn render_scene_to_encoder(
4799 &mut self,
4800 scene: &Scene,
4801 encoder: &mut wgpu::CommandEncoder,
4802 target_view: &wgpu::TextureView,
4803 clear_color_override: Option<[f64; 4]>,
4804 ) {
4805 fn affine_aabb(transform: &Transform, rect: &repose_core::Rect) -> repose_core::Rect {
4808 let m = transform.linear();
4809 let (tx, ty) = (transform.translate_x, transform.translate_y);
4810 let corners = [
4811 (rect.x, rect.y),
4812 (rect.x + rect.w, rect.y),
4813 (rect.x, rect.y + rect.h),
4814 (rect.x + rect.w, rect.y + rect.h),
4815 ];
4816 let mut min_x = f32::MAX;
4817 let mut min_y = f32::MAX;
4818 let mut max_x = f32::MIN;
4819 let mut max_y = f32::MIN;
4820 for (x, y) in corners {
4821 let wx = m[0] * x + m[1] * y + tx;
4822 let wy = m[2] * x + m[3] * y + ty;
4823 min_x = min_x.min(wx);
4824 min_y = min_y.min(wy);
4825 max_x = max_x.max(wx);
4826 max_y = max_y.max(wy);
4827 }
4828 repose_core::Rect {
4829 x: min_x,
4830 y: min_y,
4831 w: (max_x - min_x).max(0.0),
4832 h: (max_y - min_y).max(0.0),
4833 }
4834 }
4835
4836 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
4837 let x0 = (x / fb_w) * 2.0 - 1.0;
4838 let y0 = 1.0 - (y / fb_h) * 2.0;
4839 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
4840 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
4841 let min_x = x0.min(x1);
4842 let min_y = y0.min(y1);
4843 let w_ndc = (x1 - x0).abs();
4844 let h_ndc = (y1 - y0).abs();
4845 [min_x, min_y, w_ndc, h_ndc]
4846 }
4847
4848 fn rect_to_instance_ndc(
4853 rect: repose_core::Rect,
4854 transform: &Transform,
4855 fb_w: f32,
4856 fb_h: f32,
4857 ) -> ([f32; 4], [f32; 4]) {
4858 let cx = rect.x + rect.w * 0.5;
4859 let cy = rect.y + rect.h * 0.5;
4860
4861 let m = transform.linear();
4862 let tx = m[0] * cx + m[1] * cy + transform.translate_x;
4863 let ty = m[2] * cx + m[3] * cy + transform.translate_y;
4864
4865 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
4866 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
4867 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
4869 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
4870
4871 ([ndc_cx, ndc_cy, ndc_w, ndc_h], forward_rs_mat(transform))
4872 }
4873
4874 fn forward_rs_mat(transform: &Transform) -> [f32; 4] {
4878 let c = transform.rotate.cos();
4879 let s = transform.rotate.sin();
4880 let (hx, hy) = (transform.shear_x, transform.shear_y);
4881 let m = [c - s * hy, c * hx - s, s + c * hy, s * hx + c];
4882 if (m[0] * m[3] - m[1] * m[2]).abs() < 1e-6 {
4883 return [1.0, 0.0, 0.0, 1.0];
4884 }
4885 m
4886 }
4887
4888 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
4889 let mut x = r.x.floor() as i64;
4890 let mut y = r.y.floor() as i64;
4891 let fb_wi = fb_w as i64;
4892 let fb_hi = fb_h as i64;
4893 x = x.clamp(0, fb_wi.saturating_sub(1));
4894 y = y.clamp(0, fb_hi.saturating_sub(1));
4895 let w_req = r.w.ceil().max(1.0) as i64;
4896 let h_req = r.h.ceil().max(1.0) as i64;
4897 let w = (w_req).min(fb_wi - x).max(1);
4898 let h = (h_req).min(fb_hi - y).max(1);
4899 (x as u32, y as u32, w as u32, h as u32)
4900 }
4901
4902 let fb_w = self.output_width as f32;
4903 let fb_h = self.output_height as f32;
4904
4905 let mut passes: Vec<Pass> = Vec::with_capacity(1);
4906 let clear_color = clear_color_override.unwrap_or_else(|| {
4907 let lin = scene.clear_color.to_linear();
4911 [lin[0] as f64, lin[1] as f64, lin[2] as f64, lin[3] as f64]
4912 });
4913 let mut current_pass: Pass = Pass {
4914 target: PassTarget::Surface,
4915 initial_scissor: (0, 0, self.output_width, self.output_height),
4916 clear_color: Some([
4917 clear_color[0] as f32,
4918 clear_color[1] as f32,
4919 clear_color[2] as f32,
4920 clear_color[3] as f32,
4921 ]),
4922 cmds: Vec::with_capacity(scene.nodes.len()),
4923 };
4924 let mut target_stack: Vec<PassTarget> = Vec::new();
4925 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
4926 let mut layer_blurs: Vec<(u32, f32, f32)> = Vec::new();
4927 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
4928
4929 struct Batch {
4930 rects: Vec<RectInstance>,
4931 borders: Vec<BorderInstance>,
4932 ellipses: Vec<EllipseInstance>,
4933 e_borders: Vec<EllipseBorderInstance>,
4934 arcs: Vec<ArcInstance>,
4935 masks: Vec<GlyphInstance>,
4936 colors: Vec<GlyphInstance>,
4937 nv12s: Vec<Nv12Instance>,
4938 }
4939
4940 impl Batch {
4941 fn new() -> Self {
4942 Self {
4943 rects: vec![],
4944 borders: vec![],
4945 ellipses: vec![],
4946 e_borders: vec![],
4947 arcs: vec![],
4948 masks: vec![],
4949 colors: vec![],
4950 nv12s: vec![],
4951 }
4952 }
4953
4954 fn is_empty(&self) -> bool {
4955 self.rects.is_empty()
4956 && self.borders.is_empty()
4957 && self.ellipses.is_empty()
4958 && self.e_borders.is_empty()
4959 && self.arcs.is_empty()
4960 && self.masks.is_empty()
4961 && self.colors.is_empty()
4962 && self.nv12s.is_empty()
4963 }
4964
4965 fn flush(
4966 &mut self,
4967 pipes: (
4968 &mut InstancedPipe<RectInstance>,
4969 &mut InstancedPipe<BorderInstance>,
4970 &mut InstancedPipe<EllipseInstance>,
4971 &mut InstancedPipe<EllipseBorderInstance>,
4972 &mut InstancedPipe<ArcInstance>,
4973 ),
4974 glyph_pipes: (
4975 &mut InstancedPipe<GlyphInstance>,
4976 &mut InstancedPipe<GlyphInstance>,
4977 ),
4978 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
4979 device: &wgpu::Device,
4980 queue: &wgpu::Queue,
4981 cmds: &mut Vec<Cmd>,
4982 ) {
4983 let (rects, borders, ellipses, e_borders, arcs) = pipes;
4984 let (masks, colors) = glyph_pipes;
4985
4986 macro_rules! flush_one {
4987 ($buf:ident, $pipe:expr, $variant:ident) => {
4988 if !self.$buf.is_empty() {
4989 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
4990 cmds.push(Cmd::$variant { off, cnt });
4991 }
4992 self.$buf.clear();
4993 }
4994 };
4995 }
4996
4997 flush_one!(rects, rects, Rect);
4998 flush_one!(borders, borders, Border);
4999 flush_one!(ellipses, ellipses, Ellipse);
5000 flush_one!(e_borders, e_borders, EllipseBorder);
5001 flush_one!(arcs, arcs, Arc);
5002 flush_one!(masks, masks, GlyphsMask);
5003 flush_one!(colors, colors, GlyphsColor);
5004
5005 if !self.nv12s.is_empty() {
5006 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
5007 let _ = (off, cnt);
5008 }
5009 self.nv12s.clear();
5010 }
5011 }
5012 }
5013
5014 self.rects.reset();
5015 self.borders.reset();
5016 self.ellipses.reset();
5017 self.ellipse_borders.reset();
5018 self.arcs.reset();
5019 self.glyph_mask.reset();
5020 self.glyph_color.reset();
5021 self.clip_ring.reset();
5022 self.blur_ring.reset();
5023 self.nv12.reset();
5024
5025 self.slug_ring.reset();
5026 self.mesh_verts.reset();
5027 self.mesh_indices.reset();
5028 self.mesh_uniform_head = 0;
5029 self.mesh_clip_stack.clear();
5030 self.projective_ring.reset();
5031 for id in self.flatten_layer_ids.drain(..) {
5035 self.layer_pool.remove(&id);
5036 }
5037 let mut batch = Batch::new();
5038 let mut slug_verts_local: Vec<slug::TessVertex> = Vec::new();
5039 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
5040 let mut flatten_stack: Vec<FlattenRecord> = Vec::new();
5041 let mut flatten_id_head: u32 = FLATTEN_ID_BASE;
5042 let mut flatten_ids_used: Vec<u32> = Vec::new();
5043 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
5044 let mut clip_cmd_stack: Vec<(u64, u32, bool)> = Vec::with_capacity(8);
5048 let mut root_clip_rect = repose_core::Rect {
5049 x: 0.0,
5050 y: 0.0,
5051 w: fb_w,
5052 h: fb_h,
5053 };
5054 let mut saved_scissor_stack: Vec<repose_core::Rect> = Vec::new();
5055 let mut saved_root_clip_rect = root_clip_rect;
5056
5057 let mut current_prim: Option<&'static str> = None;
5058
5059 macro_rules! flush_if_prim_changed {
5060 ($prim:literal, $pipe:expr) => {
5061 if current_prim != Some($prim) {
5062 flush_batch!();
5063 current_prim = Some($prim);
5064 }
5065 };
5066 }
5067
5068 macro_rules! flush_batch {
5069 () => {
5070 if !batch.is_empty() {
5071 batch.flush(
5072 (
5073 &mut self.rects,
5074 &mut self.borders,
5075 &mut self.ellipses,
5076 &mut self.ellipse_borders,
5077 &mut self.arcs,
5078 ),
5079 (&mut self.glyph_mask, &mut self.glyph_color),
5080 &mut self.nv12,
5081 &self.device,
5082 &self.queue,
5083 &mut current_pass.cmds,
5084 )
5085 }
5086 };
5087 }
5088 for node in &scene.nodes {
5089 let t_identity = Transform::identity();
5090 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5091
5092 match node {
5093 SceneNode::Rect {
5094 rect,
5095 brush,
5096 radius,
5097 } => {
5098 flush_if_prim_changed!("rect", &self.rects);
5099 let (ndc, fwd_mat) = rect_to_instance_ndc(
5100 *rect,
5101 current_transform,
5102 current_target_size.0,
5103 current_target_size.1,
5104 );
5105 let (brush_type, color0, color1, grad_start, grad_end) =
5106 brush_to_instance_fields(brush);
5107 batch.rects.push(RectInstance {
5108 xywh: ndc,
5109 radii: radius.map(|r| r.0),
5110 brush_type,
5111 _pad: [0.0; 3],
5112 color0,
5113 color1,
5114 grad_start,
5115 grad_end,
5116 fwd_mat,
5117 });
5118 }
5119 SceneNode::Border {
5120 rect,
5121 color,
5122 width,
5123 radius,
5124 } => {
5125 flush_if_prim_changed!("border", &self.borders);
5126 let (ndc, fwd_mat) = rect_to_instance_ndc(
5127 *rect,
5128 current_transform,
5129 current_target_size.0,
5130 current_target_size.1,
5131 );
5132 batch.borders.push(BorderInstance {
5133 xywh: ndc,
5134 radii: radius.map(|r| r.0),
5135 stroke: width.0,
5136 color: color.to_linear(),
5137 fwd_mat,
5138 });
5139 }
5140 SceneNode::Ellipse { rect, brush } => {
5141 flush_if_prim_changed!("ellipse", &self.ellipses);
5142 let (ndc, fwd_mat) = rect_to_instance_ndc(
5143 *rect,
5144 current_transform,
5145 current_target_size.0,
5146 current_target_size.1,
5147 );
5148 let color = brush_to_solid_color(brush);
5149 batch.ellipses.push(EllipseInstance {
5150 xywh: ndc,
5151 color,
5152 fwd_mat,
5153 });
5154 }
5155 SceneNode::EllipseBorder { rect, color, width } => {
5156 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
5157 let (ndc, fwd_mat) = rect_to_instance_ndc(
5158 *rect,
5159 current_transform,
5160 current_target_size.0,
5161 current_target_size.1,
5162 );
5163 let pad_px = width.0 * 0.5 + 2.0;
5164 let pad = (pad_px / current_target_size.0) * 2.0;
5165 batch.e_borders.push(EllipseBorderInstance {
5166 xywh: ndc,
5167 stroke: width.0,
5168 pad,
5169 color: color.to_linear(),
5170 fwd_mat,
5171 });
5172 }
5173 SceneNode::Arc {
5174 rect,
5175 start_angle,
5176 sweep_angle,
5177 stroke_width,
5178 color,
5179 cap,
5180 } => {
5181 flush_if_prim_changed!("arc", &self.arcs);
5182 let (ndc, fwd_mat) = rect_to_instance_ndc(
5183 *rect,
5184 current_transform,
5185 current_target_size.0,
5186 current_target_size.1,
5187 );
5188 let pad_px = stroke_width.0 * 0.5 + 2.0;
5189 let pad = (pad_px / current_target_size.0) * 2.0;
5190 let cap_val = match cap {
5191 StrokeCap::Butt => 0.0,
5192 StrokeCap::Round => 1.0,
5193 StrokeCap::Square => 2.0,
5194 };
5195 batch.arcs.push(ArcInstance {
5196 xywh: ndc,
5197 start_angle: *start_angle,
5198 sweep_angle: *sweep_angle,
5199 stroke: stroke_width.0,
5200 pad,
5201 color: color.to_linear(),
5202 fwd_mat,
5203 cap: cap_val,
5204 });
5205 }
5206 SceneNode::Text {
5207 rect,
5208 text,
5209 color,
5210 size,
5211 font_family,
5212 text_align: _,
5213 font_weight,
5214 font_style,
5215 text_decoration,
5216 letter_spacing,
5217 line_height: _,
5218 extra_style,
5219 url: _,
5220 font_variation_settings,
5221 } => {
5222 flush_batch!(); let px = size.0;
5225 let lh_ratio = rect.h / px;
5226 let fw = font_weight.0;
5227 let fs = if *font_style == FontStyle::Italic {
5228 1
5229 } else {
5230 0
5231 };
5232 let shaped = repose_text::shape_line(
5233 text.as_ref(),
5234 px,
5235 lh_ratio,
5236 *font_family,
5237 fw,
5238 fs,
5239 letter_spacing.0,
5240 font_variation_settings.as_deref(),
5241 );
5242 let baseline_y = shaped.first().map(|g| rect.y + g.y);
5243
5244 let fwd = forward_rs_mat(current_transform);
5245 let has_linear = fwd != [1.0, 0.0, 0.0, 1.0];
5246
5247 let lin = current_transform.linear();
5248 let tr_x = current_transform.translate_x;
5249 let tr_y = current_transform.translate_y;
5250
5251 let make_glyph_instance =
5252 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 4]) {
5253 if has_linear {
5254 let gc_x = gx + gw * 0.5;
5255 let gc_y = gy + gh * 0.5;
5256 let wc_x = lin[0] * gc_x + lin[1] * gc_y + tr_x;
5257 let wc_y = lin[2] * gc_x + lin[3] * gc_y + tr_y;
5258 let ww = gw * current_transform.scale_x;
5259 let wh = gh * current_transform.scale_y;
5260 let ex = fwd[0].abs() * ww * 0.5 + fwd[1].abs() * wh * 0.5;
5261 let ey = fwd[2].abs() * ww * 0.5 + fwd[3].abs() * wh * 0.5;
5262 let ndc_tl = to_ndc(
5263 wc_x - ex,
5264 wc_y - ey,
5265 ex * 2.0,
5266 ey * 2.0,
5267 current_target_size.0,
5268 current_target_size.1,
5269 );
5270 let ndc = [
5271 ndc_tl[0] + ndc_tl[2] * 0.5,
5272 ndc_tl[1] + ndc_tl[3] * 0.5,
5273 ndc_tl[2],
5274 ndc_tl[3],
5275 ];
5276 (ndc, fwd)
5277 } else {
5278 let (sx, sy) = if current_transform.scale_x == 1.0
5279 && current_transform.scale_y == 1.0
5280 {
5281 (gx.round(), gy.round())
5282 } else {
5283 (gx, gy)
5284 };
5285 rect_to_instance_ndc(
5286 repose_core::Rect {
5287 x: sx,
5288 y: sy,
5289 w: gw,
5290 h: gh,
5291 },
5292 current_transform,
5293 current_target_size.0,
5294 current_target_size.1,
5295 )
5296 }
5297 };
5298
5299 let baseline_shift_y: f32 = px * extra_style.baseline_shift.0;
5300
5301 let (
5302 is_stroke,
5303 stroke_width,
5304 stroke_cap,
5305 stroke_join,
5306 stroke_miter,
5307 stroke_path_effect,
5308 ) = match &extra_style.draw_style {
5309 repose_core::DrawStyle::Stroke {
5310 width,
5311 cap,
5312 join,
5313 miter,
5314 path_effect,
5315 } => (true, *width, *cap, *join, *miter, path_effect.clone()),
5316 _ => (
5317 false,
5318 0.0,
5319 repose_core::StrokeCap::Butt,
5320 repose_core::StrokeJoin::Miter,
5321 4.0,
5322 None,
5323 ),
5324 };
5325 let stroke_tess_key = if is_stroke {
5326 Some(slug::StrokeTessKey::new(
5327 stroke_width,
5328 stroke_cap,
5329 stroke_join,
5330 stroke_miter,
5331 &stroke_path_effect,
5332 ))
5333 } else {
5334 None
5335 };
5336
5337 for sg in shaped {
5338 let gx = rect.x + sg.x + sg.bearing_x;
5339 let gy = rect.y + sg.y - sg.bearing_y + baseline_shift_y;
5340
5341 if self.slug_enabled {
5343 let ck = repose_text::lookup_cache_key(sg.key, sg.px);
5344 if let Some(ref ck) = ck {
5345 let need_tessellate = self.slug_cache.get(ck).is_none_or(|g| {
5347 if is_stroke {
5348 let key = stroke_tess_key.as_ref().unwrap();
5349 !g.stroke_variants.contains_key(key)
5350 } else {
5351 g.fill_vertices.is_none()
5352 }
5353 });
5354 if need_tessellate {
5355 if let Some((ck2, commands)) =
5356 repose_text::lookup_and_extract_outline(sg.key, sg.px)
5357 {
5358 let font_size_px = f32::from_bits(ck2.font_size_bits);
5359 if is_stroke {
5360 self.slug_cache.get_or_insert_stroke(
5361 ck2,
5362 font_size_px,
5363 &commands,
5364 stroke_width,
5365 stroke_cap,
5366 stroke_join,
5367 stroke_miter,
5368 &stroke_path_effect,
5369 );
5370 } else {
5371 self.slug_cache.get_or_insert(
5372 ck2,
5373 font_size_px,
5374 &commands,
5375 );
5376 }
5377 }
5378 } else {
5379 self.slug_cache.touch(ck);
5380 }
5381 }
5382 if let Some(entry) = ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
5383 {
5384 let ox = rect.x + sg.x;
5385 let oy = rect.y + sg.y + baseline_shift_y;
5386 let scx = current_transform.scale_x;
5387 let scy = current_transform.scale_y;
5388 let ttx = current_transform.translate_x;
5389 let tty = current_transform.translate_y;
5390
5391 let tf = |x: f32, y: f32| -> (f32, f32) {
5392 if has_linear {
5393 (
5394 lin[0] * x + lin[1] * y + ttx,
5395 lin[2] * x + lin[3] * y + tty,
5396 )
5397 } else {
5398 (x * scx + ttx, y * scy + tty)
5399 }
5400 };
5401
5402 let tw = current_target_size.0;
5403 let th = current_target_size.1;
5404
5405 let verts = if is_stroke {
5406 let key = stroke_tess_key.as_ref().unwrap();
5407 entry
5408 .stroke_variants
5409 .get(key)
5410 .map(|v| v.as_slice())
5411 .unwrap_or(&[])
5412 } else {
5413 entry.fill_vertices.as_deref().unwrap_or(&[])
5414 };
5415
5416 for &v in verts {
5417 let (sx, sy) = tf(ox + v[0] * px, oy - v[1] * px);
5418 let ndc_x = sx / tw * 2.0 - 1.0;
5419 let ndc_y = -(sy / th) * 2.0 + 1.0;
5420 slug_verts_local.push(slug::TessVertex {
5421 ndc_pos: [ndc_x, ndc_y],
5422 color: color.to_linear(),
5423 });
5424 }
5425
5426 if is_stroke {
5427 continue;
5429 }
5430 continue;
5431 }
5432 }
5433
5434 if is_stroke {
5436 continue;
5437 }
5438
5439 if let Some(info) = self.upload_glyph_color(sg.key, sg.px) {
5440 let (ndc, fwd_mat) = make_glyph_instance(gx, gy, info.w, info.h);
5441 batch.colors.push(GlyphInstance {
5442 xywh: ndc,
5443 uv: [info.u0, info.v1, info.u1, info.v0],
5444 color: color.to_linear(),
5445 fwd_mat,
5446 });
5447 } else if let Some(info) = self.upload_glyph_mask(sg.key, sg.px) {
5448 let (ndc, fwd_mat) = make_glyph_instance(gx, gy, info.w, info.h);
5449 batch.masks.push(GlyphInstance {
5450 xywh: ndc,
5451 uv: [info.u0, info.v1, info.u1, info.v0],
5452 color: color.to_linear(),
5453 fwd_mat,
5454 });
5455 }
5456 }
5457
5458 if !slug_verts_local.is_empty() {
5460 let bytes = bytemuck::cast_slice(&slug_verts_local);
5461 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
5462 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
5463 current_pass.cmds.push(Cmd::GlyphsVector {
5464 off,
5465 cnt: slug_verts_local.len() as u32,
5466 });
5467 slug_verts_local.clear();
5468 }
5469
5470 if (text_decoration.underline || text_decoration.strikethrough)
5472 && let Some(baseline_y) = baseline_y
5473 {
5474 flush_batch!();
5475 current_prim = Some("rect");
5476 let deco_color = text_decoration.color.unwrap_or(*color);
5477 let thickness = (px * 0.07).max(1.0);
5478
5479 if text_decoration.underline {
5480 let dy = baseline_y + px * 0.1;
5481 let (ndc, fwd_mat) = rect_to_instance_ndc(
5482 repose_core::Rect {
5483 x: rect.x,
5484 y: dy,
5485 w: rect.w,
5486 h: thickness,
5487 },
5488 current_transform,
5489 current_target_size.0,
5490 current_target_size.1,
5491 );
5492 batch.rects.push(RectInstance {
5493 xywh: ndc,
5494 radii: [0.0; 4],
5495 brush_type: 0,
5496 _pad: [0.0; 3],
5497 color0: deco_color.to_linear(),
5498 color1: [0.0; 4],
5499 grad_start: [0.0; 2],
5500 grad_end: [0.0; 2],
5501 fwd_mat,
5502 });
5503 }
5504 if text_decoration.strikethrough {
5505 let sy = baseline_y - px * 0.3;
5506 let (ndc, fwd_mat) = rect_to_instance_ndc(
5507 repose_core::Rect {
5508 x: rect.x,
5509 y: sy,
5510 w: rect.w,
5511 h: thickness,
5512 },
5513 current_transform,
5514 current_target_size.0,
5515 current_target_size.1,
5516 );
5517 batch.rects.push(RectInstance {
5518 xywh: ndc,
5519 radii: [0.0; 4],
5520 brush_type: 0,
5521 _pad: [0.0; 3],
5522 color0: deco_color.to_linear(),
5523 color1: [0.0; 4],
5524 grad_start: [0.0; 2],
5525 grad_end: [0.0; 2],
5526 fwd_mat,
5527 });
5528 }
5529 }
5530 }
5531 SceneNode::Image {
5532 rect,
5533 handle,
5534 tint,
5535 fit,
5536 } => {
5537 flush_batch!();
5538
5539 let (img_w, img_h, is_nv12) = match self.resolve_image_for_draw(*handle) {
5542 Some(wh) => wh,
5543 None => {
5544 log::warn!("Image handle {} not found", handle);
5545 continue;
5546 }
5547 };
5548
5549 let src_w = img_w as f32;
5550 let src_h = img_h as f32;
5551
5552 let dst_w = rect.w.max(0.0);
5553 let dst_h = rect.h.max(0.0);
5554 if dst_w <= 0.0 || dst_h <= 0.0 {
5555 continue;
5556 }
5557
5558 let (draw_rect, uv_rect) = match fit {
5559 repose_core::view::ImageFit::Contain => {
5560 let scale = (dst_w / src_w).min(dst_h / src_h);
5561 let w = src_w * scale;
5562 let h = src_h * scale;
5563 (
5564 repose_core::Rect {
5565 x: rect.x + (dst_w - w) * 0.5,
5566 y: rect.y + (dst_h - h) * 0.5,
5567 w,
5568 h,
5569 },
5570 [0.0, 1.0, 1.0, 0.0],
5571 )
5572 }
5573 repose_core::view::ImageFit::Cover => {
5574 let scale = (dst_w / src_w).max(dst_h / src_h);
5575 let content_w = src_w * scale;
5576 let content_h = src_h * scale;
5577 let overflow_x = (content_w - dst_w) * 0.5;
5578 let overflow_y = (content_h - dst_h) * 0.5;
5579 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
5580 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
5581 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
5582 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
5583 (*rect, [u0, 1.0 - v0, u1, 1.0 - v1])
5584 }
5585 repose_core::view::ImageFit::FitWidth => {
5586 let scale = dst_w / src_w;
5587 (
5588 repose_core::Rect {
5589 x: rect.x,
5590 y: rect.y + (dst_h - src_h * scale) * 0.5,
5591 w: dst_w,
5592 h: src_h * scale,
5593 },
5594 [0.0, 1.0, 1.0, 0.0],
5595 )
5596 }
5597 repose_core::view::ImageFit::FitHeight => {
5598 let scale = dst_h / src_h;
5599 (
5600 repose_core::Rect {
5601 x: rect.x + (dst_w - src_w * scale) * 0.5,
5602 y: rect.y,
5603 w: src_w * scale,
5604 h: dst_h,
5605 },
5606 [0.0, 1.0, 1.0, 0.0],
5607 )
5608 }
5609 repose_core::view::ImageFit::FillBounds => (*rect, [0.0, 1.0, 1.0, 0.0]),
5610 repose_core::view::ImageFit::Inside => {
5611 let scale = (dst_w / src_w).min(dst_h / src_h).min(1.0);
5612 let w = src_w * scale;
5613 let h = src_h * scale;
5614 (
5615 repose_core::Rect {
5616 x: rect.x + (dst_w - w) * 0.5,
5617 y: rect.y + (dst_h - h) * 0.5,
5618 w,
5619 h,
5620 },
5621 [0.0, 1.0, 1.0, 0.0],
5622 )
5623 }
5624 repose_core::view::ImageFit::None => {
5625 (
5626 repose_core::Rect {
5627 x: rect.x,
5628 y: rect.y,
5629 w: src_w.min(dst_w),
5630 h: src_h.min(dst_h),
5631 },
5632 [
5634 0.0,
5635 1.0,
5636 (dst_w / src_w).min(1.0),
5637 1.0 - (dst_h / src_h).min(1.0),
5638 ],
5639 )
5640 }
5641 _ => continue,
5642 };
5643
5644 let (ndc_center, fwd_mat) = rect_to_instance_ndc(
5645 draw_rect,
5646 current_transform,
5647 current_target_size.0,
5648 current_target_size.1,
5649 );
5650
5651 if is_nv12 {
5652 let uv_x_offset = if let Some(ImageTex::Nv12 { w, color_info, .. }) =
5653 self.images.get(handle)
5654 {
5655 match color_info.chroma_siting {
5656 ChromaSiting::Center | ChromaSiting::TopLeft => 0.0,
5657 ChromaSiting::Left => -1.0 / *w as f32,
5658 }
5659 } else {
5660 0.0
5661 };
5662
5663 let inst = Nv12Instance {
5664 xywh: ndc_center,
5665 uv: uv_rect,
5666 color: tint.to_linear(),
5667 uv_x_offset,
5668 fwd_mat,
5669 _pad: [0.0],
5670 };
5671 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
5672 {
5673 current_pass.cmds.push(Cmd::ImageNv12 {
5674 off,
5675 cnt: 1,
5676 handle: *handle,
5677 });
5678 }
5679 } else {
5680 let inst = GlyphInstance {
5682 xywh: ndc_center,
5683 uv: uv_rect,
5684 color: tint.to_linear(),
5685 fwd_mat,
5686 };
5687 if let Some((off, _)) =
5688 self.glyph_color.upload(&self.device, &self.queue, &[inst])
5689 {
5690 current_pass.cmds.push(Cmd::ImageRgba {
5691 off,
5692 cnt: 1,
5693 handle: *handle,
5694 });
5695 }
5696 }
5697 }
5698 SceneNode::Coverage {
5699 rect,
5700 handle,
5701 color,
5702 } => {
5703 flush_batch!();
5704 let Some((tile_w, tile_h)) = self.coverage_dimensions(*handle) else {
5707 log::warn!("Coverage handle {handle} not found");
5708 continue;
5709 };
5710 let draw_rect = repose_core::Rect {
5713 x: rect.x,
5714 y: rect.y,
5715 w: tile_w as f32,
5716 h: tile_h as f32,
5717 };
5718 let (ndc_center, fwd_mat) = rect_to_instance_ndc(
5719 draw_rect,
5720 current_transform,
5721 current_target_size.0,
5722 current_target_size.1,
5723 );
5724 let inst = GlyphInstance {
5725 xywh: ndc_center,
5726 uv: [0.0, 1.0, 1.0, 0.0],
5727 color: color.to_linear(),
5728 fwd_mat,
5729 };
5730 if let Some((off, _)) =
5731 self.glyph_color.upload(&self.device, &self.queue, &[inst])
5732 {
5733 current_pass.cmds.push(Cmd::Coverage {
5734 off,
5735 cnt: 1,
5736 handle: *handle,
5737 });
5738 }
5739 }
5740 SceneNode::PushClip { rect, radius, op } => {
5741 flush_batch!(); let is_diff = matches!(op, repose_core::ClipOp::Difference);
5744
5745 let t_identity = Transform::identity();
5746 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5747 let transformed = affine_aabb(current_transform, rect);
5748
5749 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5750 let next_scissor = if is_diff {
5751 top
5752 } else {
5753 intersect(top, transformed)
5754 };
5755 scissor_stack.push(next_scissor);
5756 let scissor = to_scissor(
5757 &next_scissor,
5758 current_target_size.0 as u32,
5759 current_target_size.1 as u32,
5760 );
5761
5762 let clip_ndc_tl = to_ndc(
5763 transformed.x,
5764 transformed.y,
5765 transformed.w,
5766 transformed.h,
5767 current_target_size.0,
5768 current_target_size.1,
5769 );
5770 let inst = ClipInstance {
5771 xywh: [
5772 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
5773 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
5774 clip_ndc_tl[2],
5775 clip_ndc_tl[3],
5776 ],
5777 radii: radius.map(|r| r.0),
5778 fwd_mat: [1.0, 0.0, 0.0, 1.0],
5779 };
5780 let bytes = bytemuck::bytes_of(&inst);
5781 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
5782 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
5783
5784 let rounded = radius.iter().any(|&r| r.0 > 0.5);
5785
5786 current_pass.cmds.push(Cmd::ClipPush {
5787 off,
5788 cnt: 1,
5789 scissor,
5790 difference: is_diff,
5791 rounded,
5792 });
5793 clip_cmd_stack.push((off, 1, is_diff));
5794 }
5795 SceneNode::PopClip => {
5796 flush_batch!();
5797
5798 if !scissor_stack.is_empty() {
5799 scissor_stack.pop();
5800 } else {
5801 log::warn!("PopClip with empty stack");
5802 }
5803
5804 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5805 let scissor = to_scissor(
5806 &top,
5807 current_target_size.0 as u32,
5808 current_target_size.1 as u32,
5809 );
5810 let (off, cnt, difference) = clip_cmd_stack.pop().unwrap_or((0, 0, false));
5811 current_pass.cmds.push(Cmd::ClipPop {
5812 off,
5813 cnt,
5814 scissor,
5815 difference,
5816 });
5817 }
5818 SceneNode::Shadow {
5819 rect,
5820 radius,
5821 elevation: _,
5822 color,
5823 } => {
5824 flush_if_prim_changed!("rect", &self.rects);
5825 let (ndc, fwd_mat) = rect_to_instance_ndc(
5826 *rect,
5827 current_transform,
5828 current_target_size.0,
5829 current_target_size.1,
5830 );
5831 let (brush_type, color0, _color1, _grad_start, _grad_end) =
5832 brush_to_instance_fields(&Brush::Solid(*color));
5833 batch.rects.push(RectInstance {
5834 xywh: ndc,
5835 radii: radius.map(|r| r.0),
5836 brush_type,
5837 _pad: [0.0; 3],
5838 color0,
5839 color1: [0.0; 4],
5840 grad_start: [0.0; 2],
5841 grad_end: [0.0; 2],
5842 fwd_mat,
5843 });
5844 }
5845 SceneNode::PushTransform { transform } => {
5846 flush_batch!(); if transform.has_perspective() {
5848 let top = *transform_stack.last().unwrap_or(&t_identity);
5853 self.push_perspective_layer(
5854 *transform,
5855 top,
5856 &mut transform_stack,
5857 &mut scissor_stack,
5858 &mut root_clip_rect,
5859 &mut current_target_size,
5860 &mut current_pass,
5861 &mut passes,
5862 &mut target_stack,
5863 &mut flatten_stack,
5864 &mut flatten_id_head,
5865 &mut flatten_ids_used,
5866 );
5867 } else {
5868 let combined = current_transform.combine(transform);
5869 transform_stack.push(combined);
5870 }
5871 }
5872 SceneNode::PopTransform => {
5873 flush_batch!(); if let Some(rec) = flatten_stack.last() {
5875 if transform_stack.len() == rec.stack_len + 2 {
5879 let rec = flatten_stack.pop().expect("checked above");
5880 transform_stack.pop();
5881 transform_stack.pop();
5882 self.pop_perspective_layer(
5883 rec,
5884 &mut scissor_stack,
5885 &mut root_clip_rect,
5886 &mut current_target_size,
5887 &mut current_pass,
5888 &mut passes,
5889 &mut target_stack,
5890 );
5891 continue;
5892 }
5893 }
5894 transform_stack.pop();
5895 }
5896 SceneNode::BeginLayer {
5897 rect,
5898 layer_id,
5899 alpha,
5900 blur_radius_x,
5901 blur_radius_y,
5902 rectangle_edge: _,
5903 } => {
5904 flush_batch!();
5905 let w = (rect.w.round().max(1.0)) as u32;
5908 let h = (rect.h.round().max(1.0)) as u32;
5909 saved_scissor_stack =
5910 std::mem::replace(&mut scissor_stack, Vec::with_capacity(8));
5911 saved_root_clip_rect = std::mem::replace(
5912 &mut root_clip_rect,
5913 repose_core::Rect {
5914 x: 0.0,
5915 y: 0.0,
5916 w: w as f32,
5917 h: h as f32,
5918 },
5919 );
5920 scissor_stack.push(root_clip_rect);
5921 let prev_target = current_pass.target;
5923 let prev_scissor = current_pass.initial_scissor;
5924 let saved = std::mem::replace(
5925 &mut current_pass,
5926 Pass {
5927 target: PassTarget::Layer(*layer_id),
5928 initial_scissor: (0, 0, w, h),
5929 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
5930 cmds: Vec::new(),
5931 },
5932 );
5933 passes.push(saved);
5934 target_stack.push(prev_target);
5935 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
5939 current_target_size = (w as f32, h as f32);
5940 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
5941 if blur_radius_x.0 > 0.0 || blur_radius_y.0 > 0.0 {
5943 layer_blurs.push((*layer_id, blur_radius_x.0, blur_radius_y.0));
5944 }
5945 }
5946 SceneNode::EndLayer { layer_id } => {
5947 flush_batch!();
5948 scissor_stack = std::mem::take(&mut saved_scissor_stack);
5949 root_clip_rect = saved_root_clip_rect;
5950 let saved = std::mem::replace(
5952 &mut current_pass,
5953 Pass {
5954 target: target_stack.pop().unwrap_or(PassTarget::Surface),
5955 initial_scissor: (0, 0, self.output_width, self.output_height),
5956 clear_color: None, cmds: Vec::new(),
5958 },
5959 );
5960 passes.push(saved);
5961 current_target_size = (fb_w, fb_h);
5962 if let Some((_, layer_alpha, _)) = layer_alphas
5964 .iter()
5965 .find(|(id, _, _)| id == layer_id)
5966 .copied()
5967 {
5968 let layer = self.layer_pool.get(layer_id).expect("layer target");
5969 let ndc_tl = to_ndc(
5970 layer.rect_px.0,
5971 layer.rect_px.1,
5972 layer.rect_px.2,
5973 layer.rect_px.3,
5974 fb_w,
5975 fb_h,
5976 );
5977 let uv_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
5978 let uv_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
5979 let blur_px_val = layer_blurs
5981 .iter()
5982 .find(|(id, _, _)| id == layer_id)
5983 .map(|(_, bx, by)| (*bx, *by));
5984 if let Some((blur_x, blur_y)) =
5985 blur_px_val.filter(|(bx, by)| *bx > 0.0 || *by > 0.0)
5986 {
5987 let bw_uv = (blur_x * 1.5) / layer.width.max(1) as f32;
5989 let bh_uv = (blur_y * 1.5) / layer.height.max(1) as f32;
5990 let inst = BlurInstance {
5991 xywh: [
5992 ndc_tl[0] + ndc_tl[2] * 0.5,
5993 ndc_tl[1] + ndc_tl[3] * 0.5,
5994 ndc_tl[2],
5995 ndc_tl[3],
5996 ],
5997 uv: [0.0, 0.0, uv_u1, uv_v1],
5998 color: [1.0, 1.0, 1.0, layer_alpha],
5999 blur_uv: [bw_uv, bh_uv],
6000 fwd_mat: [1.0, 0.0, 0.0, 1.0],
6001 };
6002 self.blur_ring.grow_to_fit(
6003 &self.device,
6004 std::mem::size_of::<BlurInstance>() as u64,
6005 );
6006 let bytes = bytemuck::bytes_of(&inst);
6007 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
6008 current_pass.cmds.push(Cmd::CompositeBlur {
6009 off,
6010 cnt: 1,
6011 layer_id: *layer_id,
6012 });
6013 } else {
6014 let inst = GlyphInstance {
6016 xywh: [
6017 ndc_tl[0] + ndc_tl[2] * 0.5,
6018 ndc_tl[1] + ndc_tl[3] * 0.5,
6019 ndc_tl[2],
6020 ndc_tl[3],
6021 ],
6022 uv: [0.0, uv_v1, uv_u1, 0.0],
6023 color: [1.0, 1.0, 1.0, layer_alpha],
6024 fwd_mat: [1.0, 0.0, 0.0, 1.0],
6025 };
6026 if let Some((off, cnt)) =
6027 self.glyph_color.upload(&self.device, &self.queue, &[inst])
6028 {
6029 current_pass.cmds.push(Cmd::CompositeLayer {
6030 off,
6031 cnt,
6032 layer_id: *layer_id,
6033 });
6034 }
6035 }
6036 }
6037 }
6038 SceneNode::CompositeShadow {
6039 layer_id,
6040 blur_px,
6041 offset_px,
6042 color,
6043 } => {
6044 flush_batch!();
6045 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
6046 let sx = layer.rect_px.0 + offset_px.0.0;
6048 let sy = layer.rect_px.1 + offset_px.1.0;
6049 let sw = layer.rect_px.2;
6050 let sh = layer.rect_px.3;
6051 let bw_uv = (blur_px.0 * 1.5) / layer.width.max(1) as f32;
6054 let bh_uv = (blur_px.0 * 1.5) / layer.height.max(1) as f32;
6055 let shadow_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
6056 let shadow_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
6057 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
6058 let inst = BlurInstance {
6059 xywh: [
6060 ndc_tl[0] + ndc_tl[2] * 0.5,
6061 ndc_tl[1] + ndc_tl[3] * 0.5,
6062 ndc_tl[2],
6063 ndc_tl[3],
6064 ],
6065 uv: [0.0, 0.0, shadow_u1, shadow_v1],
6066 color: [
6067 color.0 as f32 / 255.0,
6068 color.1 as f32 / 255.0,
6069 color.2 as f32 / 255.0,
6070 color.3 as f32 / 255.0,
6071 ],
6072 blur_uv: [bw_uv, bh_uv],
6073 fwd_mat: [1.0, 0.0, 0.0, 1.0],
6074 };
6075 self.blur_ring
6076 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
6077 let bytes = bytemuck::bytes_of(&inst);
6078 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
6079 current_pass.cmds.push(Cmd::CompositeShadow {
6080 off,
6081 cnt: 1,
6082 layer_id: *layer_id,
6083 });
6084 }
6085 }
6086 SceneNode::VectorMesh {
6087 mesh,
6088 transform,
6089 paint,
6090 clip: _,
6091 blend: _,
6092 } => {
6093 flush_batch!();
6094 let t_identity = Transform::identity();
6095 let current_transform = transform_stack.last().unwrap_or(&t_identity);
6096 self.emit_vector_mesh(
6097 current_transform,
6098 mesh,
6099 *transform,
6100 paint,
6101 &mut current_pass.cmds,
6102 );
6103 }
6104 SceneNode::VectorOverlay { meshes } => {
6105 flush_batch!();
6106 for m in meshes.iter() {
6107 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(m);
6108 let uoff = self.alloc_mesh_uniform(MeshUniform::identity());
6109 current_pass.cmds.push(Cmd::VectorOverlay {
6110 voff,
6111 vcnt,
6112 ioff,
6113 icnt,
6114 uoff,
6115 });
6116 }
6117 }
6118 SceneNode::PushVectorClip { mesh, op } => {
6119 flush_batch!();
6120 let difference = matches!(op, repose_core::ClipOp::Difference);
6121 let t_identity = Transform::identity();
6122 let current_transform = transform_stack.last().unwrap_or(&t_identity);
6123 let affine =
6124 combine_mesh_affine(current_transform, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
6125 let aabb = mesh_aabb(mesh, affine);
6126 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
6127 let next = if difference {
6131 top
6132 } else {
6133 intersect(top, aabb)
6134 };
6135 scissor_stack.push(next);
6136 let scissor = to_scissor(
6137 &next,
6138 current_target_size.0 as u32,
6139 current_target_size.1 as u32,
6140 );
6141 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
6142 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(
6143 affine,
6144 &repose_core::PaintDesc::Solid,
6145 ));
6146 current_pass.cmds.push(Cmd::VectorClipPush {
6147 voff,
6148 vcnt,
6149 ioff,
6150 icnt,
6151 uoff,
6152 scissor,
6153 difference,
6154 });
6155 self.mesh_clip_stack
6156 .push((voff, vcnt, ioff, icnt, uoff, difference));
6157 }
6158 SceneNode::PopVectorClip => {
6159 flush_batch!();
6160 if !scissor_stack.is_empty() {
6161 scissor_stack.pop();
6162 } else {
6163 log::warn!("PopVectorClip with empty scissor stack");
6164 }
6165 if let Some((voff, vcnt, ioff, icnt, uoff, difference)) =
6166 self.mesh_clip_stack.pop()
6167 {
6168 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
6169 let scissor = to_scissor(
6170 &top,
6171 current_target_size.0 as u32,
6172 current_target_size.1 as u32,
6173 );
6174 current_pass.cmds.push(Cmd::VectorClipPop {
6175 voff,
6176 vcnt,
6177 ioff,
6178 icnt,
6179 uoff,
6180 scissor,
6181 difference,
6182 });
6183 } else {
6184 log::warn!("PopVectorClip with empty clip stack");
6185 }
6186 }
6187 SceneNode::Callback { rect, payload } => {
6188 flush_batch!();
6189 let t = transform_stack
6190 .last()
6191 .copied()
6192 .unwrap_or(Transform::identity());
6193 let transformed = affine_aabb(&t, rect);
6194 current_pass.cmds.push(Cmd::Callback {
6195 rect: transformed,
6196 payload: payload.clone(),
6197 });
6198 }
6199 _ => {}
6200 }
6201 }
6202
6203 flush_batch!();
6204
6205 {
6206 let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
6207 let mut prepare_list: Vec<Arc<Callback>> = Vec::new();
6208 for node in &scene.nodes {
6209 if let SceneNode::Callback { payload, .. } = node
6210 && payload.downcast_ref::<Callback>().is_some()
6211 {
6212 let ptr = Arc::as_ptr(payload) as *const () as usize;
6213 if seen.insert(ptr)
6214 && let Ok(cb_arc) = payload.clone().downcast::<Callback>()
6215 {
6216 prepare_list.push(cb_arc);
6217 }
6218 }
6219 }
6220 if !prepare_list.is_empty() {
6221 let screen_desc = ScreenDescriptor {
6222 size_in_pixels: [self.output_width, self.output_height],
6223 pixels_per_point: self.pixels_per_point,
6224 target_format: self.output_format,
6225 sample_count: self.msaa_samples.max(1),
6226 };
6227 let mut user_cmd_bufs: Vec<wgpu::CommandBuffer> = Vec::new();
6228 for cb in &prepare_list {
6229 user_cmd_bufs.extend(cb.0.prepare(
6230 &self.device,
6231 &self.queue,
6232 encoder,
6233 &screen_desc,
6234 &mut self.callback_resources,
6235 ));
6236 }
6237 for cb in &prepare_list {
6238 user_cmd_bufs.extend(cb.0.finish_prepare(
6239 &self.device,
6240 &self.queue,
6241 encoder,
6242 &screen_desc,
6243 &mut self.callback_resources,
6244 ));
6245 }
6246 if !user_cmd_bufs.is_empty() {
6249 self.queue.submit(user_cmd_bufs);
6250 }
6251 }
6252 }
6253
6254 passes.push(current_pass);
6256
6257 let globals_bytes = std::mem::size_of::<Globals>() as u64;
6258 let globals_staging = self.device.create_buffer(&wgpu::BufferDescriptor {
6259 label: Some("globals staging"),
6260 size: (passes.len().max(1) as u64) * globals_bytes,
6261 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
6262 mapped_at_creation: false,
6263 });
6264 for (i, pass) in passes.iter().enumerate() {
6265 let (target_w, target_h) = match pass.target {
6266 PassTarget::Surface => (fb_w, fb_h),
6267 PassTarget::Layer(layer_id) => {
6268 let lt = self.layer_pool.get(&layer_id);
6269 (
6270 lt.map_or(fb_w, |l| l.width as f32),
6271 lt.map_or(fb_h, |l| l.height as f32),
6272 )
6273 }
6274 };
6275 self.queue.write_buffer(
6276 &globals_staging,
6277 (i as u64) * globals_bytes,
6278 bytemuck::bytes_of(&make_globals(target_w, target_h)),
6279 );
6280 }
6281
6282 let bind_mask = self.atlas_bind_group_mask();
6283 let bind_color = self.atlas_bind_group_color();
6284 let mut clip_depth: u32 = 0;
6285
6286 for (pass_index, pass) in std::mem::take(&mut passes).into_iter().enumerate() {
6287 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
6288 PassTarget::Surface => {
6289 let swap_view = target_view.clone();
6290 let use_ws = self.working_space && self.ws_view.is_some();
6291 let (color, resolve) = if use_ws {
6292 let ws_view = self.ws_view.as_ref().unwrap();
6293 if let Some(msaa_view) = &self.msaa_view {
6294 (msaa_view.clone(), Some(ws_view.clone()))
6296 } else {
6297 (ws_view.clone(), None)
6299 }
6300 } else if let Some(msaa_view) = &self.msaa_view {
6301 (msaa_view.clone(), Some(swap_view))
6302 } else {
6303 (swap_view, None)
6304 };
6305 (color, resolve, self.depth_stencil_view.clone(), false)
6306 }
6307 PassTarget::Layer(layer_id) => {
6308 if let Some(lt) = self.layer_pool.get(&layer_id) {
6309 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
6310 } else {
6311 log::warn!("missing layer target {layer_id}");
6312 continue;
6313 }
6314 }
6315 };
6316
6317 encoder.copy_buffer_to_buffer(
6318 &globals_staging,
6319 (pass_index as u64) * globals_bytes,
6320 &self.globals_buf,
6321 0,
6322 globals_bytes,
6323 );
6324
6325 if is_layer {
6326 clip_depth = 0;
6327 }
6328
6329 let (tw, th) = match pass.target {
6330 PassTarget::Surface => (self.output_width, self.output_height),
6331 PassTarget::Layer(layer_id) => self
6332 .layer_pool
6333 .get(&layer_id)
6334 .map(|l| (l.width, l.height))
6335 .unwrap_or((self.output_width, self.output_height)),
6336 };
6337 let initial_scissor = clamp_scissor(
6338 pass.initial_scissor.0,
6339 pass.initial_scissor.1,
6340 pass.initial_scissor.2,
6341 pass.initial_scissor.3,
6342 tw,
6343 th,
6344 );
6345
6346 let pipes: &Pipelines = if is_layer {
6347 &self.layer_pipes
6348 } else {
6349 &self.surface_pipes
6350 };
6351
6352 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
6353 label: Some("pass"),
6354 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
6355 view: &color_view,
6356 resolve_target: resolve_target.as_ref(),
6357 ops: wgpu::Operations {
6358 load: match pass.clear_color {
6359 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
6360 r: c[0] as f64,
6361 g: c[1] as f64,
6362 b: c[2] as f64,
6363 a: c[3] as f64,
6364 }),
6365 None => wgpu::LoadOp::Load,
6366 },
6367 store: wgpu::StoreOp::Store,
6368 },
6369 depth_slice: None,
6370 })],
6371 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
6372 view: &depth_stencil_view,
6373 depth_ops: None,
6374 stencil_ops: Some(wgpu::Operations {
6375 load: if is_layer || pass.clear_color.is_some() {
6376 wgpu::LoadOp::Clear(0)
6377 } else {
6378 wgpu::LoadOp::Load
6379 },
6380 store: wgpu::StoreOp::Store,
6381 }),
6382 }),
6383 timestamp_writes: None,
6384 occlusion_query_set: None,
6385 multiview_mask: None,
6386 });
6387
6388 rpass.set_bind_group(0, &self.globals_bind, &[]);
6389 rpass.set_stencil_reference(clip_depth);
6390 rpass.set_scissor_rect(
6391 initial_scissor.0,
6392 initial_scissor.1,
6393 initial_scissor.2,
6394 initial_scissor.3,
6395 );
6396
6397 macro_rules! draw_simple {
6398 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
6399 rpass.set_pipeline($pipeline);
6400 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
6401 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
6402 rpass.draw(0..6, 0..$n);
6403 }};
6404 }
6405
6406 macro_rules! draw_with_bind {
6407 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
6408 rpass.set_pipeline($pipeline);
6409 rpass.set_bind_group(1, $bind, &[]);
6410 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
6411 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
6412 rpass.draw(0..6, 0..$n);
6413 }};
6414 }
6415
6416 macro_rules! draw_indexed_mesh {
6417 ($pipeline:expr, $uoff:ident, $voff:ident, $vcnt:ident, $ioff:ident, $icnt:ident) => {{
6418 rpass.set_pipeline($pipeline);
6419 rpass.set_bind_group(1, &self.mesh_bind, &[$uoff as u32]);
6420 let vbytes = ($vcnt as u64) * std::mem::size_of::<MeshVertex>() as u64;
6421 rpass.set_vertex_buffer(0, self.mesh_verts.buf.slice($voff..$voff + vbytes));
6422 let ibytes = ($icnt as u64) * std::mem::size_of::<u32>() as u64;
6423 rpass.set_index_buffer(
6424 self.mesh_indices.buf.slice($ioff..$ioff + ibytes),
6425 wgpu::IndexFormat::Uint32,
6426 );
6427 rpass.draw_indexed(0..$icnt, 0, 0..1);
6428 }};
6429 }
6430
6431 for cmd in pass.cmds {
6432 match cmd {
6433 Cmd::ClipPush {
6434 off,
6435 cnt: n,
6436 scissor,
6437 difference,
6438 rounded,
6439 } => {
6440 let scissor =
6441 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
6442 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
6443 rpass.set_stencil_reference(clip_depth);
6444
6445 if difference {
6446 rpass.set_pipeline(&pipes.clip_dec);
6447 } else if self.msaa_samples > 1 && !is_layer && rounded {
6448 rpass.set_pipeline(&pipes.clip_a2c);
6449 } else {
6450 rpass.set_pipeline(&pipes.clip_bin);
6451 }
6452
6453 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
6454 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
6455 rpass.draw(0..6, 0..n);
6456
6457 if !difference {
6458 clip_depth = (clip_depth + 1).min(255);
6459 rpass.set_stencil_reference(clip_depth);
6460 }
6461 }
6462
6463 Cmd::ClipPop {
6464 off,
6465 cnt: n,
6466 scissor,
6467 difference,
6468 } => {
6469 let scissor =
6470 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
6471 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
6472
6473 if !difference && n > 0 {
6474 rpass.set_stencil_reference(clip_depth);
6475 rpass.set_pipeline(&pipes.clip_dec);
6476 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
6477 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
6478 rpass.draw(0..6, 0..n);
6479 clip_depth = clip_depth.saturating_sub(1);
6480 } else if !difference {
6481 clip_depth = clip_depth.saturating_sub(1);
6482 }
6483 rpass.set_stencil_reference(clip_depth);
6484 }
6485
6486 Cmd::Rect { off, cnt: n } => {
6487 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
6488 }
6489
6490 Cmd::Border { off, cnt: n } => {
6491 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
6492 }
6493
6494 Cmd::GlyphsMask { off, cnt: n } => {
6495 draw_with_bind!(
6496 &pipes.text_mask,
6497 self.glyph_mask.ring,
6498 GlyphInstance,
6499 &bind_mask,
6500 off,
6501 n
6502 );
6503 }
6504
6505 Cmd::GlyphsColor { off, cnt: n } => {
6506 draw_with_bind!(
6507 &pipes.text_color,
6508 self.glyph_color.ring,
6509 GlyphInstance,
6510 &bind_color,
6511 off,
6512 n
6513 );
6514 }
6515
6516 Cmd::GlyphsVector { off, cnt: n } => {
6517 if let Some(slug_pipe) = pipes.slug.as_ref() {
6518 rpass.set_pipeline(slug_pipe);
6519 let bytes = (n as u64) * std::mem::size_of::<slug::TessVertex>() as u64;
6520 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
6521 rpass.draw(0..n, 0..1);
6522 }
6523 }
6524
6525 Cmd::ImageRgba {
6526 off,
6527 cnt: n,
6528 handle,
6529 } => {
6530 let bind_opt = match self.images.get(&handle) {
6531 Some(ImageTex::Rgba { bind, .. }) => Some(bind),
6532 Some(ImageTex::User { bind, .. }) => Some(bind),
6533 _ => None,
6534 };
6535 if let Some(bind) = bind_opt {
6536 draw_with_bind!(
6537 &pipes.image_rgba,
6538 self.glyph_color.ring,
6539 GlyphInstance,
6540 bind,
6541 off,
6542 n
6543 );
6544 }
6545 }
6546 Cmd::Coverage {
6547 off,
6548 cnt: n,
6549 handle,
6550 } => {
6551 if let Some(tile) = self.coverages.get(&handle) {
6552 draw_with_bind!(
6553 &pipes.coverage,
6554 self.glyph_color.ring,
6555 GlyphInstance,
6556 &tile.bind,
6557 off,
6558 n
6559 );
6560 }
6561 }
6562
6563 Cmd::ImageNv12 {
6564 off,
6565 cnt: n,
6566 handle,
6567 } => {
6568 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
6569 draw_with_bind!(
6570 &pipes.image_nv12,
6571 self.nv12.ring,
6572 Nv12Instance,
6573 bind,
6574 off,
6575 n
6576 );
6577 }
6578 }
6579
6580 Cmd::Ellipse { off, cnt: n } => {
6581 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
6582 }
6583
6584 Cmd::EllipseBorder { off, cnt: n } => {
6585 draw_simple!(
6586 &pipes.ellipse_borders,
6587 self.ellipse_borders.ring,
6588 EllipseBorderInstance,
6589 off,
6590 n
6591 );
6592 }
6593
6594 Cmd::Arc { off, cnt: n } => {
6595 draw_simple!(&pipes.arcs, self.arcs.ring, ArcInstance, off, n);
6596 }
6597
6598 Cmd::CompositeLayer {
6599 off,
6600 cnt: n,
6601 layer_id,
6602 } => {
6603 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
6604 draw_with_bind!(
6605 &pipes.image_rgba,
6606 self.glyph_color.ring,
6607 GlyphInstance,
6608 <.bind,
6609 off,
6610 n
6611 );
6612 }
6613 }
6614 Cmd::CompositeShadow {
6615 off,
6616 cnt: n,
6617 layer_id,
6618 } => {
6619 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
6620 draw_with_bind!(
6621 &pipes.blur,
6622 self.blur_ring,
6623 BlurInstance,
6624 <.bind_linear,
6625 off,
6626 n
6627 );
6628 }
6629 }
6630 Cmd::CompositeBlur {
6631 off,
6632 cnt: n,
6633 layer_id,
6634 } => {
6635 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
6636 draw_with_bind!(
6637 &pipes.blur_content,
6638 self.blur_ring,
6639 BlurInstance,
6640 <.bind_linear,
6641 off,
6642 n
6643 );
6644 }
6645 }
6646 Cmd::CompositeProjective {
6647 off,
6648 cnt: n,
6649 layer_id,
6650 } => {
6651 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
6652 draw_with_bind!(
6656 &pipes.projective_layer,
6657 self.projective_ring,
6658 ProjectiveInstance,
6659 <.bind,
6660 off,
6661 n
6662 );
6663 }
6664 }
6665
6666 Cmd::VectorMesh {
6667 voff,
6668 vcnt,
6669 ioff,
6670 icnt,
6671 uoff,
6672 } => {
6673 draw_indexed_mesh!(&pipes.mesh, uoff, voff, vcnt, ioff, icnt);
6674 }
6675
6676 Cmd::VectorOverlay {
6677 voff,
6678 vcnt,
6679 ioff,
6680 icnt,
6681 uoff,
6682 } => {
6683 draw_indexed_mesh!(&pipes.mesh_overlay, uoff, voff, vcnt, ioff, icnt);
6684 }
6685
6686 Cmd::VectorClipPush {
6687 voff,
6688 vcnt,
6689 ioff,
6690 icnt,
6691 uoff,
6692 scissor,
6693 difference,
6694 } => {
6695 let scissor =
6696 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
6697 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
6698 rpass.set_stencil_reference(clip_depth);
6699 draw_indexed_mesh!(&pipes.mesh_clip_inc, uoff, voff, vcnt, ioff, icnt);
6700 if !difference {
6701 clip_depth = (clip_depth + 1).min(255);
6702 rpass.set_stencil_reference(clip_depth);
6703 }
6704 }
6709
6710 Cmd::VectorClipPop {
6711 voff,
6712 vcnt,
6713 ioff,
6714 icnt,
6715 uoff,
6716 scissor,
6717 difference,
6718 } => {
6719 if difference {
6725 rpass.set_stencil_reference((clip_depth + 1).min(255));
6726 } else {
6727 rpass.set_stencil_reference(clip_depth);
6728 }
6729 let scissor =
6730 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
6731 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
6732 draw_indexed_mesh!(&pipes.mesh_clip_dec, uoff, voff, vcnt, ioff, icnt);
6733 if !difference {
6734 clip_depth = clip_depth.saturating_sub(1);
6735 }
6736 rpass.set_stencil_reference(clip_depth);
6737 }
6738
6739 Cmd::Callback { rect, payload } => {
6740 if let Some(cb) = payload.downcast_ref::<Callback>() {
6741 let vp_x = rect.x.floor().max(0.0);
6742 let vp_y = rect.y.floor().max(0.0);
6743 let vp_w = rect.w.ceil().max(1.0);
6744 let vp_h = rect.h.ceil().max(1.0);
6745 if vp_w > 0.0 && vp_h > 0.0 {
6746 rpass.set_viewport(vp_x, vp_y, vp_w, vp_h, 0.0, 1.0);
6747 let info = repose_core::PaintCallbackInfo {
6748 viewport: rect,
6749 clip_rect: rect,
6750 pixels_per_point: self.pixels_per_point,
6751 screen_size_px: [tw, th],
6752 };
6753 let rpass_static: &mut wgpu::RenderPass<'static> = unsafe {
6754 std::mem::transmute::<
6755 &mut wgpu::RenderPass<'_>,
6756 &mut wgpu::RenderPass<'static>,
6757 >(&mut rpass)
6758 };
6759 cb.0.paint(info, rpass_static, &self.callback_resources);
6760 rpass.set_viewport(0.0, 0.0, tw as f32, th as f32, 0.0, 1.0);
6761 rpass.set_bind_group(0, &self.globals_bind, &[]);
6762 rpass.set_stencil_reference(clip_depth);
6763 }
6764 } else {
6765 log::warn!("Unknown paint callback payload");
6766 }
6767 }
6768 }
6769 }
6770 }
6771
6772 self.flatten_layer_ids = flatten_ids_used;
6775
6776 if self.working_space
6778 && let (Some(_ws_view), Some(ws_bind), Some(display_pipeline)) =
6779 (&self.ws_view, &self.ws_bind, &self.display_pipeline)
6780 {
6781 let swap_view = target_view.clone();
6782 let mut display_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
6783 label: Some("display transform"),
6784 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
6785 view: &swap_view,
6786 resolve_target: None,
6787 ops: wgpu::Operations {
6788 load: wgpu::LoadOp::Load,
6789 store: wgpu::StoreOp::Store,
6790 },
6791 depth_slice: None,
6792 })],
6793 depth_stencil_attachment: None,
6794 timestamp_writes: None,
6795 occlusion_query_set: None,
6796 multiview_mask: None,
6797 });
6798 display_pass.set_pipeline(display_pipeline);
6799 display_pass.set_bind_group(1, ws_bind, &[]);
6800 display_pass.draw(0..3, 0..1);
6801 }
6802
6803 self.evict_unused_images();
6805 }
6806
6807 pub fn render_to_view(
6811 &mut self,
6812 scene: &Scene,
6813 encoder: &mut wgpu::CommandEncoder,
6814 target_view: &wgpu::TextureView,
6815 width: u32,
6816 height: u32,
6817 clear_color: Option<[f64; 4]>,
6818 ) {
6819 self.resize(width, height);
6820
6821 self.frame_index = self.frame_index.wrapping_add(1);
6822 self.slug_cache.next_frame();
6823
6824 if width == 0 || height == 0 {
6825 return;
6826 }
6827
6828 self.render_scene_to_encoder(scene, encoder, target_view, clear_color);
6829 }
6830}
6831
6832fn clamp_scissor(x: u32, y: u32, w: u32, h: u32, tw: u32, th: u32) -> (u32, u32, u32, u32) {
6833 let x = x.min(tw.saturating_sub(1));
6834 let y = y.min(th.saturating_sub(1));
6835 let w = w.min(tw.saturating_sub(x)).max(1);
6836 let h = h.min(th.saturating_sub(y)).max(1);
6837 (x, y, w, h)
6838}
6839
6840fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
6841 let x0 = a.x.max(b.x);
6842 let y0 = a.y.max(b.y);
6843 let x1 = (a.x + a.w).min(b.x + b.w);
6844 let y1 = (a.y + a.h).min(b.y + b.h);
6845 repose_core::Rect {
6846 x: x0,
6847 y: y0,
6848 w: (x1 - x0).max(0.0),
6849 h: (y1 - y0).max(0.0),
6850 }
6851}