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