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