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