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