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