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