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