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 if let Some(lt) = self.layer_pool.get_mut(&layer_id) {
2686 lt.rect_px = (rect.x, rect.y, rect.w, rect.h);
2687 }
2688 return;
2689 }
2690 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2691 label: Some("graphics layer"),
2692 size: wgpu::Extent3d {
2693 width: width.max(1),
2694 height: height.max(1),
2695 depth_or_array_layers: 1,
2696 },
2697 mip_level_count: 1,
2698 sample_count: 1,
2699 dimension: wgpu::TextureDimension::D2,
2700 format: self.output_format,
2701 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
2702 view_formats: &[],
2703 });
2704 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
2705 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2706 label: Some("layer bind"),
2707 layout: &self.image_bind_layout_rgba,
2708 entries: &[
2709 wgpu::BindGroupEntry {
2710 binding: 0,
2711 resource: wgpu::BindingResource::TextureView(&view),
2712 },
2713 wgpu::BindGroupEntry {
2714 binding: 1,
2715 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
2716 },
2717 ],
2718 });
2719 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
2720 label: Some("graphics layer depth-stencil"),
2721 size: wgpu::Extent3d {
2722 width: width.max(1),
2723 height: height.max(1),
2724 depth_or_array_layers: 1,
2725 },
2726 mip_level_count: 1,
2727 sample_count: 1,
2728 dimension: wgpu::TextureDimension::D2,
2729 format: wgpu::TextureFormat::Depth24PlusStencil8,
2730 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
2731 view_formats: &[],
2732 });
2733 let depth_stencil_view =
2734 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
2735 self.layer_pool.insert(
2736 layer_id,
2737 LayerTarget {
2738 texture: tex,
2739 view,
2740 bind,
2741 depth_stencil_tex,
2742 depth_stencil_view,
2743 width,
2744 height,
2745 rect_px: (rect.x, rect.y, rect.w, rect.h),
2746 },
2747 );
2748 }
2749
2750 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
2751 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2752 label: Some("atlas bind"),
2753 layout: &self.text_bind_layout,
2754 entries: &[
2755 wgpu::BindGroupEntry {
2756 binding: 0,
2757 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
2758 },
2759 wgpu::BindGroupEntry {
2760 binding: 1,
2761 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
2762 },
2763 ],
2764 })
2765 }
2766
2767 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
2768 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2769 label: Some("atlas bind color"),
2770 layout: &self.text_bind_layout,
2771 entries: &[
2772 wgpu::BindGroupEntry {
2773 binding: 0,
2774 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
2775 },
2776 wgpu::BindGroupEntry {
2777 binding: 1,
2778 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
2779 },
2780 ],
2781 })
2782 }
2783
2784 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
2785 let keyp = (key, px.to_bits());
2786 if let Some(info) = self.atlas_mask.map.get(&keyp) {
2787 return Some(*info);
2788 }
2789
2790 let gb = repose_text::rasterize(key, px)?;
2791 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
2792 return None;
2793 }
2794
2795 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
2796
2797 let w = gb.w.max(1);
2798 let h = gb.h.max(1);
2799
2800 if !self.alloc_space_mask(w, h) {
2801 self.grow_mask_and_rebuild();
2802 }
2803 if !self.alloc_space_mask(w, h) {
2804 return None;
2805 }
2806 let x = self.atlas_mask.next_x;
2807 let y = self.atlas_mask.next_y;
2808 self.atlas_mask.next_x += w + 1;
2809 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
2810
2811 let layout = wgpu::TexelCopyBufferLayout {
2812 offset: 0,
2813 bytes_per_row: Some(w),
2814 rows_per_image: Some(h),
2815 };
2816 let size = wgpu::Extent3d {
2817 width: w,
2818 height: h,
2819 depth_or_array_layers: 1,
2820 };
2821 self.queue.write_texture(
2822 wgpu::TexelCopyTextureInfoBase {
2823 texture: &self.atlas_mask.tex,
2824 mip_level: 0,
2825 origin: wgpu::Origin3d { x, y, z: 0 },
2826 aspect: wgpu::TextureAspect::All,
2827 },
2828 &coverage,
2829 layout,
2830 size,
2831 );
2832
2833 let info = GlyphInfo {
2834 u0: x as f32 / self.atlas_mask.size as f32,
2835 v0: y as f32 / self.atlas_mask.size as f32,
2836 u1: (x + w) as f32 / self.atlas_mask.size as f32,
2837 v1: (y + h) as f32 / self.atlas_mask.size as f32,
2838 w: w as f32,
2839 h: h as f32,
2840 bearing_x: 0.0,
2841 bearing_y: 0.0,
2842 advance: 0.0,
2843 };
2844 self.atlas_mask.map.insert(keyp, info);
2845 Some(info)
2846 }
2847
2848 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
2849 let keyp = (key, px.to_bits());
2850 if let Some(info) = self.atlas_color.map.get(&keyp) {
2851 return Some(*info);
2852 }
2853 let gb = repose_text::rasterize(key, px)?;
2854 if !matches!(gb.content, repose_text::SwashContent::Color) {
2855 return None;
2856 }
2857 let w = gb.w.max(1);
2858 let h = gb.h.max(1);
2859 if !self.alloc_space_color(w, h) {
2860 self.grow_color_and_rebuild();
2861 }
2862 if !self.alloc_space_color(w, h) {
2863 return None;
2864 }
2865 let x = self.atlas_color.next_x;
2866 let y = self.atlas_color.next_y;
2867 self.atlas_color.next_x += w + 1;
2868 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
2869
2870 let layout = wgpu::TexelCopyBufferLayout {
2871 offset: 0,
2872 bytes_per_row: Some(w * 4),
2873 rows_per_image: Some(h),
2874 };
2875 let size = wgpu::Extent3d {
2876 width: w,
2877 height: h,
2878 depth_or_array_layers: 1,
2879 };
2880 self.queue.write_texture(
2881 wgpu::TexelCopyTextureInfoBase {
2882 texture: &self.atlas_color.tex,
2883 mip_level: 0,
2884 origin: wgpu::Origin3d { x, y, z: 0 },
2885 aspect: wgpu::TextureAspect::All,
2886 },
2887 &gb.data,
2888 layout,
2889 size,
2890 );
2891 let info = GlyphInfo {
2892 u0: x as f32 / self.atlas_color.size as f32,
2893 v0: y as f32 / self.atlas_color.size as f32,
2894 u1: (x + w) as f32 / self.atlas_color.size as f32,
2895 v1: (y + h) as f32 / self.atlas_color.size as f32,
2896 w: w as f32,
2897 h: h as f32,
2898 bearing_x: 0.0,
2899 bearing_y: 0.0,
2900 advance: 0.0,
2901 };
2902 self.atlas_color.map.insert(keyp, info);
2903 Some(info)
2904 }
2905
2906 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
2907 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
2908 self.atlas_mask.next_x = 1;
2909 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
2910 self.atlas_mask.row_h = 0;
2911 }
2912 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
2913 return false;
2914 }
2915 true
2916 }
2917
2918 fn grow_mask_and_rebuild(&mut self) {
2919 let new_size = (self.atlas_mask.size * 2).min(4096);
2920 if new_size == self.atlas_mask.size {
2921 return;
2922 }
2923 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2924 label: Some("glyph atlas A8 (grown)"),
2925 size: wgpu::Extent3d {
2926 width: new_size,
2927 height: new_size,
2928 depth_or_array_layers: 1,
2929 },
2930 mip_level_count: 1,
2931 sample_count: 1,
2932 dimension: wgpu::TextureDimension::D2,
2933 format: wgpu::TextureFormat::R8Unorm,
2934 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2935 view_formats: &[],
2936 });
2937 self.atlas_mask.tex = tex;
2938 self.atlas_mask.view = self
2939 .atlas_mask
2940 .tex
2941 .create_view(&wgpu::TextureViewDescriptor::default());
2942 self.atlas_mask.size = new_size;
2943 self.atlas_mask.next_x = 1;
2944 self.atlas_mask.next_y = 1;
2945 self.atlas_mask.row_h = 0;
2946 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
2947 self.atlas_mask.map.clear();
2948 for (k, px_bits) in keys {
2949 let _ = self.upload_glyph_mask(k, f32::from_bits(px_bits));
2950 }
2951 }
2952
2953 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
2954 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
2955 self.atlas_color.next_x = 1;
2956 self.atlas_color.next_y += self.atlas_color.row_h + 1;
2957 self.atlas_color.row_h = 0;
2958 }
2959 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
2960 return false;
2961 }
2962 true
2963 }
2964
2965 fn grow_color_and_rebuild(&mut self) {
2966 let new_size = (self.atlas_color.size * 2).min(4096);
2967 if new_size == self.atlas_color.size {
2968 return;
2969 }
2970 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2971 label: Some("glyph atlas RGBA (grown)"),
2972 size: wgpu::Extent3d {
2973 width: new_size,
2974 height: new_size,
2975 depth_or_array_layers: 1,
2976 },
2977 mip_level_count: 1,
2978 sample_count: 1,
2979 dimension: wgpu::TextureDimension::D2,
2980 format: wgpu::TextureFormat::Rgba8UnormSrgb,
2981 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2982 view_formats: &[],
2983 });
2984 self.atlas_color.tex = tex;
2985 self.atlas_color.view = self
2986 .atlas_color
2987 .tex
2988 .create_view(&wgpu::TextureViewDescriptor::default());
2989 self.atlas_color.size = new_size;
2990 self.atlas_color.next_x = 1;
2991 self.atlas_color.next_y = 1;
2992 self.atlas_color.row_h = 0;
2993 let keys: Vec<(repose_text::GlyphKey, u32)> =
2994 self.atlas_color.map.keys().copied().collect();
2995 self.atlas_color.map.clear();
2996 for (k, px_bits) in keys {
2997 let _ = self.upload_glyph_color(k, f32::from_bits(px_bits));
2998 }
2999 }
3000}
3001
3002fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
3003 match brush {
3004 Brush::Solid(c) => (
3005 0u32,
3006 c.to_linear(),
3007 [0.0, 0.0, 0.0, 0.0],
3008 [0.0, 0.0],
3009 [0.0, 1.0],
3010 ),
3011 Brush::Linear {
3012 start,
3013 end,
3014 start_color,
3015 end_color,
3016 } => (
3017 1u32,
3018 start_color.to_linear(),
3019 end_color.to_linear(),
3020 [start.x, start.y],
3021 [end.x, end.y],
3022 ),
3023 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
3024 }
3025}
3026
3027fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
3028 match brush {
3029 Brush::Solid(c) => c.to_linear(),
3030 Brush::Linear { start_color, .. } => start_color.to_linear(),
3031 _ => [0.0; 4],
3032 }
3033}
3034
3035fn init_atlas_mask(device: &wgpu::Device) -> AtlasA8 {
3036 let size = 1024u32;
3037 let tex = device.create_texture(&wgpu::TextureDescriptor {
3038 label: Some("glyph atlas A8"),
3039 size: wgpu::Extent3d {
3040 width: size,
3041 height: size,
3042 depth_or_array_layers: 1,
3043 },
3044 mip_level_count: 1,
3045 sample_count: 1,
3046 dimension: wgpu::TextureDimension::D2,
3047 format: wgpu::TextureFormat::R8Unorm,
3048 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3049 view_formats: &[],
3050 });
3051 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3052 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
3053 label: Some("glyph atlas sampler A8"),
3054 address_mode_u: wgpu::AddressMode::ClampToEdge,
3055 address_mode_v: wgpu::AddressMode::ClampToEdge,
3056 address_mode_w: wgpu::AddressMode::ClampToEdge,
3057 mag_filter: wgpu::FilterMode::Linear,
3058 min_filter: wgpu::FilterMode::Linear,
3059 mipmap_filter: wgpu::MipmapFilterMode::Linear,
3060 ..Default::default()
3061 });
3062
3063 AtlasA8 {
3064 tex,
3065 view,
3066 sampler,
3067 size,
3068 next_x: 1,
3069 next_y: 1,
3070 row_h: 0,
3071 map: HashMap::new(),
3072 }
3073}
3074
3075fn init_atlas_color(device: &wgpu::Device) -> AtlasRGBA {
3076 let size = 1024u32;
3077 let tex = device.create_texture(&wgpu::TextureDescriptor {
3078 label: Some("glyph atlas RGBA"),
3079 size: wgpu::Extent3d {
3080 width: size,
3081 height: size,
3082 depth_or_array_layers: 1,
3083 },
3084 mip_level_count: 1,
3085 sample_count: 1,
3086 dimension: wgpu::TextureDimension::D2,
3087 format: wgpu::TextureFormat::Rgba8UnormSrgb,
3088 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3089 view_formats: &[],
3090 });
3091 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3092 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
3093 label: Some("glyph atlas sampler RGBA"),
3094 address_mode_u: wgpu::AddressMode::ClampToEdge,
3095 address_mode_v: wgpu::AddressMode::ClampToEdge,
3096 address_mode_w: wgpu::AddressMode::ClampToEdge,
3097 mag_filter: wgpu::FilterMode::Linear,
3098 min_filter: wgpu::FilterMode::Linear,
3099 mipmap_filter: wgpu::MipmapFilterMode::Linear,
3100 ..Default::default()
3101 });
3102 AtlasRGBA {
3103 tex,
3104 view,
3105 sampler,
3106 size,
3107 next_x: 1,
3108 next_y: 1,
3109 row_h: 0,
3110 map: HashMap::new(),
3111 }
3112}
3113
3114#[cfg(feature = "winit-surface")]
3115impl RenderBackend for WgpuSurfaceBackend {
3116 fn configure_surface(&mut self, width: u32, height: u32) {
3117 if width == 0 || height == 0 {
3118 return;
3119 }
3120 self.renderer.output_width = width;
3121 self.renderer.output_height = height;
3122 if let Some(ref mut config) = self.surface_config {
3123 config.width = width;
3124 config.height = height;
3125 }
3126 if let (Some(surface), Some(config)) = (self.surface.as_ref(), self.surface_config.as_ref()) {
3127 surface.configure(&self.renderer.device, config);
3128 }
3129 self.renderer.recreate_msaa_and_depth_stencil();
3130 self.renderer.recreate_working_space_texture();
3131 }
3132
3133 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
3134 let surface = self.surface.as_ref().expect("WgpuSurfaceBackend::frame() requires a surface (use from_device + render_to_view instead)");
3135 let surface_config = self.surface_config.as_ref().expect("surface_config required for frame()");
3136
3137 self.renderer.frame_index = self.renderer.frame_index.wrapping_add(1);
3138 self.renderer.slug_cache.next_frame();
3139
3140 if self.renderer.output_width == 0 || self.renderer.output_height == 0 {
3141 return;
3142 }
3143
3144 let mut retries = 0u32;
3145 const MAX_RETRIES: u32 = 4;
3146 let frame = loop {
3147 match surface.get_current_texture() {
3148 wgpu::CurrentSurfaceTexture::Success(f) => break f,
3149 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
3150 log::warn!("suboptimal surface; reconfiguring");
3151 surface.configure(&self.renderer.device, surface_config);
3152 break f;
3153 }
3154 wgpu::CurrentSurfaceTexture::Outdated => {
3155 retries += 1;
3156 if retries >= MAX_RETRIES {
3157 log::warn!("surface outdated persisted after {MAX_RETRIES} retries; skipping frame");
3158 return;
3159 }
3160 log::warn!("surface outdated; reconfiguring");
3161 surface.configure(&self.renderer.device, surface_config);
3162 }
3163 wgpu::CurrentSurfaceTexture::Lost => {
3164 retries += 1;
3165 if retries >= MAX_RETRIES {
3166 log::warn!("surface lost persisted after {MAX_RETRIES} retries; skipping frame");
3167 return;
3168 }
3169 log::warn!("surface lost; reconfiguring");
3170 surface.configure(&self.renderer.device, surface_config);
3171 }
3172 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
3173 request_frame();
3174 return;
3175 }
3176 wgpu::CurrentSurfaceTexture::Validation => {
3177 retries += 1;
3178 if retries >= MAX_RETRIES {
3179 log::warn!("surface validation persisted after {MAX_RETRIES} retries; skipping frame");
3180 return;
3181 }
3182 surface.configure(&self.renderer.device, surface_config);
3183 }
3184 }
3185 };
3186
3187 let swap_view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default());
3188 let mut encoder = self.renderer.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
3189 label: Some("frame encoder"),
3190 });
3191
3192 let clear_color = Some([
3193 scene.clear_color.0 as f64 / 255.0,
3194 scene.clear_color.1 as f64 / 255.0,
3195 scene.clear_color.2 as f64 / 255.0,
3196 scene.clear_color.3 as f64 / 255.0,
3197 ]);
3198
3199 self.renderer.render_scene_to_encoder(scene, &mut encoder, &swap_view, clear_color);
3200
3201 self.renderer.queue.submit(std::iter::once(encoder.finish()));
3202 if let Err(e) = catch_unwind(AssertUnwindSafe(|| self.renderer.queue.present(frame))) {
3203 log::warn!("queue.present panicked: {:?}", e);
3204 }
3205 }
3206}
3207
3208impl WgpuSceneRenderer {
3209 pub fn render_scene_to_encoder(
3210 &mut self,
3211 scene: &Scene,
3212 encoder: &mut wgpu::CommandEncoder,
3213 target_view: &wgpu::TextureView,
3214 clear_color_override: Option<[f64; 4]>,
3215 ) {
3216 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
3217 let x0 = (x / fb_w) * 2.0 - 1.0;
3218 let y0 = 1.0 - (y / fb_h) * 2.0;
3219 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
3220 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
3221 let min_x = x0.min(x1);
3222 let min_y = y0.min(y1);
3223 let w_ndc = (x1 - x0).abs();
3224 let h_ndc = (y1 - y0).abs();
3225 [min_x, min_y, w_ndc, h_ndc]
3226 }
3227
3228 fn rect_to_instance_ndc(
3230 rect: repose_core::Rect,
3231 transform: &Transform,
3232 fb_w: f32,
3233 fb_h: f32,
3234 ) -> ([f32; 4], [f32; 2]) {
3235 let cx = rect.x + rect.w * 0.5;
3236 let cy = rect.y + rect.h * 0.5;
3237
3238 let sx = cx * transform.scale_x;
3240 let sy = cy * transform.scale_y;
3241 let cos_a = transform.rotate.cos();
3242 let sin_a = transform.rotate.sin();
3243 let tx = sx * cos_a - sy * sin_a + transform.translate_x;
3244 let ty = sx * sin_a + sy * cos_a + transform.translate_y;
3245
3246 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
3248 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
3249 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
3251 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
3252
3253 ([ndc_cx, ndc_cy, ndc_w, ndc_h], [cos_a, sin_a])
3254 }
3255
3256 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
3257 let mut x = r.x.floor() as i64;
3258 let mut y = r.y.floor() as i64;
3259 let fb_wi = fb_w as i64;
3260 let fb_hi = fb_h as i64;
3261 x = x.clamp(0, fb_wi.saturating_sub(1));
3262 y = y.clamp(0, fb_hi.saturating_sub(1));
3263 let w_req = r.w.ceil().max(1.0) as i64;
3264 let h_req = r.h.ceil().max(1.0) as i64;
3265 let w = (w_req).min(fb_wi - x).max(1);
3266 let h = (h_req).min(fb_hi - y).max(1);
3267 (x as u32, y as u32, w as u32, h as u32)
3268 }
3269
3270 let fb_w = self.output_width as f32;
3271 let fb_h = self.output_height as f32;
3272
3273 let mut passes: Vec<Pass> = Vec::with_capacity(1);
3274 let clear_color = clear_color_override.unwrap_or_else(|| {
3275 [
3276 scene.clear_color.0 as f64 / 255.0,
3277 scene.clear_color.1 as f64 / 255.0,
3278 scene.clear_color.2 as f64 / 255.0,
3279 scene.clear_color.3 as f64 / 255.0,
3280 ]
3281 });
3282 let mut current_pass: Pass = Pass {
3283 target: PassTarget::Surface,
3284 initial_scissor: (0, 0, self.output_width, self.output_height),
3285 clear_color: Some([
3286 clear_color[0] as f32,
3287 clear_color[1] as f32,
3288 clear_color[2] as f32,
3289 clear_color[3] as f32,
3290 ]),
3291 cmds: Vec::with_capacity(scene.nodes.len()),
3292 };
3293 let mut target_stack: Vec<PassTarget> = Vec::new();
3294 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
3295 let mut layer_blurs: Vec<(u32, f32, f32)> = Vec::new();
3296 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
3297
3298 struct Batch {
3299 rects: Vec<RectInstance>,
3300 borders: Vec<BorderInstance>,
3301 ellipses: Vec<EllipseInstance>,
3302 e_borders: Vec<EllipseBorderInstance>,
3303 arcs: Vec<ArcInstance>,
3304 masks: Vec<GlyphInstance>,
3305 colors: Vec<GlyphInstance>,
3306 nv12s: Vec<Nv12Instance>,
3307 }
3308
3309 impl Batch {
3310 fn new() -> Self {
3311 Self {
3312 rects: vec![],
3313 borders: vec![],
3314 ellipses: vec![],
3315 e_borders: vec![],
3316 arcs: vec![],
3317 masks: vec![],
3318 colors: vec![],
3319 nv12s: vec![],
3320 }
3321 }
3322
3323 fn is_empty(&self) -> bool {
3324 self.rects.is_empty()
3325 && self.borders.is_empty()
3326 && self.ellipses.is_empty()
3327 && self.e_borders.is_empty()
3328 && self.arcs.is_empty()
3329 && self.masks.is_empty()
3330 && self.colors.is_empty()
3331 && self.nv12s.is_empty()
3332 }
3333
3334 fn flush(
3335 &mut self,
3336 pipes: (
3337 &mut InstancedPipe<RectInstance>,
3338 &mut InstancedPipe<BorderInstance>,
3339 &mut InstancedPipe<EllipseInstance>,
3340 &mut InstancedPipe<EllipseBorderInstance>,
3341 &mut InstancedPipe<ArcInstance>,
3342 ),
3343 glyph_pipes: (
3344 &mut InstancedPipe<GlyphInstance>,
3345 &mut InstancedPipe<GlyphInstance>,
3346 ),
3347 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
3348 device: &wgpu::Device,
3349 queue: &wgpu::Queue,
3350 cmds: &mut Vec<Cmd>,
3351 ) {
3352 let (rects, borders, ellipses, e_borders, arcs) = pipes;
3353 let (masks, colors) = glyph_pipes;
3354
3355 macro_rules! flush_one {
3356 ($buf:ident, $pipe:expr, $variant:ident) => {
3357 if !self.$buf.is_empty() {
3358 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
3359 cmds.push(Cmd::$variant { off, cnt });
3360 }
3361 self.$buf.clear();
3362 }
3363 };
3364 }
3365
3366 flush_one!(rects, rects, Rect);
3367 flush_one!(borders, borders, Border);
3368 flush_one!(ellipses, ellipses, Ellipse);
3369 flush_one!(e_borders, e_borders, EllipseBorder);
3370 flush_one!(arcs, arcs, Arc);
3371 flush_one!(masks, masks, GlyphsMask);
3372 flush_one!(colors, colors, GlyphsColor);
3373
3374 if !self.nv12s.is_empty() {
3375 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
3376 let _ = (off, cnt);
3377 }
3378 self.nv12s.clear();
3379 }
3380 }
3381 }
3382
3383 self.rects.reset();
3384 self.borders.reset();
3385 self.ellipses.reset();
3386 self.ellipse_borders.reset();
3387 self.arcs.reset();
3388 self.glyph_mask.reset();
3389 self.glyph_color.reset();
3390 self.clip_ring.reset();
3391 self.blur_ring.reset();
3392 self.nv12.reset();
3393
3394 self.slug_ring.reset();
3395 let mut batch = Batch::new();
3396 let mut slug_verts_local: Vec<slug::TessVertex> = Vec::new();
3397 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
3398 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
3399 let root_clip_rect = repose_core::Rect {
3400 x: 0.0,
3401 y: 0.0,
3402 w: fb_w,
3403 h: fb_h,
3404 };
3405
3406 let mut current_prim: Option<&'static str> = None;
3407
3408 macro_rules! flush_if_prim_changed {
3409 ($prim:literal, $pipe:expr) => {
3410 if current_prim != Some($prim) {
3411 flush_batch!();
3412 current_prim = Some($prim);
3413 }
3414 };
3415 }
3416
3417 macro_rules! flush_batch {
3418 () => {
3419 if !batch.is_empty() {
3420 batch.flush(
3421 (
3422 &mut self.rects,
3423 &mut self.borders,
3424 &mut self.ellipses,
3425 &mut self.ellipse_borders,
3426 &mut self.arcs,
3427 ),
3428 (&mut self.glyph_mask, &mut self.glyph_color),
3429 &mut self.nv12,
3430 &self.device,
3431 &self.queue,
3432 &mut current_pass.cmds,
3433 )
3434 }
3435 };
3436 }
3437 for node in &scene.nodes {
3438 let t_identity = Transform::identity();
3439 let current_transform = transform_stack.last().unwrap_or(&t_identity);
3440
3441 match node {
3442 SceneNode::Rect {
3443 rect,
3444 brush,
3445 radius,
3446 } => {
3447 flush_if_prim_changed!("rect", &self.rects);
3448 let (ndc, sin_cos) = rect_to_instance_ndc(
3449 *rect,
3450 current_transform,
3451 current_target_size.0,
3452 current_target_size.1,
3453 );
3454 let (brush_type, color0, color1, grad_start, grad_end) =
3455 brush_to_instance_fields(brush);
3456 batch.rects.push(RectInstance {
3457 xywh: ndc,
3458 radii: *radius,
3459 brush_type,
3460 _pad: [0.0; 3],
3461 color0,
3462 color1,
3463 grad_start,
3464 grad_end,
3465 sin_cos,
3466 });
3467 }
3468 SceneNode::Border {
3469 rect,
3470 color,
3471 width,
3472 radius,
3473 } => {
3474 flush_if_prim_changed!("border", &self.borders);
3475 let (ndc, sin_cos) = rect_to_instance_ndc(
3476 *rect,
3477 current_transform,
3478 current_target_size.0,
3479 current_target_size.1,
3480 );
3481 batch.borders.push(BorderInstance {
3482 xywh: ndc,
3483 radii: *radius,
3484 stroke: *width,
3485 color: color.to_linear(),
3486 sin_cos,
3487 });
3488 }
3489 SceneNode::Ellipse { rect, brush } => {
3490 flush_if_prim_changed!("ellipse", &self.ellipses);
3491 let (ndc, sin_cos) = rect_to_instance_ndc(
3492 *rect,
3493 current_transform,
3494 current_target_size.0,
3495 current_target_size.1,
3496 );
3497 let color = brush_to_solid_color(brush);
3498 batch.ellipses.push(EllipseInstance {
3499 xywh: ndc,
3500 color,
3501 sin_cos,
3502 });
3503 }
3504 SceneNode::EllipseBorder { rect, color, width } => {
3505 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
3506 let (ndc, sin_cos) = rect_to_instance_ndc(
3507 *rect,
3508 current_transform,
3509 current_target_size.0,
3510 current_target_size.1,
3511 );
3512 let pad_px = *width * 0.5 + 2.0;
3513 let pad = (pad_px / current_target_size.0) * 2.0;
3514 batch.e_borders.push(EllipseBorderInstance {
3515 xywh: ndc,
3516 stroke: *width,
3517 pad,
3518 color: color.to_linear(),
3519 sin_cos,
3520 });
3521 }
3522 SceneNode::Arc {
3523 rect,
3524 start_angle,
3525 sweep_angle,
3526 stroke_width,
3527 color,
3528 cap,
3529 } => {
3530 flush_if_prim_changed!("arc", &self.arcs);
3531 let (ndc, sin_cos) = rect_to_instance_ndc(
3532 *rect,
3533 current_transform,
3534 current_target_size.0,
3535 current_target_size.1,
3536 );
3537 let pad_px = *stroke_width * 0.5 + 2.0;
3538 let pad = (pad_px / current_target_size.0) * 2.0;
3539 let cap_val = match cap {
3540 StrokeCap::Butt => 0.0,
3541 StrokeCap::Round => 1.0,
3542 StrokeCap::Square => 2.0,
3543 };
3544 batch.arcs.push(ArcInstance {
3545 xywh: ndc,
3546 start_angle: *start_angle,
3547 sweep_angle: *sweep_angle,
3548 stroke: *stroke_width,
3549 pad,
3550 color: color.to_linear(),
3551 sin_cos,
3552 cap: cap_val,
3553 });
3554 }
3555 SceneNode::Text {
3556 rect,
3557 text,
3558 color,
3559 size,
3560 font_family,
3561 text_align: _,
3562 font_weight,
3563 font_style,
3564 text_decoration,
3565 letter_spacing,
3566 line_height: _,
3567 extra_style,
3568 url: _,
3569 font_variation_settings,
3570 } => {
3571 flush_batch!(); let px = *size;
3574 let lh_ratio = rect.h / px;
3575 let fw = font_weight.0;
3576 let fs = if *font_style == FontStyle::Italic {
3577 1
3578 } else {
3579 0
3580 };
3581 let shaped = repose_text::shape_line(
3582 text.as_ref(),
3583 px,
3584 lh_ratio,
3585 *font_family,
3586 fw,
3587 fs,
3588 *letter_spacing,
3589 font_variation_settings.as_deref(),
3590 );
3591 let baseline_y = shaped.first().map(|g| rect.y + g.y);
3592
3593 let cos_a = current_transform.rotate.cos();
3594 let sin_a = current_transform.rotate.sin();
3595 let has_rotation = current_transform.rotate != 0.0;
3596
3597 let pivot_x = rect.x + rect.w * 0.5;
3599 let pivot_y = rect.y + rect.h * 0.5;
3600
3601 let make_glyph_instance =
3603 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 2]) {
3604 if has_rotation {
3605 let corners =
3606 [(gx, gy), (gx + gw, gy), (gx + gw, gy + gh), (gx, gy + gh)];
3607 let mut min_x = f32::MAX;
3608 let mut max_x = f32::MIN;
3609 let mut min_y = f32::MAX;
3610 let mut max_y = f32::MIN;
3611 for &(x, y) in &corners {
3612 let dx = x - pivot_x;
3613 let dy = y - pivot_y;
3614 let rx = pivot_x + dx * cos_a - dy * sin_a;
3615 let ry = pivot_y + dx * sin_a + dy * cos_a;
3616 min_x = min_x.min(rx);
3617 max_x = max_x.max(rx);
3618 min_y = min_y.min(ry);
3619 max_y = max_y.max(ry);
3620 }
3621 let bb_w = max_x - min_x;
3622 let bb_h = max_y - min_y;
3623 let ndc_tl = to_ndc(
3624 min_x,
3625 min_y,
3626 bb_w,
3627 bb_h,
3628 current_target_size.0,
3629 current_target_size.1,
3630 );
3631 let ndc = [
3632 ndc_tl[0] + ndc_tl[2] * 0.5,
3633 ndc_tl[1] + ndc_tl[3] * 0.5,
3634 ndc_tl[2],
3635 ndc_tl[3],
3636 ];
3637 (ndc, [cos_a, sin_a])
3638 } else {
3639 let (sx, sy) = if current_transform.scale_x == 1.0
3641 && current_transform.scale_y == 1.0
3642 {
3643 (gx.round(), gy.round())
3644 } else {
3645 (gx, gy)
3646 };
3647 rect_to_instance_ndc(
3648 repose_core::Rect {
3649 x: sx,
3650 y: sy,
3651 w: gw,
3652 h: gh,
3653 },
3654 current_transform,
3655 current_target_size.0,
3656 current_target_size.1,
3657 )
3658 }
3659 };
3660
3661 let baseline_shift_y: f32 = px * extra_style.baseline_shift.0;
3662
3663 let (
3664 is_stroke,
3665 stroke_width,
3666 stroke_cap,
3667 stroke_join,
3668 stroke_miter,
3669 stroke_path_effect,
3670 ) = match &extra_style.draw_style {
3671 repose_core::DrawStyle::Stroke {
3672 width,
3673 cap,
3674 join,
3675 miter,
3676 path_effect,
3677 } => (true, *width, *cap, *join, *miter, path_effect.clone()),
3678 _ => (
3679 false,
3680 0.0,
3681 repose_core::StrokeCap::Butt,
3682 repose_core::StrokeJoin::Miter,
3683 4.0,
3684 None,
3685 ),
3686 };
3687 let stroke_tess_key = if is_stroke {
3688 Some(slug::StrokeTessKey::new(
3689 stroke_width,
3690 stroke_cap,
3691 stroke_join,
3692 stroke_miter,
3693 &stroke_path_effect,
3694 ))
3695 } else {
3696 None
3697 };
3698
3699 for sg in shaped {
3700 let gx = rect.x + sg.x + sg.bearing_x;
3701 let gy = rect.y + sg.y - sg.bearing_y + baseline_shift_y;
3702
3703 if self.slug_enabled {
3705 let ck = repose_text::lookup_cache_key(sg.key, sg.px);
3706 if let Some(ref ck) = ck {
3707 let need_tessellate = self.slug_cache.get(ck).map_or(true, |g| {
3709 if is_stroke {
3710 let key = stroke_tess_key.as_ref().unwrap();
3711 !g.stroke_variants.contains_key(key)
3712 } else {
3713 g.fill_vertices.is_none()
3714 }
3715 });
3716 if need_tessellate {
3717 if let Some((ck2, commands)) =
3718 repose_text::lookup_and_extract_outline(sg.key, sg.px)
3719 {
3720 let font_size_px = f32::from_bits(ck2.font_size_bits);
3721 if is_stroke {
3722 self.slug_cache.get_or_insert_stroke(
3723 ck2,
3724 font_size_px,
3725 &commands,
3726 stroke_width,
3727 stroke_cap,
3728 stroke_join,
3729 stroke_miter,
3730 &stroke_path_effect,
3731 );
3732 } else {
3733 self.slug_cache.get_or_insert(
3734 ck2,
3735 font_size_px,
3736 &commands,
3737 );
3738 }
3739 }
3740 } else {
3741 self.slug_cache.touch(ck);
3742 }
3743 }
3744 if let Some(entry) = ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
3745 {
3746 let ox = rect.x + sg.x;
3747 let oy = rect.y + sg.y + baseline_shift_y;
3748 let scx = current_transform.scale_x;
3749 let scy = current_transform.scale_y;
3750 let ttx = current_transform.translate_x;
3751 let tty = current_transform.translate_y;
3752
3753 let tf = |x: f32, y: f32| -> (f32, f32) {
3754 if has_rotation {
3755 let dx = x - pivot_x;
3756 let dy = y - pivot_y;
3757 let rx = pivot_x + dx * cos_a - dy * sin_a;
3758 let ry = pivot_y + dx * sin_a + dy * cos_a;
3759 (rx, ry)
3760 } else {
3761 (x * scx + ttx, y * scy + tty)
3762 }
3763 };
3764
3765 let tw = current_target_size.0;
3766 let th = current_target_size.1;
3767
3768 let verts = if is_stroke {
3769 let key = stroke_tess_key.as_ref().unwrap();
3770 entry
3771 .stroke_variants
3772 .get(key)
3773 .map(|v| v.as_slice())
3774 .unwrap_or(&[])
3775 } else {
3776 entry.fill_vertices.as_deref().unwrap_or(&[])
3777 };
3778
3779 for &v in verts {
3780 let (sx, sy) = tf(ox + v[0] * px, oy - v[1] * px);
3781 let ndc_x = sx / tw * 2.0 - 1.0;
3782 let ndc_y = -(sy / th) * 2.0 + 1.0;
3783 slug_verts_local.push(slug::TessVertex {
3784 ndc_pos: [ndc_x, ndc_y],
3785 color: color.to_linear(),
3786 });
3787 }
3788
3789 if is_stroke {
3790 continue;
3792 }
3793 continue;
3794 }
3795 }
3796
3797 if is_stroke {
3799 continue;
3800 }
3801
3802 if let Some(info) = self.upload_glyph_color(sg.key, sg.px) {
3804 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
3805 batch.colors.push(GlyphInstance {
3806 xywh: ndc,
3807 uv: [info.u0, info.v1, info.u1, info.v0],
3808 color: color.to_linear(),
3809 sin_cos,
3810 });
3811 } else if let Some(info) = self.upload_glyph_mask(sg.key, sg.px) {
3812 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
3813 batch.masks.push(GlyphInstance {
3814 xywh: ndc,
3815 uv: [info.u0, info.v1, info.u1, info.v0],
3816 color: color.to_linear(),
3817 sin_cos,
3818 });
3819 }
3820 }
3821
3822 if !slug_verts_local.is_empty() {
3824 let bytes = bytemuck::cast_slice(&slug_verts_local);
3825 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
3826 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
3827 current_pass.cmds.push(Cmd::GlyphsVector {
3828 off,
3829 cnt: slug_verts_local.len() as u32,
3830 });
3831 slug_verts_local.clear();
3832 }
3833
3834 if (text_decoration.underline || text_decoration.strikethrough)
3836 && let Some(baseline_y) = baseline_y
3837 {
3838 flush_batch!();
3839 current_prim = Some("rect");
3840 let deco_color = text_decoration.color.unwrap_or(*color);
3841 let thickness = (px * 0.07).max(1.0);
3842
3843 if text_decoration.underline {
3844 let dy = baseline_y + px * 0.1;
3845 let (ndc, sin_cos) = rect_to_instance_ndc(
3846 repose_core::Rect {
3847 x: rect.x,
3848 y: dy,
3849 w: rect.w,
3850 h: thickness,
3851 },
3852 current_transform,
3853 current_target_size.0,
3854 current_target_size.1,
3855 );
3856 batch.rects.push(RectInstance {
3857 xywh: ndc,
3858 radii: [0.0; 4],
3859 brush_type: 0,
3860 _pad: [0.0; 3],
3861 color0: deco_color.to_linear(),
3862 color1: [0.0; 4],
3863 grad_start: [0.0; 2],
3864 grad_end: [0.0; 2],
3865 sin_cos,
3866 });
3867 }
3868 if text_decoration.strikethrough {
3869 let sy = baseline_y - px * 0.3;
3870 let (ndc, sin_cos) = rect_to_instance_ndc(
3871 repose_core::Rect {
3872 x: rect.x,
3873 y: sy,
3874 w: rect.w,
3875 h: thickness,
3876 },
3877 current_transform,
3878 current_target_size.0,
3879 current_target_size.1,
3880 );
3881 batch.rects.push(RectInstance {
3882 xywh: ndc,
3883 radii: [0.0; 4],
3884 brush_type: 0,
3885 _pad: [0.0; 3],
3886 color0: deco_color.to_linear(),
3887 color1: [0.0; 4],
3888 grad_start: [0.0; 2],
3889 grad_end: [0.0; 2],
3890 sin_cos,
3891 });
3892 }
3893 }
3894 }
3895 SceneNode::Image {
3896 rect,
3897 handle,
3898 tint,
3899 fit,
3900 } => {
3901 flush_batch!();
3902
3903 let (img_w, img_h, is_nv12) = if let Some(t) = self.images.get_mut(handle) {
3905 match t {
3906 ImageTex::Rgba {
3907 w,
3908 h,
3909 last_used_frame,
3910 ..
3911 } => {
3912 *last_used_frame = self.frame_index;
3913 (*w, *h, false)
3914 }
3915 ImageTex::Nv12 {
3916 w,
3917 h,
3918 last_used_frame,
3919 ..
3920 } => {
3921 *last_used_frame = self.frame_index;
3922 (*w, *h, true)
3923 }
3924 }
3925 } else {
3926 log::warn!("Image handle {} not found", handle);
3927 continue;
3928 };
3929
3930 let src_w = img_w as f32;
3931 let src_h = img_h as f32;
3932 let transformed = current_transform.apply_to_rect(*rect);
3933 let dst_w = transformed.w.max(0.0);
3934 let dst_h = transformed.h.max(0.0);
3935 if dst_w <= 0.0 || dst_h <= 0.0 {
3936 continue;
3937 }
3938
3939 let (xywh_ndc, uv_rect) = match fit {
3940 repose_core::view::ImageFit::Contain => {
3941 let scale = (dst_w / src_w).min(dst_h / src_h);
3942 let w = src_w * scale;
3943 let h = src_h * scale;
3944 let x = transformed.x + (dst_w - w) * 0.5;
3945 let y = transformed.y + (dst_h - h) * 0.5;
3946 (
3947 to_ndc(x, y, w, h, current_target_size.0, current_target_size.1),
3948 [0.0, 1.0, 1.0, 0.0],
3949 )
3950 }
3951 repose_core::view::ImageFit::Cover => {
3952 let scale = (dst_w / src_w).max(dst_h / src_h);
3953 let content_w = src_w * scale;
3954 let content_h = src_h * scale;
3955 let overflow_x = (content_w - dst_w) * 0.5;
3956 let overflow_y = (content_h - dst_h) * 0.5;
3957 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
3958 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
3959 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
3960 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
3961 (
3962 to_ndc(
3963 transformed.x,
3964 transformed.y,
3965 dst_w,
3966 dst_h,
3967 current_target_size.0,
3968 current_target_size.1,
3969 ),
3970 [u0, 1.0 - v1, u1, 1.0 - v0],
3971 )
3972 }
3973 repose_core::view::ImageFit::FitWidth => {
3974 let scale = dst_w / src_w;
3975 let w = dst_w;
3976 let h = src_h * scale;
3977 let y = transformed.y + (dst_h - h) * 0.5;
3978 (
3979 to_ndc(
3980 transformed.x,
3981 y,
3982 w,
3983 h,
3984 current_target_size.0,
3985 current_target_size.1,
3986 ),
3987 [0.0, 1.0, 1.0, 0.0],
3988 )
3989 }
3990 repose_core::view::ImageFit::FitHeight => {
3991 let scale = dst_h / src_h;
3992 let w = src_w * scale;
3993 let h = dst_h;
3994 let x = transformed.x + (dst_w - w) * 0.5;
3995 (
3996 to_ndc(
3997 x,
3998 transformed.y,
3999 w,
4000 h,
4001 current_target_size.0,
4002 current_target_size.1,
4003 ),
4004 [0.0, 1.0, 1.0, 0.0],
4005 )
4006 }
4007 _ => ([0.0; 4], [0.0; 4]),
4008 };
4009
4010 let ndc_center = [
4012 xywh_ndc[0] + xywh_ndc[2] * 0.5,
4013 xywh_ndc[1] + xywh_ndc[3] * 0.5,
4014 xywh_ndc[2],
4015 xywh_ndc[3],
4016 ];
4017
4018 if is_nv12 {
4019 let uv_x_offset = if let Some(ImageTex::Nv12 { w, color_info, .. }) =
4020 self.images.get(handle)
4021 {
4022 match color_info.chroma_siting {
4023 ChromaSiting::Center | ChromaSiting::TopLeft => 0.0,
4024 ChromaSiting::Left => -1.0 / *w as f32,
4025 }
4026 } else {
4027 0.0
4028 };
4029
4030 let inst = Nv12Instance {
4031 xywh: ndc_center,
4032 uv: uv_rect,
4033 color: tint.to_linear(),
4034 uv_x_offset,
4035 sin_cos: [1.0, 0.0],
4036 _pad: [0.0],
4037 };
4038 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
4039 {
4040 current_pass.cmds.push(Cmd::ImageNv12 {
4041 off,
4042 cnt: 1,
4043 handle: *handle,
4044 });
4045 }
4046 } else {
4047 let inst = GlyphInstance {
4049 xywh: ndc_center,
4050 uv: uv_rect,
4051 color: tint.to_linear(),
4052 sin_cos: [1.0, 0.0],
4053 };
4054 if let Some((off, _)) =
4055 self.glyph_color.upload(&self.device, &self.queue, &[inst])
4056 {
4057 current_pass.cmds.push(Cmd::ImageRgba {
4058 off,
4059 cnt: 1,
4060 handle: *handle,
4061 });
4062 }
4063 }
4064 }
4065 SceneNode::PushClip { rect, radius, op } => {
4066 flush_batch!(); let is_diff = matches!(op, repose_core::ClipOp::Difference);
4069
4070 let t_identity = Transform::identity();
4071 let current_transform = transform_stack.last().unwrap_or(&t_identity);
4072 let transformed = current_transform.apply_to_rect(*rect);
4073
4074 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
4075 let next_scissor = if is_diff {
4076 top
4077 } else {
4078 intersect(top, transformed)
4079 };
4080 scissor_stack.push(next_scissor);
4081 let scissor = to_scissor(
4082 &next_scissor,
4083 current_target_size.0 as u32,
4084 current_target_size.1 as u32,
4085 );
4086
4087 let clip_ndc_tl = to_ndc(
4088 transformed.x,
4089 transformed.y,
4090 transformed.w,
4091 transformed.h,
4092 current_target_size.0,
4093 current_target_size.1,
4094 );
4095 let inst = ClipInstance {
4096 xywh: [
4097 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
4098 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
4099 clip_ndc_tl[2],
4100 clip_ndc_tl[3],
4101 ],
4102 radii: *radius,
4103 sin_cos: [1.0, 0.0],
4104 };
4105 let bytes = bytemuck::bytes_of(&inst);
4106 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
4107 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
4108
4109 let rounded = radius.iter().any(|&r| r > 0.5);
4110
4111 current_pass.cmds.push(Cmd::ClipPush {
4112 off,
4113 cnt: 1,
4114 scissor,
4115 difference: is_diff,
4116 rounded,
4117 });
4118 }
4119 SceneNode::PopClip => {
4120 flush_batch!();
4121
4122 if !scissor_stack.is_empty() {
4123 scissor_stack.pop();
4124 } else {
4125 log::warn!("PopClip with empty stack");
4126 }
4127
4128 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
4129 let scissor = to_scissor(
4130 &top,
4131 current_target_size.0 as u32,
4132 current_target_size.1 as u32,
4133 );
4134 current_pass.cmds.push(Cmd::ClipPop { scissor });
4135 }
4136 SceneNode::Shadow {
4137 rect,
4138 radius,
4139 elevation: _,
4140 color,
4141 } => {
4142 flush_if_prim_changed!("rect", &self.rects);
4143 let (ndc, sin_cos) = rect_to_instance_ndc(
4144 *rect,
4145 current_transform,
4146 current_target_size.0,
4147 current_target_size.1,
4148 );
4149 let (brush_type, color0, _color1, _grad_start, _grad_end) =
4150 brush_to_instance_fields(&Brush::Solid(*color));
4151 batch.rects.push(RectInstance {
4152 xywh: ndc,
4153 radii: *radius,
4154 brush_type,
4155 _pad: [0.0; 3],
4156 color0,
4157 color1: [0.0; 4],
4158 grad_start: [0.0; 2],
4159 grad_end: [0.0; 2],
4160 sin_cos,
4161 });
4162 }
4163 SceneNode::PushTransform { transform } => {
4164 flush_batch!(); let combined = current_transform.combine(transform);
4166 transform_stack.push(combined);
4167 }
4168 SceneNode::PopTransform => {
4169 flush_batch!(); transform_stack.pop();
4171 }
4172 SceneNode::BeginLayer {
4173 rect,
4174 layer_id,
4175 alpha,
4176 blur_radius_x,
4177 blur_radius_y,
4178 rectangle_edge: _,
4179 } => {
4180 flush_batch!();
4181 let w = (rect.w.max(1.0)).ceil() as u32;
4183 let h = (rect.h.max(1.0)).ceil() as u32;
4184 let prev_target = current_pass.target;
4186 let prev_scissor = current_pass.initial_scissor;
4187 let saved = std::mem::replace(
4188 &mut current_pass,
4189 Pass {
4190 target: PassTarget::Layer(*layer_id),
4191 initial_scissor: (0, 0, w, h),
4192 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
4193 cmds: Vec::new(),
4194 },
4195 );
4196 passes.push(saved);
4197 target_stack.push(prev_target);
4198 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
4202 current_target_size = (w as f32, h as f32);
4203 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
4204 if *blur_radius_x > 0.0 || *blur_radius_y > 0.0 {
4206 layer_blurs.push((*layer_id, *blur_radius_x, *blur_radius_y));
4207 }
4208 }
4209 SceneNode::EndLayer { layer_id } => {
4210 flush_batch!();
4211 let saved = std::mem::replace(
4213 &mut current_pass,
4214 Pass {
4215 target: target_stack.pop().unwrap_or(PassTarget::Surface),
4216 initial_scissor: (0, 0, self.output_width, self.output_height),
4217 clear_color: None, cmds: Vec::new(),
4219 },
4220 );
4221 passes.push(saved);
4222 current_target_size = (fb_w, fb_h);
4223 if let Some((_, layer_alpha, _)) = layer_alphas
4225 .iter()
4226 .find(|(id, _, _)| id == layer_id)
4227 .copied()
4228 {
4229 let layer = self.layer_pool.get(layer_id).expect("layer target");
4230 let ndc_tl = to_ndc(
4231 layer.rect_px.0,
4232 layer.rect_px.1,
4233 layer.rect_px.2,
4234 layer.rect_px.3,
4235 fb_w,
4236 fb_h,
4237 );
4238 let blur_px_val = layer_blurs
4240 .iter()
4241 .find(|(id, _, _)| id == layer_id)
4242 .map(|(_, bx, by)| (*bx, *by));
4243 if let Some((blur_x, blur_y)) =
4244 blur_px_val.filter(|(bx, by)| *bx > 0.0 || *by > 0.0)
4245 {
4246 let bw_uv = (blur_x * 1.5) / layer.width.max(1) as f32;
4248 let bh_uv = (blur_y * 1.5) / layer.height.max(1) as f32;
4249 let inst = BlurInstance {
4250 xywh: [
4251 ndc_tl[0] + ndc_tl[2] * 0.5,
4252 ndc_tl[1] + ndc_tl[3] * 0.5,
4253 ndc_tl[2],
4254 ndc_tl[3],
4255 ],
4256 uv: [0.0, 0.0, 1.0, 1.0],
4257 color: [1.0, 1.0, 1.0, layer_alpha],
4258 blur_uv: [bw_uv, bh_uv],
4259 sin_cos: [1.0, 0.0],
4260 };
4261 self.blur_ring.grow_to_fit(
4262 &self.device,
4263 std::mem::size_of::<BlurInstance>() as u64,
4264 );
4265 let bytes = bytemuck::bytes_of(&inst);
4266 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
4267 current_pass.cmds.push(Cmd::CompositeBlur {
4268 off,
4269 cnt: 1,
4270 layer_id: *layer_id,
4271 });
4272 } else {
4273 let inst = GlyphInstance {
4275 xywh: [
4276 ndc_tl[0] + ndc_tl[2] * 0.5,
4277 ndc_tl[1] + ndc_tl[3] * 0.5,
4278 ndc_tl[2],
4279 ndc_tl[3],
4280 ],
4281 uv: [0.0, 1.0, 1.0, 0.0],
4282 color: [1.0, 1.0, 1.0, layer_alpha],
4283 sin_cos: [1.0, 0.0],
4284 };
4285 if let Some((off, cnt)) =
4286 self.glyph_color.upload(&self.device, &self.queue, &[inst])
4287 {
4288 current_pass.cmds.push(Cmd::CompositeLayer {
4289 off,
4290 cnt,
4291 layer_id: *layer_id,
4292 alpha: layer_alpha,
4293 });
4294 }
4295 }
4296 }
4297 }
4298 SceneNode::CompositeShadow {
4299 layer_id,
4300 blur_px,
4301 offset_px,
4302 color,
4303 } => {
4304 flush_batch!();
4305 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
4306 let sx = layer.rect_px.0 + offset_px.0;
4308 let sy = layer.rect_px.1 + offset_px.1;
4309 let sw = layer.rect_px.2;
4310 let sh = layer.rect_px.3;
4311 let bw_uv = (blur_px * 1.5) / layer.width.max(1) as f32;
4314 let bh_uv = (blur_px * 1.5) / layer.height.max(1) as f32;
4315 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
4316 let inst = BlurInstance {
4317 xywh: [
4318 ndc_tl[0] + ndc_tl[2] * 0.5,
4319 ndc_tl[1] + ndc_tl[3] * 0.5,
4320 ndc_tl[2],
4321 ndc_tl[3],
4322 ],
4323 uv: [0.0, 0.0, 1.0, 1.0],
4324 color: [
4325 color.0 as f32 / 255.0,
4326 color.1 as f32 / 255.0,
4327 color.2 as f32 / 255.0,
4328 color.3 as f32 / 255.0,
4329 ],
4330 blur_uv: [bw_uv, bh_uv],
4331 sin_cos: [1.0, 0.0],
4332 };
4333 self.blur_ring
4334 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
4335 let bytes = bytemuck::bytes_of(&inst);
4336 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
4337 current_pass.cmds.push(Cmd::CompositeShadow {
4338 off,
4339 cnt: 1,
4340 layer_id: *layer_id,
4341 });
4342 }
4343 }
4344 _ => {}
4345 }
4346 }
4347
4348 flush_batch!();
4349
4350 passes.push(current_pass);
4352
4353 let bind_mask = self.atlas_bind_group_mask();
4354 let bind_color = self.atlas_bind_group_color();
4355 let mut clip_depth: u32 = 0;
4356
4357 for pass in std::mem::take(&mut passes) {
4358 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
4359 PassTarget::Surface => {
4360 let swap_view = target_view.clone();
4361 let use_ws = self.working_space && self.ws_view.is_some();
4362 let (color, resolve) = if use_ws {
4363 let ws_view = self.ws_view.as_ref().unwrap();
4364 if let Some(msaa_view) = &self.msaa_view {
4365 (msaa_view.clone(), Some(ws_view.clone()))
4367 } else {
4368 (ws_view.clone(), None)
4370 }
4371 } else if let Some(msaa_view) = &self.msaa_view {
4372 (msaa_view.clone(), Some(swap_view))
4373 } else {
4374 (swap_view, None)
4375 };
4376 (color, resolve, self.depth_stencil_view.clone(), false)
4377 }
4378 PassTarget::Layer(layer_id) => {
4379 if let Some(lt) = self.layer_pool.get(&layer_id) {
4380 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
4381 } else {
4382 log::warn!("missing layer target {layer_id}");
4383 continue;
4384 }
4385 }
4386 };
4387
4388 let (target_w, target_h) = match pass.target {
4389 PassTarget::Surface => (fb_w, fb_h),
4390 PassTarget::Layer(layer_id) => {
4391 let lt = self.layer_pool.get(&layer_id);
4392 (
4393 lt.map_or(fb_w, |l| l.width as f32),
4394 lt.map_or(fb_h, |l| l.height as f32),
4395 )
4396 }
4397 };
4398 let globals = Globals {
4399 ndc_to_px: [target_w * 0.5, target_h * 0.5],
4400 _pad: [0.0, 0.0],
4401 };
4402 self.queue
4403 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
4404
4405 if is_layer {
4406 clip_depth = 0;
4407 }
4408
4409 let pipes: &Pipelines = if is_layer {
4410 &self.layer_pipes
4411 } else {
4412 &self.surface_pipes
4413 };
4414
4415 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
4416 label: Some("pass"),
4417 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
4418 view: &color_view,
4419 resolve_target: resolve_target.as_ref(),
4420 ops: wgpu::Operations {
4421 load: match pass.clear_color {
4422 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
4423 r: c[0] as f64,
4424 g: c[1] as f64,
4425 b: c[2] as f64,
4426 a: c[3] as f64,
4427 }),
4428 None => wgpu::LoadOp::Load,
4429 },
4430 store: wgpu::StoreOp::Store,
4431 },
4432 depth_slice: None,
4433 })],
4434 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
4435 view: &depth_stencil_view,
4436 depth_ops: None,
4437 stencil_ops: Some(wgpu::Operations {
4438 load: if is_layer || pass.clear_color.is_some() {
4439 wgpu::LoadOp::Clear(0)
4440 } else {
4441 wgpu::LoadOp::Load
4442 },
4443 store: wgpu::StoreOp::Store,
4444 }),
4445 }),
4446 timestamp_writes: None,
4447 occlusion_query_set: None,
4448 multiview_mask: None,
4449 });
4450
4451 rpass.set_bind_group(0, &self.globals_bind, &[]);
4452 rpass.set_stencil_reference(clip_depth);
4453 rpass.set_scissor_rect(
4454 pass.initial_scissor.0,
4455 pass.initial_scissor.1,
4456 pass.initial_scissor.2,
4457 pass.initial_scissor.3,
4458 );
4459
4460 macro_rules! draw_simple {
4461 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
4462 rpass.set_pipeline($pipeline);
4463 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
4464 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
4465 rpass.draw(0..6, 0..$n);
4466 }};
4467 }
4468
4469 macro_rules! draw_with_bind {
4470 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
4471 rpass.set_pipeline($pipeline);
4472 rpass.set_bind_group(1, $bind, &[]);
4473 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
4474 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
4475 rpass.draw(0..6, 0..$n);
4476 }};
4477 }
4478
4479 for cmd in pass.cmds {
4480 match cmd {
4481 Cmd::ClipPush {
4482 off,
4483 cnt: n,
4484 scissor,
4485 difference,
4486 rounded,
4487 } => {
4488 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
4489 rpass.set_stencil_reference(clip_depth);
4490
4491 if difference {
4492 rpass.set_pipeline(&pipes.clip_dec);
4493 } else if self.msaa_samples > 1 && !is_layer && rounded {
4494 rpass.set_pipeline(&pipes.clip_a2c);
4495 } else {
4496 rpass.set_pipeline(&pipes.clip_bin);
4497 }
4498
4499 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
4500 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
4501 rpass.draw(0..6, 0..n);
4502
4503 if !difference {
4504 clip_depth = (clip_depth + 1).min(255);
4505 rpass.set_stencil_reference(clip_depth);
4506 }
4507 }
4508
4509 Cmd::ClipPop { scissor } => {
4510 clip_depth = clip_depth.saturating_sub(1);
4511 rpass.set_stencil_reference(clip_depth);
4512 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
4513 }
4514
4515 Cmd::Rect { off, cnt: n } => {
4516 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
4517 }
4518
4519 Cmd::Border { off, cnt: n } => {
4520 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
4521 }
4522
4523 Cmd::GlyphsMask { off, cnt: n } => {
4524 draw_with_bind!(
4525 &pipes.text_mask,
4526 self.glyph_mask.ring,
4527 GlyphInstance,
4528 &bind_mask,
4529 off,
4530 n
4531 );
4532 }
4533
4534 Cmd::GlyphsColor { off, cnt: n } => {
4535 draw_with_bind!(
4536 &pipes.text_color,
4537 self.glyph_color.ring,
4538 GlyphInstance,
4539 &bind_color,
4540 off,
4541 n
4542 );
4543 }
4544
4545 Cmd::GlyphsVector { off, cnt: n } => {
4546 if let Some(ref slug_pipe) = pipes.slug.as_ref() {
4547 rpass.set_pipeline(slug_pipe);
4548 let bytes = (n as u64) * std::mem::size_of::<slug::TessVertex>() as u64;
4549 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
4550 rpass.draw(0..n, 0..1);
4551 }
4552 }
4553
4554 Cmd::ImageRgba {
4555 off,
4556 cnt: n,
4557 handle,
4558 } => {
4559 if let Some(ImageTex::Rgba { bind, .. }) = self.images.get(&handle) {
4560 draw_with_bind!(
4561 &pipes.image_rgba,
4562 self.glyph_color.ring,
4563 GlyphInstance,
4564 bind,
4565 off,
4566 n
4567 );
4568 }
4569 }
4570
4571 Cmd::ImageNv12 {
4572 off,
4573 cnt: n,
4574 handle,
4575 } => {
4576 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
4577 draw_with_bind!(
4578 &pipes.image_nv12,
4579 self.nv12.ring,
4580 Nv12Instance,
4581 bind,
4582 off,
4583 n
4584 );
4585 }
4586 }
4587
4588 Cmd::Ellipse { off, cnt: n } => {
4589 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
4590 }
4591
4592 Cmd::EllipseBorder { off, cnt: n } => {
4593 draw_simple!(
4594 &pipes.ellipse_borders,
4595 self.ellipse_borders.ring,
4596 EllipseBorderInstance,
4597 off,
4598 n
4599 );
4600 }
4601
4602 Cmd::Arc { off, cnt: n } => {
4603 draw_simple!(&pipes.arcs, self.arcs.ring, ArcInstance, off, n);
4604 }
4605
4606 Cmd::PushTransform(_) => {}
4607 Cmd::PopTransform => {}
4608 Cmd::CompositeLayer {
4609 off,
4610 cnt: n,
4611 layer_id,
4612 alpha: _,
4613 } => {
4614 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
4615 draw_with_bind!(
4616 &pipes.image_rgba,
4617 self.glyph_color.ring,
4618 GlyphInstance,
4619 <.bind,
4620 off,
4621 n
4622 );
4623 }
4624 }
4625 Cmd::CompositeShadow {
4626 off,
4627 cnt: n,
4628 layer_id,
4629 } => {
4630 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
4631 draw_with_bind!(
4632 &pipes.blur,
4633 self.blur_ring,
4634 BlurInstance,
4635 <.bind,
4636 off,
4637 n
4638 );
4639 }
4640 }
4641 Cmd::CompositeBlur {
4642 off,
4643 cnt: n,
4644 layer_id,
4645 } => {
4646 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
4647 draw_with_bind!(
4648 &pipes.blur_content,
4649 self.blur_ring,
4650 BlurInstance,
4651 <.bind,
4652 off,
4653 n
4654 );
4655 }
4656 }
4657 }
4658 }
4659 }
4660
4661 if self.working_space {
4663 if let (Some(_ws_view), Some(ws_bind), Some(display_pipeline)) =
4664 (&self.ws_view, &self.ws_bind, &self.display_pipeline)
4665 {
4666 let swap_view = target_view.clone();
4667 let mut display_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
4668 label: Some("display transform"),
4669 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
4670 view: &swap_view,
4671 resolve_target: None,
4672 ops: wgpu::Operations {
4673 load: wgpu::LoadOp::Load,
4674 store: wgpu::StoreOp::Store,
4675 },
4676 depth_slice: None,
4677 })],
4678 depth_stencil_attachment: None,
4679 timestamp_writes: None,
4680 occlusion_query_set: None,
4681 multiview_mask: None,
4682 });
4683 display_pass.set_pipeline(display_pipeline);
4684 display_pass.set_bind_group(1, ws_bind, &[]);
4685 display_pass.draw(0..3, 0..1);
4686 }
4687 }
4688
4689
4690 self.evict_unused_images();
4692 }
4693
4694 pub fn render_to_view(
4698 &mut self,
4699 scene: &Scene,
4700 encoder: &mut wgpu::CommandEncoder,
4701 target_view: &wgpu::TextureView,
4702 width: u32,
4703 height: u32,
4704 clear_color: Option<[f64; 4]>,
4705 ) {
4706 self.resize(width, height);
4707
4708 self.frame_index = self.frame_index.wrapping_add(1);
4709 self.slug_cache.next_frame();
4710
4711 if width == 0 || height == 0 {
4712 return;
4713 }
4714
4715 self.render_scene_to_encoder(scene, encoder, target_view, clear_color);
4716 }
4717}
4718
4719
4720fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
4721 let x0 = a.x.max(b.x);
4722 let y0 = a.y.max(b.y);
4723 let x1 = (a.x + a.w).min(b.x + b.w);
4724 let y1 = (a.y + a.h).min(b.y + b.h);
4725 repose_core::Rect {
4726 x: x0,
4727 y: y0,
4728 w: (x1 - x0).max(0.0),
4729 h: (y1 - y0).max(0.0),
4730 }
4731}