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