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