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