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