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