1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::num::NonZero;
4#[cfg(feature = "winit-surface")]
5use std::panic::{AssertUnwindSafe, catch_unwind};
6use std::sync::Arc;
7
8use repose_core::color::{ChromaSiting, ColorInfo, PixelFormat};
9use repose_core::request_frame;
10use repose_core::{
11 Brush, FontStyle, GlyphRasterConfig, PresentModePref, RenderBackend, Scene, SceneNode,
12 StrokeCap, Transform,
13};
14use wgpu::Instance;
15
16mod slug;
17
18mod commands;
19pub use commands::apply_render_commands;
20
21pub mod offscreen;
22
23mod callback;
24pub use callback::{Callback, CallbackResources, ScreenDescriptor, WgpuCallback};
25
26#[derive(Clone)]
27struct UploadRing {
28 buf: wgpu::Buffer,
29 cap: u64,
30 head: u64,
31 usage: wgpu::BufferUsages,
32}
33
34impl UploadRing {
35 fn new(device: &wgpu::Device, label: &str, cap: u64, usage: wgpu::BufferUsages) -> Self {
36 let buf = device.create_buffer(&wgpu::BufferDescriptor {
37 label: Some(label),
38 size: cap,
39 usage,
40 mapped_at_creation: false,
41 });
42 Self {
43 buf,
44 cap,
45 head: 0,
46 usage,
47 }
48 }
49
50 fn reset(&mut self) {
51 self.head = 0;
52 }
53
54 fn grow_to_fit(&mut self, device: &wgpu::Device, needed: u64) {
55 let start = (self.head + 3) & !3;
56 let aligned_needed = (needed + 3) & !3;
57 if start + needed <= self.cap {
59 return;
60 }
61 let required = start + needed;
62 let mut new_cap = required.next_power_of_two().max(self.cap * 2).max(256);
63 new_cap = (new_cap + 3) & !3;
64 if new_cap < aligned_needed {
65 new_cap = aligned_needed.next_power_of_two();
66 }
67 self.buf = device.create_buffer(&wgpu::BufferDescriptor {
68 label: Some("upload ring (grown)"),
69 size: new_cap,
70 usage: self.usage,
71 mapped_at_creation: false,
72 });
73 self.cap = new_cap;
74 if start + needed > self.cap {
75 self.head = 0;
76 }
77 }
78
79 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> (u64, u64) {
80 let len = bytes.len() as u64;
81 let start = (self.head + 3) & !3; let end = start + len;
83 if end > self.cap {
84 log::error!(
86 "UploadRing overflow: start={start} len={len} cap={} - growing",
87 self.cap
88 );
89 if len > self.cap {
90 return (0, 0);
93 }
94 let wrapped_start = 0;
96 let wrapped_end = len;
97 if wrapped_end <= self.cap {
98 queue.write_buffer(&self.buf, wrapped_start, bytes);
99 self.head = wrapped_end;
100 return (wrapped_start, len);
101 }
102 return (0, 0);
103 }
104 queue.write_buffer(&self.buf, start, bytes);
105 self.head = end;
106 (start, len)
107 }
108}
109
110struct InstancedPipe<I: bytemuck::Pod> {
111 ring: UploadRing,
112 _marker: std::marker::PhantomData<I>,
113}
114
115impl<I: bytemuck::Pod> InstancedPipe<I> {
116 fn new(ring: UploadRing) -> Self {
117 Self {
118 ring,
119 _marker: std::marker::PhantomData,
120 }
121 }
122
123 fn upload(
124 &mut self,
125 device: &wgpu::Device,
126 queue: &wgpu::Queue,
127 data: &[I],
128 ) -> Option<(u64, u32)> {
129 if data.is_empty() {
130 return None;
131 }
132 let bytes = bytemuck::cast_slice(data);
133 self.ring.grow_to_fit(device, bytes.len() as u64);
134 let (off, wrote) = self.ring.alloc_write(queue, bytes);
135 debug_assert_eq!(wrote as usize, bytes.len());
136 Some((off, data.len() as u32))
137 }
138
139 fn reset(&mut self) {
140 self.ring.reset();
141 }
142}
143
144#[repr(C)]
145#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
146struct Globals {
147 ndc_to_px: [f32; 2],
148 _pad: [f32; 2],
149}
150
151fn make_globals(target_w: f32, target_h: f32) -> Globals {
152 Globals {
153 ndc_to_px: [target_w * 0.5, target_h * 0.5],
154 _pad: [0.0, 0.0],
155 }
156}
157
158pub struct WgpuSceneRenderer {
159 pub device: wgpu::Device,
160 pub queue: wgpu::Queue,
161 pub output_format: wgpu::TextureFormat,
162 pub output_width: u32,
163 pub output_height: u32,
164 pub pixels_per_point: f32,
166
167 surface_pipes: Pipelines,
170 layer_pipes: Pipelines,
171
172 rects: InstancedPipe<RectInstance>,
174 borders: InstancedPipe<BorderInstance>,
175 ellipses: InstancedPipe<EllipseInstance>,
176 ellipse_borders: InstancedPipe<EllipseBorderInstance>,
177 arcs: InstancedPipe<ArcInstance>,
178 glyph_mask: InstancedPipe<GlyphInstance>,
179 glyph_color: InstancedPipe<GlyphInstance>,
180
181 image_bind_layout_rgba: wgpu::BindGroupLayout,
183 image_bind_layout_nv12: wgpu::BindGroupLayout,
184 image_sampler: wgpu::Sampler,
185 layer_sampler: wgpu::Sampler,
186 layer_sampler_linear: wgpu::Sampler,
187
188 blur_ring: UploadRing,
190
191 text_bind_layout: wgpu::BindGroupLayout,
192
193 clip_ring: UploadRing,
195
196 slug_enabled: bool,
198 slug_ring: UploadRing,
199 slug_cache: slug::GlyphSlugCache,
200
201 nv12: InstancedPipe<Nv12Instance>,
203
204 mesh_verts: UploadRing,
206 mesh_indices: UploadRing,
207 mesh_uniform_buf: wgpu::Buffer,
208 mesh_bind_layout: wgpu::BindGroupLayout,
209 mesh_bind: wgpu::BindGroup,
210 mesh_uniform_head: u64,
211 mesh_clip_stack: Vec<(u64, u32, u64, u32, u64)>,
215
216 msaa_samples: u32,
217
218 depth_stencil_tex: wgpu::Texture,
220 depth_stencil_view: wgpu::TextureView,
221
222 msaa_tex: Option<wgpu::Texture>,
224 msaa_view: Option<wgpu::TextureView>,
225
226 globals_buf: wgpu::Buffer,
227 globals_bind: wgpu::BindGroup,
228
229 atlas_mask: AtlasA8,
231 atlas_color: AtlasRGBA,
232
233 next_image_handle: u64,
235 images: HashMap<u64, ImageTex>,
236 retained: HashMap<u64, RetainedImage>,
237
238 frame_index: u64,
240 image_bytes_total: u64,
241 image_evict_after_frames: u64,
242 image_budget_bytes: u64,
243
244 layer_pool: HashMap<u32, LayerTarget>,
247
248 working_space: bool,
252 ws_tex: Option<wgpu::Texture>,
253 ws_view: Option<wgpu::TextureView>,
254 ws_bind: Option<wgpu::BindGroup>,
255 display_pipeline: Option<wgpu::RenderPipeline>,
256 display_layout: Option<wgpu::BindGroupLayout>,
257
258 pub callback_resources: CallbackResources,
259}
260
261pub struct WgpuSurfaceBackend {
262 pub surface: Option<wgpu::Surface<'static>>,
263 pub surface_config: Option<wgpu::SurfaceConfiguration>,
264 pub renderer: WgpuSceneRenderer,
265}
266
267impl std::ops::Deref for WgpuSurfaceBackend {
268 type Target = WgpuSceneRenderer;
269 fn deref(&self) -> &Self::Target {
270 &self.renderer
271 }
272}
273impl std::ops::DerefMut for WgpuSurfaceBackend {
274 fn deref_mut(&mut self) -> &mut Self::Target {
275 &mut self.renderer
276 }
277}
278
279#[cfg(feature = "winit-surface")]
280pub type WgpuBackend = WgpuSurfaceBackend;
281
282impl Drop for WgpuSceneRenderer {
283 fn drop(&mut self) {
284 let _ = self.device.poll(wgpu::PollType::Poll);
285 #[cfg(not(target_arch = "wasm32"))]
286 {
287 let _ = self.device.poll(wgpu::PollType::Wait {
288 submission_index: None,
289 timeout: Some(std::time::Duration::from_millis(100)),
290 });
291 }
292 }
293}
294
295#[derive(Clone)]
296struct LayerTarget {
297 view: wgpu::TextureView,
298 bind: wgpu::BindGroup,
299 bind_linear: wgpu::BindGroup,
300 depth_stencil_view: wgpu::TextureView,
301 width: u32,
302 height: u32,
303 rect_px: (f32, f32, f32, f32),
304}
305
306#[derive(Clone, Copy)]
308enum PassTarget {
309 Surface,
310 Layer(u32),
311}
312
313struct Pipelines {
318 rects: wgpu::RenderPipeline,
319 borders: wgpu::RenderPipeline,
320 ellipses: wgpu::RenderPipeline,
321 ellipse_borders: wgpu::RenderPipeline,
322 arcs: wgpu::RenderPipeline,
323 text_mask: wgpu::RenderPipeline,
324 text_color: wgpu::RenderPipeline,
325 image_rgba: wgpu::RenderPipeline,
326 image_nv12: wgpu::RenderPipeline,
327 blur: wgpu::RenderPipeline,
328 blur_content: wgpu::RenderPipeline,
329 clip_a2c: wgpu::RenderPipeline,
330 clip_bin: wgpu::RenderPipeline,
331 clip_dec: wgpu::RenderPipeline,
332 slug: Option<wgpu::RenderPipeline>,
333 mesh: wgpu::RenderPipeline,
337 mesh_overlay: wgpu::RenderPipeline,
340 mesh_clip_inc: wgpu::RenderPipeline,
342 mesh_clip_dec: wgpu::RenderPipeline,
344}
345
346impl Pipelines {
347 fn create(
348 device: &wgpu::Device,
349 format: wgpu::TextureFormat,
350 sample_count: u32,
351 globals_layout: &wgpu::BindGroupLayout,
352 text_bind_layout: &wgpu::BindGroupLayout,
353 image_bind_layout_nv12: &wgpu::BindGroupLayout,
354 clip_pipeline_layout: &wgpu::PipelineLayout,
355 stencil_for_content: &wgpu::DepthStencilState,
356 stencil_for_clip_inc: &wgpu::DepthStencilState,
357 stencil_for_clip_dec: &wgpu::DepthStencilState,
358 clip_color_target: &wgpu::ColorTargetState,
359 clip_vertex_layout: &wgpu::VertexBufferLayout,
360 mesh_bind_layout: &wgpu::BindGroupLayout,
361 ) -> Self {
362 let msaa_state = wgpu::MultisampleState {
363 count: sample_count,
364 mask: !0,
365 alpha_to_coverage_enabled: false,
366 };
367
368 macro_rules! make_content_pipeline {
369 ($name:ident, $shader:literal, $inst_type:ty, $attrs:expr) => {
370 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
371 label: Some(concat!($shader, ".wgsl")),
372 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(concat!(
373 "shaders/", $shader, ".wgsl"
374 )))),
375 });
376 let pipeline_layout =
377 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
378 label: Some(concat!($shader, " pipeline layout")),
379 bind_group_layouts: &[Some(globals_layout)],
380 immediate_size: 0,
381 });
382 let $name = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
383 label: Some(concat!($shader, " pipeline")),
384 layout: Some(&pipeline_layout),
385 vertex: wgpu::VertexState {
386 module: &shader_module,
387 entry_point: Some("vs_main"),
388 buffers: &[Some(wgpu::VertexBufferLayout {
389 array_stride: std::mem::size_of::<$inst_type>() as u64,
390 step_mode: wgpu::VertexStepMode::Instance,
391 attributes: $attrs,
392 })],
393 compilation_options: wgpu::PipelineCompilationOptions::default(),
394 },
395 fragment: Some(wgpu::FragmentState {
396 module: &shader_module,
397 entry_point: Some("fs_main"),
398 targets: &[Some(wgpu::ColorTargetState {
399 format,
400 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
401 write_mask: wgpu::ColorWrites::ALL,
402 })],
403 compilation_options: wgpu::PipelineCompilationOptions::default(),
404 }),
405 primitive: wgpu::PrimitiveState::default(),
406 depth_stencil: Some(stencil_for_content.clone()),
407 multisample: msaa_state,
408 multiview_mask: None,
409 cache: None,
410 });
411 };
412 }
413
414 let rect_attrs: &[wgpu::VertexAttribute] = &[
415 wgpu::VertexAttribute {
416 shader_location: 0,
417 offset: 0,
418 format: wgpu::VertexFormat::Float32x4,
419 },
420 wgpu::VertexAttribute {
421 shader_location: 1,
422 offset: 16,
423 format: wgpu::VertexFormat::Float32x4,
424 },
425 wgpu::VertexAttribute {
426 shader_location: 2,
427 offset: 32,
428 format: wgpu::VertexFormat::Uint32,
429 },
430 wgpu::VertexAttribute {
431 shader_location: 3,
432 offset: 48,
433 format: wgpu::VertexFormat::Float32x4,
434 },
435 wgpu::VertexAttribute {
436 shader_location: 4,
437 offset: 64,
438 format: wgpu::VertexFormat::Float32x4,
439 },
440 wgpu::VertexAttribute {
441 shader_location: 5,
442 offset: 80,
443 format: wgpu::VertexFormat::Float32x2,
444 },
445 wgpu::VertexAttribute {
446 shader_location: 6,
447 offset: 88,
448 format: wgpu::VertexFormat::Float32x2,
449 },
450 wgpu::VertexAttribute {
451 shader_location: 7,
452 offset: 96,
453 format: wgpu::VertexFormat::Float32x2,
454 },
455 ];
456 let border_attrs: &[wgpu::VertexAttribute] = &[
457 wgpu::VertexAttribute {
458 shader_location: 0,
459 offset: 0,
460 format: wgpu::VertexFormat::Float32x4,
461 },
462 wgpu::VertexAttribute {
463 shader_location: 1,
464 offset: 16,
465 format: wgpu::VertexFormat::Float32x4,
466 },
467 wgpu::VertexAttribute {
468 shader_location: 2,
469 offset: 32,
470 format: wgpu::VertexFormat::Float32,
471 },
472 wgpu::VertexAttribute {
473 shader_location: 3,
474 offset: 36,
475 format: wgpu::VertexFormat::Float32x4,
476 },
477 wgpu::VertexAttribute {
478 shader_location: 4,
479 offset: 52,
480 format: wgpu::VertexFormat::Float32x2,
481 },
482 ];
483 let ellipse_attrs: &[wgpu::VertexAttribute] = &[
484 wgpu::VertexAttribute {
485 shader_location: 0,
486 offset: 0,
487 format: wgpu::VertexFormat::Float32x4,
488 },
489 wgpu::VertexAttribute {
490 shader_location: 1,
491 offset: 16,
492 format: wgpu::VertexFormat::Float32x4,
493 },
494 wgpu::VertexAttribute {
495 shader_location: 2,
496 offset: 32,
497 format: wgpu::VertexFormat::Float32x2,
498 },
499 ];
500 let ellipse_border_attrs: &[wgpu::VertexAttribute] = &[
501 wgpu::VertexAttribute {
502 shader_location: 0,
503 offset: 0,
504 format: wgpu::VertexFormat::Float32x4,
505 },
506 wgpu::VertexAttribute {
507 shader_location: 1,
508 offset: 16,
509 format: wgpu::VertexFormat::Float32,
510 },
511 wgpu::VertexAttribute {
512 shader_location: 2,
513 offset: 20,
514 format: wgpu::VertexFormat::Float32,
515 },
516 wgpu::VertexAttribute {
517 shader_location: 3,
518 offset: 24,
519 format: wgpu::VertexFormat::Float32x4,
520 },
521 wgpu::VertexAttribute {
522 shader_location: 4,
523 offset: 40,
524 format: wgpu::VertexFormat::Float32x2,
525 },
526 ];
527
528 make_content_pipeline!(rects, "rect", RectInstance, rect_attrs);
529 make_content_pipeline!(borders, "border", BorderInstance, border_attrs);
530 make_content_pipeline!(ellipses, "ellipse", EllipseInstance, ellipse_attrs);
531 make_content_pipeline!(
532 ellipse_borders,
533 "ellipse_border",
534 EllipseBorderInstance,
535 ellipse_border_attrs
536 );
537
538 let arc_attrs: &[wgpu::VertexAttribute] = &[
539 wgpu::VertexAttribute {
540 shader_location: 0,
541 offset: 0,
542 format: wgpu::VertexFormat::Float32x4,
543 },
544 wgpu::VertexAttribute {
545 shader_location: 1,
546 offset: 16,
547 format: wgpu::VertexFormat::Float32,
548 },
549 wgpu::VertexAttribute {
550 shader_location: 2,
551 offset: 20,
552 format: wgpu::VertexFormat::Float32,
553 },
554 wgpu::VertexAttribute {
555 shader_location: 3,
556 offset: 24,
557 format: wgpu::VertexFormat::Float32,
558 },
559 wgpu::VertexAttribute {
560 shader_location: 4,
561 offset: 28,
562 format: wgpu::VertexFormat::Float32,
563 },
564 wgpu::VertexAttribute {
565 shader_location: 5,
566 offset: 32,
567 format: wgpu::VertexFormat::Float32x4,
568 },
569 wgpu::VertexAttribute {
570 shader_location: 6,
571 offset: 48,
572 format: wgpu::VertexFormat::Float32x2,
573 },
574 wgpu::VertexAttribute {
575 shader_location: 7,
576 offset: 56,
577 format: wgpu::VertexFormat::Float32,
578 },
579 ];
580
581 make_content_pipeline!(arcs, "arc", ArcInstance, arc_attrs);
582
583 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
585 label: Some("text.wgsl"),
586 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
587 });
588 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
590 label: Some("text_color.wgsl"),
591 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
592 "shaders/text_color.wgsl"
593 ))),
594 });
595 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
596 label: Some("text pipeline layout"),
597 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
598 immediate_size: 0,
599 });
600 let glyph_vertex = wgpu::VertexBufferLayout {
601 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
602 step_mode: wgpu::VertexStepMode::Instance,
603 attributes: &[
604 wgpu::VertexAttribute {
605 shader_location: 0,
606 offset: 0,
607 format: wgpu::VertexFormat::Float32x4,
608 },
609 wgpu::VertexAttribute {
610 shader_location: 1,
611 offset: 16,
612 format: wgpu::VertexFormat::Float32x4,
613 },
614 wgpu::VertexAttribute {
615 shader_location: 2,
616 offset: 32,
617 format: wgpu::VertexFormat::Float32x4,
618 },
619 wgpu::VertexAttribute {
620 shader_location: 3,
621 offset: 48,
622 format: wgpu::VertexFormat::Float32x2,
623 },
624 ],
625 };
626 let text_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
627 label: Some("text pipeline (mask)"),
628 layout: Some(&text_pipeline_layout),
629 vertex: wgpu::VertexState {
630 module: &text_mask_shader,
631 entry_point: Some("vs_main"),
632 buffers: &[Some(glyph_vertex.clone())],
633 compilation_options: wgpu::PipelineCompilationOptions::default(),
634 },
635 fragment: Some(wgpu::FragmentState {
636 module: &text_mask_shader,
637 entry_point: Some("fs_main"),
638 targets: &[Some(wgpu::ColorTargetState {
639 format,
640 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
641 write_mask: wgpu::ColorWrites::ALL,
642 })],
643 compilation_options: wgpu::PipelineCompilationOptions::default(),
644 }),
645 primitive: wgpu::PrimitiveState::default(),
646 depth_stencil: Some(stencil_for_content.clone()),
647 multisample: msaa_state,
648 multiview_mask: None,
649 cache: None,
650 });
651 let text_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
652 label: Some("text pipeline (color)"),
653 layout: Some(&text_pipeline_layout),
654 vertex: wgpu::VertexState {
655 module: &text_color_shader,
656 entry_point: Some("vs_main"),
657 buffers: &[Some(glyph_vertex)],
658 compilation_options: wgpu::PipelineCompilationOptions::default(),
659 },
660 fragment: Some(wgpu::FragmentState {
661 module: &text_color_shader,
662 entry_point: Some("fs_main"),
663 targets: &[Some(wgpu::ColorTargetState {
664 format,
665 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
666 write_mask: wgpu::ColorWrites::ALL,
667 })],
668 compilation_options: wgpu::PipelineCompilationOptions::default(),
669 }),
670 primitive: wgpu::PrimitiveState::default(),
671 depth_stencil: Some(stencil_for_content.clone()),
672 multisample: msaa_state,
673 multiview_mask: None,
674 cache: None,
675 });
676 let image_rgba = text_color.clone();
678
679 let blur_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
681 label: Some("blur_shadow.wgsl"),
682 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
683 "shaders/blur_shadow.wgsl"
684 ))),
685 });
686 let blur_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
687 label: Some("blur pipeline layout"),
688 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
689 immediate_size: 0,
690 });
691 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
692 label: Some("blur pipeline"),
693 layout: Some(&blur_pipeline_layout),
694 vertex: wgpu::VertexState {
695 module: &blur_shader,
696 entry_point: Some("vs_main"),
697 buffers: &[Some(wgpu::VertexBufferLayout {
698 array_stride: std::mem::size_of::<BlurInstance>() as u64,
699 step_mode: wgpu::VertexStepMode::Instance,
700 attributes: &[
701 wgpu::VertexAttribute {
702 shader_location: 0,
703 offset: 0,
704 format: wgpu::VertexFormat::Float32x4,
705 },
706 wgpu::VertexAttribute {
707 shader_location: 1,
708 offset: 16,
709 format: wgpu::VertexFormat::Float32x4,
710 },
711 wgpu::VertexAttribute {
712 shader_location: 2,
713 offset: 32,
714 format: wgpu::VertexFormat::Float32x4,
715 },
716 wgpu::VertexAttribute {
717 shader_location: 3,
718 offset: 48,
719 format: wgpu::VertexFormat::Float32x2,
720 },
721 wgpu::VertexAttribute {
722 shader_location: 4,
723 offset: 56,
724 format: wgpu::VertexFormat::Float32x2,
725 },
726 ],
727 })],
728 compilation_options: wgpu::PipelineCompilationOptions::default(),
729 },
730 fragment: Some(wgpu::FragmentState {
731 module: &blur_shader,
732 entry_point: Some("fs_main"),
733 targets: &[Some(wgpu::ColorTargetState {
734 format,
735 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
736 write_mask: wgpu::ColorWrites::ALL,
737 })],
738 compilation_options: wgpu::PipelineCompilationOptions::default(),
739 }),
740 primitive: wgpu::PrimitiveState::default(),
741 depth_stencil: Some(stencil_for_content.clone()),
742 multisample: msaa_state,
743 multiview_mask: None,
744 cache: None,
745 });
746
747 let blur_content_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
749 label: Some("blur_content.wgsl"),
750 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
751 "shaders/blur_content.wgsl"
752 ))),
753 });
754 let blur_content = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
755 label: Some("blur content pipeline"),
756 layout: Some(&blur_pipeline_layout),
757 vertex: wgpu::VertexState {
758 module: &blur_content_shader,
759 entry_point: Some("vs_main"),
760 buffers: &[Some(wgpu::VertexBufferLayout {
761 array_stride: std::mem::size_of::<BlurInstance>() as u64,
762 step_mode: wgpu::VertexStepMode::Instance,
763 attributes: &[
764 wgpu::VertexAttribute {
765 shader_location: 0,
766 offset: 0,
767 format: wgpu::VertexFormat::Float32x4,
768 },
769 wgpu::VertexAttribute {
770 shader_location: 1,
771 offset: 16,
772 format: wgpu::VertexFormat::Float32x4,
773 },
774 wgpu::VertexAttribute {
775 shader_location: 2,
776 offset: 32,
777 format: wgpu::VertexFormat::Float32x4,
778 },
779 wgpu::VertexAttribute {
780 shader_location: 3,
781 offset: 48,
782 format: wgpu::VertexFormat::Float32x2,
783 },
784 wgpu::VertexAttribute {
785 shader_location: 4,
786 offset: 56,
787 format: wgpu::VertexFormat::Float32x2,
788 },
789 ],
790 })],
791 compilation_options: wgpu::PipelineCompilationOptions::default(),
792 },
793 fragment: Some(wgpu::FragmentState {
794 module: &blur_content_shader,
795 entry_point: Some("fs_main"),
796 targets: &[Some(wgpu::ColorTargetState {
797 format,
798 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
799 write_mask: wgpu::ColorWrites::ALL,
800 })],
801 compilation_options: wgpu::PipelineCompilationOptions::default(),
802 }),
803 primitive: wgpu::PrimitiveState::default(),
804 depth_stencil: Some(stencil_for_content.clone()),
805 multisample: msaa_state,
806 multiview_mask: None,
807 cache: None,
808 });
809
810 let image_nv12_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
812 label: Some("image_nv12.wgsl"),
813 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
814 "shaders/image_nv12.wgsl"
815 ))),
816 });
817 let image_nv12_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
818 label: Some("image nv12 pipeline layout"),
819 bind_group_layouts: &[Some(globals_layout), Some(image_bind_layout_nv12)],
820 immediate_size: 0,
821 });
822 let image_nv12 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
823 label: Some("image nv12 pipeline"),
824 layout: Some(&image_nv12_layout),
825 vertex: wgpu::VertexState {
826 module: &image_nv12_shader,
827 entry_point: Some("vs_main"),
828 buffers: &[Some(wgpu::VertexBufferLayout {
829 array_stride: std::mem::size_of::<Nv12Instance>() as u64,
830 step_mode: wgpu::VertexStepMode::Instance,
831 attributes: &[
832 wgpu::VertexAttribute {
833 shader_location: 0,
834 offset: 0,
835 format: wgpu::VertexFormat::Float32x4,
836 },
837 wgpu::VertexAttribute {
838 shader_location: 1,
839 offset: 16,
840 format: wgpu::VertexFormat::Float32x4,
841 },
842 wgpu::VertexAttribute {
843 shader_location: 2,
844 offset: 32,
845 format: wgpu::VertexFormat::Float32x4,
846 },
847 wgpu::VertexAttribute {
848 shader_location: 3,
849 offset: 48,
850 format: wgpu::VertexFormat::Float32,
851 },
852 wgpu::VertexAttribute {
853 shader_location: 4,
854 offset: 52,
855 format: wgpu::VertexFormat::Float32x2,
856 },
857 ],
858 })],
859 compilation_options: wgpu::PipelineCompilationOptions::default(),
860 },
861 fragment: Some(wgpu::FragmentState {
862 module: &image_nv12_shader,
863 entry_point: Some("fs_main"),
864 targets: &[Some(wgpu::ColorTargetState {
865 format,
866 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
867 write_mask: wgpu::ColorWrites::ALL,
868 })],
869 compilation_options: wgpu::PipelineCompilationOptions::default(),
870 }),
871 primitive: wgpu::PrimitiveState::default(),
872 depth_stencil: Some(stencil_for_content.clone()),
873 multisample: msaa_state,
874 multiview_mask: None,
875 cache: None,
876 });
877
878 let clip_shader_a2c = device.create_shader_module(wgpu::ShaderModuleDescriptor {
880 label: Some("clip_round_rect_a2c.wgsl"),
881 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
882 "shaders/clip_round_rect_a2c.wgsl"
883 ))),
884 });
885 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
886 label: Some("clip_round_rect_bin.wgsl"),
887 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
888 "shaders/clip_round_rect_bin.wgsl"
889 ))),
890 });
891 let clip_a2c = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
892 label: Some("clip pipeline (a2c)"),
893 layout: Some(clip_pipeline_layout),
894 vertex: wgpu::VertexState {
895 module: &clip_shader_a2c,
896 entry_point: Some("vs_main"),
897 buffers: &[Some(clip_vertex_layout.clone())],
898 compilation_options: wgpu::PipelineCompilationOptions::default(),
899 },
900 fragment: Some(wgpu::FragmentState {
901 module: &clip_shader_a2c,
902 entry_point: Some("fs_main"),
903 targets: &[Some(clip_color_target.clone())],
904 compilation_options: wgpu::PipelineCompilationOptions::default(),
905 }),
906 primitive: wgpu::PrimitiveState::default(),
907 depth_stencil: Some(stencil_for_clip_inc.clone()),
908 multisample: wgpu::MultisampleState {
909 count: sample_count,
910 mask: !0,
911 alpha_to_coverage_enabled: sample_count > 1,
912 },
913 multiview_mask: None,
914 cache: None,
915 });
916 let clip_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
917 label: Some("clip pipeline (bin)"),
918 layout: Some(clip_pipeline_layout),
919 vertex: wgpu::VertexState {
920 module: &clip_shader_bin,
921 entry_point: Some("vs_main"),
922 buffers: &[Some(clip_vertex_layout.clone())],
923 compilation_options: wgpu::PipelineCompilationOptions::default(),
924 },
925 fragment: Some(wgpu::FragmentState {
926 module: &clip_shader_bin,
927 entry_point: Some("fs_main"),
928 targets: &[Some(clip_color_target.clone())],
929 compilation_options: wgpu::PipelineCompilationOptions::default(),
930 }),
931 primitive: wgpu::PrimitiveState::default(),
932 depth_stencil: Some(stencil_for_clip_inc.clone()),
933 multisample: wgpu::MultisampleState {
934 count: sample_count,
935 mask: !0,
936 alpha_to_coverage_enabled: false,
937 },
938 multiview_mask: None,
939 cache: None,
940 });
941 let clip_dec = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
942 label: Some("clip pipeline (dec)"),
943 layout: Some(clip_pipeline_layout),
944 vertex: wgpu::VertexState {
945 module: &clip_shader_bin,
946 entry_point: Some("vs_main"),
947 buffers: &[Some(clip_vertex_layout.clone())],
948 compilation_options: wgpu::PipelineCompilationOptions::default(),
949 },
950 fragment: Some(wgpu::FragmentState {
951 module: &clip_shader_bin,
952 entry_point: Some("fs_main"),
953 targets: &[Some(clip_color_target.clone())],
954 compilation_options: wgpu::PipelineCompilationOptions::default(),
955 }),
956 primitive: wgpu::PrimitiveState::default(),
957 depth_stencil: Some(stencil_for_clip_dec.clone()),
958 multisample: wgpu::MultisampleState {
959 count: sample_count,
960 mask: !0,
961 alpha_to_coverage_enabled: false,
962 },
963 multiview_mask: None,
964 cache: None,
965 });
966
967 let slug = Some(slug::create_pipeline(
968 device,
969 format,
970 sample_count,
971 stencil_for_content,
972 ));
973
974 let mesh_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
976 label: Some("mesh.wgsl"),
977 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/mesh.wgsl"))),
978 });
979 let mesh_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
980 label: Some("mesh pipeline layout"),
981 bind_group_layouts: &[Some(globals_layout), Some(mesh_bind_layout)],
982 immediate_size: 0,
983 });
984 let mesh_vertex_layout = wgpu::VertexBufferLayout {
985 array_stride: std::mem::size_of::<MeshVertex>() as u64,
986 step_mode: wgpu::VertexStepMode::Vertex,
987 attributes: &[
988 wgpu::VertexAttribute {
989 shader_location: 0,
990 offset: 0,
991 format: wgpu::VertexFormat::Float32x2,
992 },
993 wgpu::VertexAttribute {
994 shader_location: 1,
995 offset: 8,
996 format: wgpu::VertexFormat::Float32x4,
997 },
998 wgpu::VertexAttribute {
999 shader_location: 2,
1000 offset: 24,
1001 format: wgpu::VertexFormat::Float32x2,
1002 },
1003 ],
1004 };
1005 let make_mesh_pipeline =
1006 |label: &str, depth: &wgpu::DepthStencilState, color: &wgpu::ColorTargetState| {
1007 device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1008 label: Some(label),
1009 layout: Some(&mesh_pipeline_layout),
1010 vertex: wgpu::VertexState {
1011 module: &mesh_shader,
1012 entry_point: Some("vs_main"),
1013 buffers: &[Some(mesh_vertex_layout.clone())],
1014 compilation_options: wgpu::PipelineCompilationOptions::default(),
1015 },
1016 fragment: Some(wgpu::FragmentState {
1017 module: &mesh_shader,
1018 entry_point: Some("fs_main"),
1019 targets: &[Some(color.clone())],
1020 compilation_options: wgpu::PipelineCompilationOptions::default(),
1021 }),
1022 primitive: wgpu::PrimitiveState {
1023 topology: wgpu::PrimitiveTopology::TriangleList,
1024 ..Default::default()
1025 },
1026 depth_stencil: Some(depth.clone()),
1027 multisample: msaa_state,
1028 multiview_mask: None,
1029 cache: None,
1030 })
1031 };
1032 let mesh_color_target = wgpu::ColorTargetState {
1033 format,
1034 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
1035 write_mask: wgpu::ColorWrites::ALL,
1036 };
1037 let mut stencil_for_mesh = stencil_for_content.clone();
1038 stencil_for_mesh.stencil.front.compare = wgpu::CompareFunction::Equal;
1039 stencil_for_mesh.stencil.back.compare = wgpu::CompareFunction::Equal;
1040 let mesh = make_mesh_pipeline("mesh pipeline", &stencil_for_mesh, &mesh_color_target);
1041 let mesh_overlay = make_mesh_pipeline(
1042 "mesh overlay pipeline",
1043 stencil_for_content,
1044 &mesh_color_target,
1045 );
1046 let mesh_clip_inc = make_mesh_pipeline(
1047 "mesh clip (inc) pipeline",
1048 stencil_for_clip_inc,
1049 clip_color_target,
1050 );
1051 let mesh_clip_dec = make_mesh_pipeline(
1052 "mesh clip (dec) pipeline",
1053 stencil_for_clip_dec,
1054 clip_color_target,
1055 );
1056
1057 Self {
1058 rects,
1059 borders,
1060 ellipses,
1061 ellipse_borders,
1062 arcs,
1063 text_mask,
1064 text_color,
1065 image_rgba,
1066 image_nv12,
1067 blur,
1068 blur_content,
1069 clip_a2c,
1070 clip_bin,
1071 clip_dec,
1072 slug,
1073 mesh,
1074 mesh_overlay,
1075 mesh_clip_inc,
1076 mesh_clip_dec,
1077 }
1078 }
1079}
1080
1081struct Pass {
1083 target: PassTarget,
1084 initial_scissor: (u32, u32, u32, u32),
1086 clear_color: Option<[f32; 4]>,
1089 cmds: Vec<Cmd>,
1090}
1091
1092#[allow(non_snake_case)]
1093enum Cmd {
1094 ClipPush {
1095 off: u64,
1096 cnt: u32,
1097 scissor: (u32, u32, u32, u32),
1098 difference: bool,
1099 rounded: bool,
1100 },
1101 ClipPop {
1102 off: u64,
1103 cnt: u32,
1104 scissor: (u32, u32, u32, u32),
1105 difference: bool,
1106 },
1107 Rect {
1108 off: u64,
1109 cnt: u32,
1110 },
1111 Border {
1112 off: u64,
1113 cnt: u32,
1114 },
1115 Ellipse {
1116 off: u64,
1117 cnt: u32,
1118 },
1119 EllipseBorder {
1120 off: u64,
1121 cnt: u32,
1122 },
1123 Arc {
1124 off: u64,
1125 cnt: u32,
1126 },
1127 GlyphsMask {
1128 off: u64,
1129 cnt: u32,
1130 },
1131 GlyphsColor {
1132 off: u64,
1133 cnt: u32,
1134 },
1135 GlyphsVector {
1136 off: u64,
1137 cnt: u32,
1138 },
1139 ImageRgba {
1140 off: u64,
1141 cnt: u32,
1142 handle: u64,
1143 },
1144 ImageNv12 {
1145 off: u64,
1146 cnt: u32,
1147 handle: u64,
1148 },
1149 CompositeLayer {
1153 off: u64,
1154 cnt: u32,
1155 layer_id: u32,
1156 },
1157 CompositeShadow {
1161 off: u64,
1162 cnt: u32,
1163 layer_id: u32,
1164 },
1165 CompositeBlur {
1168 off: u64,
1169 cnt: u32,
1170 layer_id: u32,
1171 },
1172 VectorMesh {
1174 voff: u64,
1175 vcnt: u32,
1176 ioff: u64,
1177 icnt: u32,
1178 uoff: u64,
1179 },
1180 VectorOverlay {
1182 voff: u64,
1183 vcnt: u32,
1184 ioff: u64,
1185 icnt: u32,
1186 uoff: u64,
1187 },
1188 VectorClipPush {
1190 voff: u64,
1191 vcnt: u32,
1192 ioff: u64,
1193 icnt: u32,
1194 uoff: u64,
1195 scissor: (u32, u32, u32, u32),
1196 },
1197 VectorClipPop {
1199 voff: u64,
1200 vcnt: u32,
1201 ioff: u64,
1202 icnt: u32,
1203 uoff: u64,
1204 scissor: (u32, u32, u32, u32),
1205 },
1206 Callback {
1207 rect: repose_core::Rect,
1208 payload: repose_core::PaintCallbackPayload,
1209 },
1210}
1211
1212enum ImageTex {
1213 Rgba {
1214 tex: wgpu::Texture,
1215 bind: wgpu::BindGroup,
1216 w: u32,
1217 h: u32,
1218 format: wgpu::TextureFormat,
1219 last_used_frame: u64,
1220 bytes: u64,
1221 },
1222 User {
1224 bind: wgpu::BindGroup,
1225 w: u32,
1226 h: u32,
1227 last_used_frame: u64,
1228 bytes: u64,
1229 },
1230 Nv12 {
1231 tex_y: wgpu::Texture,
1232 tex_uv: wgpu::Texture,
1233 bind: wgpu::BindGroup,
1234 yuv_buf: wgpu::Buffer,
1235 w: u32,
1236 h: u32,
1237 color_info: ColorInfo,
1238 last_used_frame: u64,
1239 bytes: u64,
1240 },
1241}
1242
1243#[derive(Clone)]
1244struct RetainedImage {
1245 w: u32,
1246 h: u32,
1247 format: wgpu::TextureFormat,
1248 rgba: Vec<u8>,
1249}
1250
1251struct AtlasA8 {
1252 tex: wgpu::Texture,
1253 view: wgpu::TextureView,
1254 sampler: wgpu::Sampler,
1255 size: u32,
1256 next_x: u32,
1257 next_y: u32,
1258 row_h: u32,
1259 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
1260}
1261
1262struct AtlasRGBA {
1263 tex: wgpu::Texture,
1264 view: wgpu::TextureView,
1265 sampler: wgpu::Sampler,
1266 size: u32,
1267 next_x: u32,
1268 next_y: u32,
1269 row_h: u32,
1270 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
1271}
1272
1273#[derive(Clone, Copy)]
1274struct GlyphInfo {
1275 u0: f32,
1276 v0: f32,
1277 u1: f32,
1278 v1: f32,
1279 w: f32,
1280 h: f32,
1281}
1282
1283#[repr(C)]
1284#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1285struct RectInstance {
1286 xywh: [f32; 4],
1287 radii: [f32; 4],
1288 brush_type: u32,
1289 _pad: [f32; 3],
1290 color0: [f32; 4],
1291 color1: [f32; 4],
1292 grad_start: [f32; 2],
1293 grad_end: [f32; 2],
1294 sin_cos: [f32; 2],
1295}
1296
1297#[repr(C)]
1298#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1299struct BorderInstance {
1300 xywh: [f32; 4],
1301 radii: [f32; 4],
1302 stroke: f32,
1303 color: [f32; 4],
1304 sin_cos: [f32; 2],
1305}
1306
1307#[repr(C)]
1308#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1309struct EllipseInstance {
1310 xywh: [f32; 4],
1311 color: [f32; 4],
1312 sin_cos: [f32; 2],
1313}
1314
1315#[repr(C)]
1316#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1317struct EllipseBorderInstance {
1318 xywh: [f32; 4],
1319 stroke: f32,
1320 pad: f32,
1321 color: [f32; 4],
1322 sin_cos: [f32; 2],
1323}
1324
1325#[repr(C)]
1326#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1327struct ArcInstance {
1328 xywh: [f32; 4],
1329 start_angle: f32,
1330 sweep_angle: f32,
1331 stroke: f32,
1332 pad: f32,
1333 color: [f32; 4],
1334 sin_cos: [f32; 2],
1335 cap: f32, }
1337
1338#[repr(C)]
1339#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1340struct GlyphInstance {
1341 xywh: [f32; 4],
1342 uv: [f32; 4],
1343 color: [f32; 4],
1344 sin_cos: [f32; 2],
1345}
1346
1347#[repr(C)]
1348#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1349struct BlurInstance {
1350 xywh: [f32; 4],
1351 uv: [f32; 4],
1352 color: [f32; 4],
1353 blur_uv: [f32; 2],
1354 sin_cos: [f32; 2],
1355}
1356
1357#[repr(C)]
1360#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1361struct YuvTransformRaw {
1362 row0: [f32; 4],
1363 row1: [f32; 4],
1364 row2: [f32; 4],
1365 b: [f32; 4],
1366}
1367
1368#[repr(C)]
1369#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1370struct Nv12Instance {
1371 xywh: [f32; 4],
1372 uv: [f32; 4],
1373 color: [f32; 4], uv_x_offset: f32,
1375 sin_cos: [f32; 2],
1376 _pad: [f32; 1],
1377}
1378
1379#[repr(C)]
1380#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1381struct ClipInstance {
1382 xywh: [f32; 4],
1383 radii: [f32; 4],
1384 sin_cos: [f32; 2],
1385}
1386
1387#[repr(C)]
1388#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1389struct MeshVertex {
1390 pos: [f32; 2],
1391 color: [f32; 4],
1392 uv: [f32; 2],
1393}
1394
1395#[repr(C)]
1396#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
1397struct MeshUniform {
1398 m0: [f32; 4],
1399 m1: [f32; 4],
1400 paint: [u32; 4],
1401 color0: [f32; 4],
1402 color1: [f32; 4],
1403 grad_start: [f32; 2],
1404 _p3: [f32; 2],
1405 grad_end: [f32; 2],
1406 _p4: [f32; 2],
1407}
1408
1409const MESH_UNIFORM_SLOT: u64 = 256;
1411const MESH_UNIFORM_CAP: u64 = 4 * 1024 * 1024;
1412
1413impl MeshUniform {
1414 fn identity() -> Self {
1415 Self {
1416 m0: [1.0, 0.0, 0.0, 0.0],
1417 m1: [0.0, 1.0, 0.0, 0.0],
1418 paint: [0; 4],
1419 color0: [0.0; 4],
1420 color1: [0.0; 4],
1421 grad_start: [0.0; 2],
1422 _p3: [0.0; 2],
1423 grad_end: [0.0; 2],
1424 _p4: [0.0; 2],
1425 }
1426 }
1427}
1428
1429fn mesh_uniform_from_paint(affine: [f32; 6], paint: &repose_core::PaintDesc) -> MeshUniform {
1430 let (paint_type, color0, color1, grad_start, grad_end) = match paint {
1431 repose_core::PaintDesc::Solid => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
1432 repose_core::PaintDesc::Linear {
1433 start,
1434 end,
1435 start_color,
1436 end_color,
1437 } => (
1438 1u32,
1439 start_color.to_linear(),
1440 end_color.to_linear(),
1441 [start.x, start.y],
1442 [end.x, end.y],
1443 ),
1444 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
1446 };
1447 MeshUniform {
1448 m0: [affine[0], affine[1], affine[2], 0.0],
1449 m1: [affine[3], affine[4], affine[5], 0.0],
1450 paint: [paint_type, 0, 0, 0],
1451 color0,
1452 color1,
1453 grad_start,
1454 _p3: [0.0; 2],
1455 grad_end,
1456 _p4: [0.0; 2],
1457 }
1458}
1459
1460fn combine_mesh_affine(current: &Transform, mesh: [f32; 6]) -> [f32; 6] {
1461 let cos_a = current.rotate.cos();
1462 let sin_a = current.rotate.sin();
1463 let cm00 = current.scale_x * cos_a;
1465 let cm01 = -current.scale_y * sin_a;
1466 let cm10 = current.scale_x * sin_a;
1467 let cm11 = current.scale_y * cos_a;
1468 let mm00 = mesh[0];
1470 let mm01 = mesh[1];
1471 let mm10 = mesh[2];
1472 let mm11 = mesh[3];
1473 let mtx = mesh[4];
1474 let mty = mesh[5];
1475 let r00 = cm00 * mm00 + cm01 * mm10;
1476 let r01 = cm00 * mm01 + cm01 * mm11;
1477 let r10 = cm10 * mm00 + cm11 * mm10;
1478 let r11 = cm10 * mm01 + cm11 * mm11;
1479 let tx = cm00 * mtx + cm01 * mty + current.translate_x;
1480 let ty = cm10 * mtx + cm11 * mty + current.translate_y;
1481 [r00, r01, tx, r10, r11, ty]
1484}
1485
1486fn mesh_aabb(mesh: &repose_core::VectorMeshData, affine: [f32; 6]) -> repose_core::Rect {
1487 let mut min_x = f32::MAX;
1488 let mut min_y = f32::MAX;
1489 let mut max_x = f32::MIN;
1490 let mut max_y = f32::MIN;
1491 for v in mesh.vertices.iter() {
1492 let x = affine[0] * v.pos[0] + affine[1] * v.pos[1] + affine[2];
1493 let y = affine[3] * v.pos[0] + affine[4] * v.pos[1] + affine[5];
1494 min_x = min_x.min(x);
1495 min_y = min_y.min(y);
1496 max_x = max_x.max(x);
1497 max_y = max_y.max(y);
1498 }
1499 let w = (max_x - min_x).max(0.0);
1500 let h = (max_y - min_y).max(0.0);
1501 if !min_x.is_finite() || !min_y.is_finite() {
1502 return repose_core::Rect {
1503 x: 0.0,
1504 y: 0.0,
1505 w: 0.0,
1506 h: 0.0,
1507 };
1508 }
1509 repose_core::Rect {
1510 x: min_x,
1511 y: min_y,
1512 w,
1513 h,
1514 }
1515}
1516
1517fn swash_to_a8_coverage(content: repose_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
1518 match content {
1519 repose_text::SwashContent::Mask => Some(data.to_vec()),
1520 repose_text::SwashContent::SubpixelMask => {
1521 let mut out = Vec::with_capacity(data.len() / 4);
1522 for px in data.chunks_exact(4) {
1523 let r = px[0];
1524 let g = px[1];
1525 let b = px[2];
1526 out.push(r.max(g).max(b));
1527 }
1528 Some(out)
1529 }
1530 repose_text::SwashContent::Color => None,
1531 }
1532}
1533
1534impl WgpuSceneRenderer {
1535 pub fn from_device(
1536 device: wgpu::Device,
1537 queue: wgpu::Queue,
1538 output_format: wgpu::TextureFormat,
1539 msaa_samples: u32,
1540 ) -> Self {
1541 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1542 label: Some("globals layout"),
1543 entries: &[wgpu::BindGroupLayoutEntry {
1544 binding: 0,
1545 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1546 ty: wgpu::BindingType::Buffer {
1547 ty: wgpu::BufferBindingType::Uniform,
1548 has_dynamic_offset: false,
1549 min_binding_size: None,
1550 },
1551 count: None,
1552 }],
1553 });
1554
1555 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
1556 label: Some("globals buf"),
1557 size: std::mem::size_of::<Globals>() as u64,
1558 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1559 mapped_at_creation: false,
1560 });
1561
1562 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1563 label: Some("globals bind"),
1564 layout: &globals_layout,
1565 entries: &[wgpu::BindGroupEntry {
1566 binding: 0,
1567 resource: globals_buf.as_entire_binding(),
1568 }],
1569 });
1570
1571 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
1572
1573 let stencil_for_content = wgpu::DepthStencilState {
1574 format: ds_format,
1575 depth_write_enabled: Some(false),
1576 depth_compare: Some(wgpu::CompareFunction::Always),
1577 stencil: wgpu::StencilState {
1578 front: wgpu::StencilFaceState {
1579 compare: wgpu::CompareFunction::LessEqual,
1580 fail_op: wgpu::StencilOperation::Keep,
1581 depth_fail_op: wgpu::StencilOperation::Keep,
1582 pass_op: wgpu::StencilOperation::Keep,
1583 },
1584 back: wgpu::StencilFaceState {
1585 compare: wgpu::CompareFunction::LessEqual,
1586 fail_op: wgpu::StencilOperation::Keep,
1587 depth_fail_op: wgpu::StencilOperation::Keep,
1588 pass_op: wgpu::StencilOperation::Keep,
1589 },
1590 read_mask: 0xFF,
1591 write_mask: 0x00,
1592 },
1593 bias: wgpu::DepthBiasState::default(),
1594 };
1595
1596 let stencil_for_clip_inc = wgpu::DepthStencilState {
1597 format: ds_format,
1598 depth_write_enabled: Some(false),
1599 depth_compare: Some(wgpu::CompareFunction::Always),
1600 stencil: wgpu::StencilState {
1601 front: wgpu::StencilFaceState {
1602 compare: wgpu::CompareFunction::Equal,
1603 fail_op: wgpu::StencilOperation::Keep,
1604 depth_fail_op: wgpu::StencilOperation::Keep,
1605 pass_op: wgpu::StencilOperation::IncrementClamp,
1606 },
1607 back: wgpu::StencilFaceState {
1608 compare: wgpu::CompareFunction::Equal,
1609 fail_op: wgpu::StencilOperation::Keep,
1610 depth_fail_op: wgpu::StencilOperation::Keep,
1611 pass_op: wgpu::StencilOperation::IncrementClamp,
1612 },
1613 read_mask: 0xFF,
1614 write_mask: 0xFF,
1615 },
1616 bias: wgpu::DepthBiasState::default(),
1617 };
1618
1619 let stencil_for_clip_dec = wgpu::DepthStencilState {
1620 format: ds_format,
1621 depth_write_enabled: Some(false),
1622 depth_compare: Some(wgpu::CompareFunction::Always),
1623 stencil: wgpu::StencilState {
1624 front: wgpu::StencilFaceState {
1625 compare: wgpu::CompareFunction::Equal,
1626 fail_op: wgpu::StencilOperation::Keep,
1627 depth_fail_op: wgpu::StencilOperation::Keep,
1628 pass_op: wgpu::StencilOperation::DecrementClamp,
1629 },
1630 back: wgpu::StencilFaceState {
1631 compare: wgpu::CompareFunction::Equal,
1632 fail_op: wgpu::StencilOperation::Keep,
1633 depth_fail_op: wgpu::StencilOperation::Keep,
1634 pass_op: wgpu::StencilOperation::DecrementClamp,
1635 },
1636 read_mask: 0xFF,
1637 write_mask: 0xFF,
1638 },
1639 bias: wgpu::DepthBiasState::default(),
1640 };
1641
1642 let _multisample_state = wgpu::MultisampleState {
1643 count: msaa_samples,
1644 mask: !0,
1645 alpha_to_coverage_enabled: false,
1646 };
1647
1648 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1652 label: Some("image/text sampler"),
1653 address_mode_u: wgpu::AddressMode::ClampToEdge,
1654 address_mode_v: wgpu::AddressMode::ClampToEdge,
1655 mag_filter: wgpu::FilterMode::Linear,
1656 min_filter: wgpu::FilterMode::Linear,
1657 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1658 ..Default::default()
1659 });
1660
1661 let layer_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1663 label: Some("layer nearest sampler"),
1664 address_mode_u: wgpu::AddressMode::ClampToEdge,
1665 address_mode_v: wgpu::AddressMode::ClampToEdge,
1666 mag_filter: wgpu::FilterMode::Nearest,
1667 min_filter: wgpu::FilterMode::Nearest,
1668 mipmap_filter: wgpu::MipmapFilterMode::Nearest,
1669 ..Default::default()
1670 });
1671
1672 let layer_sampler_linear = device.create_sampler(&wgpu::SamplerDescriptor {
1675 label: Some("layer linear sampler"),
1676 address_mode_u: wgpu::AddressMode::ClampToEdge,
1677 address_mode_v: wgpu::AddressMode::ClampToEdge,
1678 mag_filter: wgpu::FilterMode::Linear,
1679 min_filter: wgpu::FilterMode::Linear,
1680 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1681 ..Default::default()
1682 });
1683
1684 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1686 label: Some("text/rgba bind layout"),
1687 entries: &[
1688 wgpu::BindGroupLayoutEntry {
1689 binding: 0,
1690 visibility: wgpu::ShaderStages::FRAGMENT,
1691 ty: wgpu::BindingType::Texture {
1692 multisampled: false,
1693 view_dimension: wgpu::TextureViewDimension::D2,
1694 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1695 },
1696 count: None,
1697 },
1698 wgpu::BindGroupLayoutEntry {
1699 binding: 1,
1700 visibility: wgpu::ShaderStages::FRAGMENT,
1701 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1702 count: None,
1703 },
1704 ],
1705 });
1706 let image_bind_layout_rgba = text_bind_layout.clone();
1708
1709 let image_bind_layout_nv12 =
1711 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1712 label: Some("image bind layout nv12"),
1713 entries: &[
1714 wgpu::BindGroupLayoutEntry {
1716 binding: 0,
1717 visibility: wgpu::ShaderStages::FRAGMENT,
1718 ty: wgpu::BindingType::Texture {
1719 multisampled: false,
1720 view_dimension: wgpu::TextureViewDimension::D2,
1721 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1722 },
1723 count: None,
1724 },
1725 wgpu::BindGroupLayoutEntry {
1727 binding: 1,
1728 visibility: wgpu::ShaderStages::FRAGMENT,
1729 ty: wgpu::BindingType::Texture {
1730 multisampled: false,
1731 view_dimension: wgpu::TextureViewDimension::D2,
1732 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1733 },
1734 count: None,
1735 },
1736 wgpu::BindGroupLayoutEntry {
1738 binding: 2,
1739 visibility: wgpu::ShaderStages::FRAGMENT,
1740 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1741 count: None,
1742 },
1743 wgpu::BindGroupLayoutEntry {
1745 binding: 3,
1746 visibility: wgpu::ShaderStages::FRAGMENT,
1747 ty: wgpu::BindingType::Buffer {
1748 ty: wgpu::BufferBindingType::Uniform,
1749 has_dynamic_offset: false,
1750 min_binding_size: None,
1751 },
1752 count: None,
1753 },
1754 ],
1755 });
1756
1757 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1759 label: Some("clip pipeline layout"),
1760 bind_group_layouts: &[Some(&globals_layout)],
1761 immediate_size: 0,
1762 });
1763 let clip_vertex_layout = wgpu::VertexBufferLayout {
1764 array_stride: std::mem::size_of::<ClipInstance>() as u64,
1765 step_mode: wgpu::VertexStepMode::Instance,
1766 attributes: &[
1767 wgpu::VertexAttribute {
1768 shader_location: 0,
1769 offset: 0,
1770 format: wgpu::VertexFormat::Float32x4,
1771 },
1772 wgpu::VertexAttribute {
1773 shader_location: 1,
1774 offset: 16,
1775 format: wgpu::VertexFormat::Float32x4,
1776 },
1777 wgpu::VertexAttribute {
1778 shader_location: 2,
1779 offset: 32,
1780 format: wgpu::VertexFormat::Float32x2,
1781 },
1782 ],
1783 };
1784 let clip_color_target = wgpu::ColorTargetState {
1785 format: output_format,
1786 blend: None,
1787 write_mask: wgpu::ColorWrites::empty(),
1788 };
1789
1790 let mesh_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1792 label: Some("mesh uniform layout"),
1793 entries: &[wgpu::BindGroupLayoutEntry {
1794 binding: 0,
1795 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1796 ty: wgpu::BindingType::Buffer {
1797 ty: wgpu::BufferBindingType::Uniform,
1798 has_dynamic_offset: true,
1799 min_binding_size: NonZero::new(MESH_UNIFORM_SLOT),
1800 },
1801 count: None,
1802 }],
1803 });
1804 let mesh_uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
1805 label: Some("mesh uniform buffer"),
1806 size: MESH_UNIFORM_CAP,
1807 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1808 mapped_at_creation: false,
1809 });
1810 let mesh_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1811 label: Some("mesh uniform bind"),
1812 layout: &mesh_bind_layout,
1813 entries: &[wgpu::BindGroupEntry {
1814 binding: 0,
1815 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1816 buffer: &mesh_uniform_buf,
1817 offset: 0,
1818 size: NonZero::new(MESH_UNIFORM_SLOT),
1819 }),
1820 }],
1821 });
1822
1823 let surface_pipes = Pipelines::create(
1826 &device,
1827 output_format,
1828 msaa_samples,
1829 &globals_layout,
1830 &text_bind_layout,
1831 &image_bind_layout_nv12,
1832 &clip_pipeline_layout,
1833 &stencil_for_content,
1834 &stencil_for_clip_inc,
1835 &stencil_for_clip_dec,
1836 &clip_color_target,
1837 &clip_vertex_layout,
1838 &mesh_bind_layout,
1839 );
1840 let layer_pipes = Pipelines::create(
1841 &device,
1842 output_format,
1843 1,
1844 &globals_layout,
1845 &text_bind_layout,
1846 &image_bind_layout_nv12,
1847 &clip_pipeline_layout,
1848 &stencil_for_content,
1849 &stencil_for_clip_inc,
1850 &stencil_for_clip_dec,
1851 &clip_color_target,
1852 &clip_vertex_layout,
1853 &mesh_bind_layout,
1854 );
1855
1856 let slug_enabled = true;
1858
1859 let blur_ring = UploadRing::new(
1861 &device,
1862 "blur ring",
1863 1024 * 1024,
1864 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1865 );
1866
1867 let atlas_mask = init_atlas_mask(&device);
1869 let atlas_color = init_atlas_color(&device);
1870
1871 let ring_rect = UploadRing::new(
1873 &device,
1874 "ring rect",
1875 1 << 20,
1876 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1877 );
1878 let ring_border = UploadRing::new(
1879 &device,
1880 "ring border",
1881 1 << 20,
1882 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1883 );
1884 let ring_ellipse = UploadRing::new(
1885 &device,
1886 "ring ellipse",
1887 1 << 20,
1888 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1889 );
1890 let ring_ellipse_border = UploadRing::new(
1891 &device,
1892 "ring ellipse border",
1893 1 << 20,
1894 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1895 );
1896 let ring_arc = UploadRing::new(
1897 &device,
1898 "ring arc",
1899 1 << 20,
1900 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1901 );
1902 let ring_glyph_mask = UploadRing::new(
1903 &device,
1904 "ring glyph mask",
1905 1 << 20,
1906 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1907 );
1908 let ring_glyph_color = UploadRing::new(
1909 &device,
1910 "ring glyph color",
1911 1 << 20,
1912 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1913 );
1914 let ring_slug = UploadRing::new(
1915 &device,
1916 "ring slug",
1917 1 << 22,
1918 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1919 );
1920 let ring_clip = UploadRing::new(
1921 &device,
1922 "ring clip",
1923 1 << 16,
1924 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1925 );
1926 let ring_nv12 = UploadRing::new(
1927 &device,
1928 "ring nv12",
1929 1 << 20,
1930 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1931 );
1932 let ring_mesh_verts = UploadRing::new(
1933 &device,
1934 "ring mesh verts",
1935 1 << 22,
1936 wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
1937 );
1938 let ring_mesh_indices = UploadRing::new(
1939 &device,
1940 "ring mesh indices",
1941 1 << 22,
1942 wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
1943 );
1944
1945 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
1947 label: Some("temp ds"),
1948 size: wgpu::Extent3d {
1949 width: 1,
1950 height: 1,
1951 depth_or_array_layers: 1,
1952 },
1953 mip_level_count: 1,
1954 sample_count: 1,
1955 dimension: wgpu::TextureDimension::D2,
1956 format: wgpu::TextureFormat::Depth24PlusStencil8,
1957 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1958 view_formats: &[],
1959 });
1960 let depth_stencil_view =
1961 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1962
1963 let mut renderer = WgpuSceneRenderer {
1964 device,
1965 queue,
1966 output_format,
1967 output_width: 0,
1968 output_height: 0,
1969 pixels_per_point: 1.0,
1970
1971 surface_pipes,
1972 layer_pipes,
1973
1974 rects: InstancedPipe::new(ring_rect),
1975 borders: InstancedPipe::new(ring_border),
1976 ellipses: InstancedPipe::new(ring_ellipse),
1977 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
1978 arcs: InstancedPipe::new(ring_arc),
1979 glyph_mask: InstancedPipe::new(ring_glyph_mask),
1980 glyph_color: InstancedPipe::new(ring_glyph_color),
1981
1982 text_bind_layout,
1983
1984 image_bind_layout_rgba,
1985 image_bind_layout_nv12,
1986 image_sampler,
1987 layer_sampler,
1988 layer_sampler_linear,
1989
1990 blur_ring,
1991
1992 slug_enabled,
1993 slug_ring: ring_slug,
1994 slug_cache: slug::GlyphSlugCache::new(),
1995
1996 clip_ring: ring_clip,
1997
1998 nv12: InstancedPipe::new(ring_nv12),
1999
2000 mesh_verts: ring_mesh_verts,
2001 mesh_indices: ring_mesh_indices,
2002 mesh_uniform_buf,
2003 mesh_bind_layout,
2004 mesh_bind,
2005 mesh_uniform_head: 0,
2006 mesh_clip_stack: Vec::new(),
2007
2008 msaa_samples,
2009 depth_stencil_tex,
2010 depth_stencil_view,
2011 msaa_tex: None,
2012 msaa_view: None,
2013 globals_bind,
2014 globals_buf,
2015
2016 atlas_mask,
2017 atlas_color,
2018
2019 next_image_handle: 1,
2020 images: HashMap::new(),
2021 retained: HashMap::new(),
2022
2023 frame_index: 0,
2024 image_bytes_total: 0,
2025 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
2028
2029 working_space: false,
2030 ws_tex: None,
2031 ws_view: None,
2032 ws_bind: None,
2033 display_pipeline: None,
2034 display_layout: None,
2035
2036 callback_resources: CallbackResources::default(),
2037 };
2038
2039 renderer.recreate_msaa_and_depth_stencil();
2040 renderer
2041 }
2042}
2043
2044impl WgpuSurfaceBackend {
2045 #[cfg(feature = "winit-surface")]
2046 pub async fn new_async(
2047 window: Arc<winit::window::Window>,
2048 ) -> anyhow::Result<WgpuSurfaceBackend> {
2049 Self::new_async_with_options(window, 4, PresentModePref::Auto).await
2050 }
2051
2052 #[cfg(feature = "winit-surface")]
2055 pub async fn new_async_with_msaa(
2056 window: Arc<winit::window::Window>,
2057 msaa_samples: u32,
2058 ) -> anyhow::Result<WgpuSurfaceBackend> {
2059 Self::new_async_with_options(window, msaa_samples, PresentModePref::Auto).await
2060 }
2061
2062 #[cfg(feature = "winit-surface")]
2065 pub async fn new_async_with_options(
2066 window: Arc<winit::window::Window>,
2067 msaa_samples: u32,
2068 present_mode: PresentModePref,
2069 ) -> anyhow::Result<WgpuSurfaceBackend> {
2070 let instance: Instance;
2071
2072 if cfg!(target_arch = "wasm32") {
2073 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
2074 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
2075 instance = wgpu::util::new_instance_with_webgpu_detection(desc).await;
2076 } else {
2077 instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
2078 };
2079
2080 let surface = instance.create_surface(window.clone())?;
2081
2082 let adapter = instance
2083 .request_adapter(&wgpu::RequestAdapterOptions {
2084 power_preference: wgpu::PowerPreference::HighPerformance,
2085 compatible_surface: Some(&surface),
2086 force_fallback_adapter: false,
2087 apply_limit_buckets: false,
2088 })
2089 .await
2090 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
2091
2092 let limits = adapter.limits();
2093
2094 #[cfg(target_os = "linux")]
2095 let features = {
2096 let af = adapter.features();
2097 let mut f = wgpu::Features::empty();
2098 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD) {
2099 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD;
2100 }
2101 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF) {
2102 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF;
2103 }
2104 f
2105 };
2106 #[cfg(not(target_os = "linux"))]
2107 let features = wgpu::Features::empty();
2108
2109 let (device, queue) = adapter
2110 .request_device(&wgpu::DeviceDescriptor {
2111 label: Some("repose-rs device"),
2112 required_features: features,
2113 required_limits: limits,
2114 experimental_features: wgpu::ExperimentalFeatures::disabled(),
2115 memory_hints: wgpu::MemoryHints::default(),
2116 trace: wgpu::Trace::Off,
2117 })
2118 .await
2119 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
2120
2121 let size = window.inner_size();
2122
2123 let caps = surface.get_capabilities(&adapter);
2124
2125 let (format, view_format) = if cfg!(target_arch = "wasm32")
2126 && adapter
2127 .get_downlevel_capabilities()
2128 .flags
2129 .contains(wgpu::DownlevelFlags::SURFACE_VIEW_FORMATS)
2130 {
2131 let non_srgb = caps
2132 .formats
2133 .iter()
2134 .copied()
2135 .find(|f| !f.is_srgb())
2136 .unwrap_or(caps.formats[0]);
2137 (non_srgb, Some(non_srgb.add_srgb_suffix()))
2138 } else if cfg!(target_arch = "wasm32") {
2139 let fmt = caps
2140 .formats
2141 .iter()
2142 .copied()
2143 .find(|f| f.is_srgb())
2144 .unwrap_or(caps.formats[0]);
2145 (fmt, None)
2146 } else {
2147 let fmt = caps
2148 .formats
2149 .iter()
2150 .copied()
2151 .find(|f| f.is_srgb())
2152 .unwrap_or(caps.formats[0]);
2153 (fmt, None)
2154 };
2155
2156 let present_mode = pick_present_mode(&caps, present_mode);
2157 let alpha_mode = caps.alpha_modes[0];
2158
2159 let render_format = view_format.unwrap_or(format);
2160 let msaa_samples = pick_surface_msaa(&adapter, format, msaa_samples);
2161 let renderer = WgpuSceneRenderer::from_device(device, queue, render_format, msaa_samples);
2162
2163 let view_formats = view_format.into_iter().collect::<Vec<_>>();
2164
2165 let config = wgpu::SurfaceConfiguration {
2166 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
2167 format,
2168 width: size.width.max(1),
2169 height: size.height.max(1),
2170 present_mode,
2171 alpha_mode,
2172 color_space: wgpu::SurfaceColorSpace::Auto,
2173 view_formats,
2174 desired_maximum_frame_latency: 1,
2175 };
2176 surface.configure(&renderer.device, &config);
2177
2178 Ok(WgpuSurfaceBackend {
2179 surface: Some(surface),
2180 surface_config: Some(config),
2181 renderer,
2182 })
2183 }
2184
2185 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2186 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2187 pollster::block_on(Self::new_async(window))
2188 }
2189
2190 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2191 pub fn new_with_msaa(
2192 window: Arc<winit::window::Window>,
2193 msaa_samples: u32,
2194 ) -> anyhow::Result<WgpuSurfaceBackend> {
2195 pollster::block_on(Self::new_async_with_msaa(window, msaa_samples))
2196 }
2197
2198 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2199 pub fn new_with_options(
2200 window: Arc<winit::window::Window>,
2201 msaa_samples: u32,
2202 present_mode: PresentModePref,
2203 ) -> anyhow::Result<WgpuSurfaceBackend> {
2204 pollster::block_on(Self::new_async_with_options(
2205 window,
2206 msaa_samples,
2207 present_mode,
2208 ))
2209 }
2210
2211 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2212 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2213 anyhow::bail!("Use WgpuSurfaceBackend::new_async(window).await on wasm32")
2214 }
2215
2216 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2217 pub fn new_with_msaa(
2218 _window: Arc<winit::window::Window>,
2219 _msaa_samples: u32,
2220 ) -> anyhow::Result<WgpuSurfaceBackend> {
2221 anyhow::bail!("Use WgpuSurfaceBackend::new_async_with_msaa(window, msaa).await on wasm32")
2222 }
2223
2224 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2225 pub fn new_with_options(
2226 _window: Arc<winit::window::Window>,
2227 _msaa_samples: u32,
2228 _present_mode: PresentModePref,
2229 ) -> anyhow::Result<WgpuSurfaceBackend> {
2230 anyhow::bail!(
2231 "Use WgpuSurfaceBackend::new_async_with_options(window, msaa, mode).await on wasm32"
2232 )
2233 }
2234}
2235
2236fn pick_present_mode(caps: &wgpu::SurfaceCapabilities, pref: PresentModePref) -> wgpu::PresentMode {
2239 let auto = || {
2240 caps.present_modes
2241 .iter()
2242 .copied()
2243 .find(|m| *m == wgpu::PresentMode::Fifo)
2244 .or_else(|| {
2245 caps.present_modes
2246 .iter()
2247 .copied()
2248 .find(|m| *m == wgpu::PresentMode::Mailbox)
2249 })
2250 .unwrap_or(wgpu::PresentMode::Immediate)
2251 };
2252 match pref {
2253 PresentModePref::Auto => auto(),
2254 PresentModePref::Fifo if caps.present_modes.contains(&wgpu::PresentMode::Fifo) => {
2255 wgpu::PresentMode::Fifo
2256 }
2257 PresentModePref::Mailbox if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) => {
2258 wgpu::PresentMode::Mailbox
2259 }
2260 PresentModePref::Immediate
2261 if caps.present_modes.contains(&wgpu::PresentMode::Immediate) =>
2262 {
2263 wgpu::PresentMode::Immediate
2264 }
2265 _ => auto(),
2266 }
2267}
2268
2269pub fn pick_surface_msaa(
2272 adapter: &wgpu::Adapter,
2273 format: wgpu::TextureFormat,
2274 requested: u32,
2275) -> u32 {
2276 let requested = requested.max(1);
2277 let color_feat = adapter.get_texture_format_features(format);
2278 let depth_feat = adapter.get_texture_format_features(wgpu::TextureFormat::Depth24PlusStencil8);
2279 let supported = |n: u32| {
2280 color_feat.flags.sample_count_supported(n)
2281 && color_feat
2282 .flags
2283 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
2284 && depth_feat.flags.sample_count_supported(n)
2285 };
2286 let mut candidates = vec![requested];
2287 for n in [8, 4, 2, 1] {
2288 if n < requested {
2289 candidates.push(n);
2290 }
2291 }
2292 let chosen = candidates.into_iter().find(|&n| supported(n)).unwrap_or(1);
2293 if chosen != requested {
2294 log::info!("requested MSAA x{requested}, using x{chosen}");
2295 }
2296 chosen
2297}
2298
2299impl WgpuSceneRenderer {
2300 pub fn set_image_from_bytes(
2303 &mut self,
2304 handle: u64,
2305 data: &[u8],
2306 srgb: bool,
2307 ) -> anyhow::Result<()> {
2308 let img = image::load_from_memory(data)?;
2309 let rgba = img.to_rgba8();
2310 let (w, h) = rgba.dimensions();
2311 self.set_image_rgba8(handle, w, h, &rgba, srgb)
2312 }
2313
2314 pub fn set_image_rgba8(
2315 &mut self,
2316 handle: u64,
2317 w: u32,
2318 h: u32,
2319 rgba: &[u8],
2320 srgb: bool,
2321 ) -> anyhow::Result<()> {
2322 let expected = (w as usize) * (h as usize) * 4;
2323 if rgba.len() < expected {
2324 return Err(anyhow::anyhow!(
2325 "RGBA buffer too small: {} < {}",
2326 rgba.len(),
2327 expected
2328 ));
2329 }
2330
2331 let format = if srgb {
2332 wgpu::TextureFormat::Rgba8UnormSrgb
2333 } else {
2334 wgpu::TextureFormat::Rgba8Unorm
2335 };
2336
2337 let needs_recreate = match self.images.get(&handle) {
2338 Some(ImageTex::Rgba {
2339 w: cw,
2340 h: ch,
2341 format: cf,
2342 ..
2343 }) => *cw != w || *ch != h || *cf != format,
2344 _ => true,
2345 };
2346
2347 if needs_recreate {
2348 self.remove_image(handle);
2349
2350 let (tex, bind) = self.create_rgba_tex(w, h, format);
2351 let bytes = (w as u64) * (h as u64) * 4;
2352 self.image_bytes_total += bytes;
2353
2354 self.images.insert(
2355 handle,
2356 ImageTex::Rgba {
2357 tex,
2358 bind,
2359 w,
2360 h,
2361 format,
2362 last_used_frame: self.frame_index,
2363 bytes,
2364 },
2365 );
2366 }
2367
2368 self.retained.insert(
2369 handle,
2370 RetainedImage {
2371 w,
2372 h,
2373 format,
2374 rgba: rgba[..expected].to_vec(),
2375 },
2376 );
2377
2378 let tex = match self.images.get(&handle) {
2379 Some(ImageTex::Rgba { tex, .. }) => tex,
2380 _ => unreachable!(),
2381 };
2382
2383 self.queue.write_texture(
2384 wgpu::TexelCopyTextureInfo {
2385 texture: tex,
2386 mip_level: 0,
2387 origin: wgpu::Origin3d::ZERO,
2388 aspect: wgpu::TextureAspect::All,
2389 },
2390 &rgba[..expected],
2391 wgpu::TexelCopyBufferLayout {
2392 offset: 0,
2393 bytes_per_row: Some(4 * w),
2394 rows_per_image: Some(h),
2395 },
2396 wgpu::Extent3d {
2397 width: w,
2398 height: h,
2399 depth_or_array_layers: 1,
2400 },
2401 );
2402
2403 self.evict_budget_excess();
2405
2406 Ok(())
2407 }
2408
2409 fn create_rgba_tex(
2412 &self,
2413 w: u32,
2414 h: u32,
2415 format: wgpu::TextureFormat,
2416 ) -> (wgpu::Texture, wgpu::BindGroup) {
2417 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2418 label: Some("user image rgba"),
2419 size: wgpu::Extent3d {
2420 width: w,
2421 height: h,
2422 depth_or_array_layers: 1,
2423 },
2424 mip_level_count: 1,
2425 sample_count: 1,
2426 dimension: wgpu::TextureDimension::D2,
2427 format,
2428 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2429 view_formats: &[],
2430 });
2431 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
2432
2433 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2434 label: Some("image bind rgba"),
2435 layout: &self.image_bind_layout_rgba,
2436 entries: &[
2437 wgpu::BindGroupEntry {
2438 binding: 0,
2439 resource: wgpu::BindingResource::TextureView(&view),
2440 },
2441 wgpu::BindGroupEntry {
2442 binding: 1,
2443 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2444 },
2445 ],
2446 });
2447
2448 (tex, bind)
2449 }
2450
2451 pub fn register_native_texture(
2453 &mut self,
2454 view: &wgpu::TextureView,
2455 width: u32,
2456 height: u32,
2457 ) -> u64 {
2458 let handle = self.next_image_handle;
2459 self.next_image_handle += 1;
2460 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2461 label: Some("user native image"),
2462 layout: &self.image_bind_layout_rgba,
2463 entries: &[
2464 wgpu::BindGroupEntry {
2465 binding: 0,
2466 resource: wgpu::BindingResource::TextureView(view),
2467 },
2468 wgpu::BindGroupEntry {
2469 binding: 1,
2470 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2471 },
2472 ],
2473 });
2474 self.images.insert(
2475 handle,
2476 ImageTex::User {
2477 bind,
2478 w: width,
2479 h: height,
2480 last_used_frame: self.frame_index,
2481 bytes: 0,
2482 },
2483 );
2484 handle
2485 }
2486
2487 pub fn register_native_texture_with_sampler(
2489 &mut self,
2490 view: &wgpu::TextureView,
2491 sampler_desc: wgpu::SamplerDescriptor<'_>,
2492 width: u32,
2493 height: u32,
2494 ) -> u64 {
2495 let handle = self.next_image_handle;
2496 self.next_image_handle += 1;
2497 let sampler = self.device.create_sampler(&sampler_desc);
2498 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2499 label: Some("user native image sampleropts"),
2500 layout: &self.image_bind_layout_rgba,
2501 entries: &[
2502 wgpu::BindGroupEntry {
2503 binding: 0,
2504 resource: wgpu::BindingResource::TextureView(view),
2505 },
2506 wgpu::BindGroupEntry {
2507 binding: 1,
2508 resource: wgpu::BindingResource::Sampler(&sampler),
2509 },
2510 ],
2511 });
2512 self.images.insert(
2513 handle,
2514 ImageTex::User {
2515 bind,
2516 w: width,
2517 h: height,
2518 last_used_frame: self.frame_index,
2519 bytes: 0,
2520 },
2521 );
2522 handle
2523 }
2524
2525 pub fn update_native_texture(&mut self, handle: u64, view: &wgpu::TextureView) {
2527 let Some(entry) = self.images.get_mut(&handle) else {
2528 log::warn!("update_native_texture: handle {handle} not found");
2529 return;
2530 };
2531 let w = match entry {
2532 ImageTex::User { w, .. } => *w,
2533 ImageTex::Rgba { w, .. } => *w,
2534 _ => {
2535 log::warn!("update_native_texture: handle {handle} is not rgba/user");
2536 return;
2537 }
2538 };
2539 let h = match entry {
2540 ImageTex::User { h, .. } => *h,
2541 ImageTex::Rgba { h, .. } => *h,
2542 _ => 0,
2543 };
2544 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2545 label: Some("user native image update"),
2546 layout: &self.image_bind_layout_rgba,
2547 entries: &[
2548 wgpu::BindGroupEntry {
2549 binding: 0,
2550 resource: wgpu::BindingResource::TextureView(view),
2551 },
2552 wgpu::BindGroupEntry {
2553 binding: 1,
2554 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2555 },
2556 ],
2557 });
2558 *entry = ImageTex::User {
2559 bind,
2560 w,
2561 h,
2562 last_used_frame: self.frame_index,
2563 bytes: 0,
2564 };
2565 }
2566
2567 pub fn set_image_nv12(
2568 &mut self,
2569 handle: u64,
2570 w: u32,
2571 h: u32,
2572 y: &[u8],
2573 uv: &[u8],
2574 color_info: ColorInfo,
2575 ) -> anyhow::Result<()> {
2576 let y_expected = (w as usize) * (h as usize);
2577 let uv_w = w.div_ceil(2);
2578 let uv_h = h.div_ceil(2);
2579 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
2580
2581 if y.len() < y_expected {
2582 return Err(anyhow::anyhow!("Y plane too small"));
2583 }
2584 if uv.len() < uv_expected {
2585 return Err(anyhow::anyhow!("UV plane too small"));
2586 }
2587
2588 let needs_recreate = match self.images.get(&handle) {
2589 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
2590 _ => true,
2591 };
2592
2593 let yuv = color_info.to_yuv_transform();
2595 let yuv_raw = YuvTransformRaw {
2596 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2597 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2598 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2599 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
2600 };
2601
2602 if needs_recreate {
2603 self.remove_image(handle);
2604
2605 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
2606 label: Some("nv12 Y"),
2607 size: wgpu::Extent3d {
2608 width: w,
2609 height: h,
2610 depth_or_array_layers: 1,
2611 },
2612 mip_level_count: 1,
2613 sample_count: 1,
2614 dimension: wgpu::TextureDimension::D2,
2615 format: wgpu::TextureFormat::R8Unorm,
2616 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2617 view_formats: &[],
2618 });
2619 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
2620
2621 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
2622 label: Some("nv12 UV"),
2623 size: wgpu::Extent3d {
2624 width: uv_w,
2625 height: uv_h,
2626 depth_or_array_layers: 1,
2627 },
2628 mip_level_count: 1,
2629 sample_count: 1,
2630 dimension: wgpu::TextureDimension::D2,
2631 format: wgpu::TextureFormat::Rg8Unorm,
2632 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2633 view_formats: &[],
2634 });
2635 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
2636
2637 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2639 label: Some("nv12 yuv transform"),
2640 size: std::mem::size_of::<YuvTransformRaw>() as u64,
2641 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2642 mapped_at_creation: false,
2643 });
2644
2645 self.queue
2647 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2648
2649 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2650 label: Some("nv12 bind"),
2651 layout: &self.image_bind_layout_nv12,
2652 entries: &[
2653 wgpu::BindGroupEntry {
2654 binding: 0,
2655 resource: wgpu::BindingResource::TextureView(&view_y),
2656 },
2657 wgpu::BindGroupEntry {
2658 binding: 1,
2659 resource: wgpu::BindingResource::TextureView(&view_uv),
2660 },
2661 wgpu::BindGroupEntry {
2662 binding: 2,
2663 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2664 },
2665 wgpu::BindGroupEntry {
2666 binding: 3,
2667 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2668 buffer: &yuv_buf,
2669 offset: 0,
2670 size: None,
2671 }),
2672 },
2673 ],
2674 });
2675
2676 let bytes = (w as u64) * (h as u64)
2677 + (uv_w as u64) * (uv_h as u64) * 2
2678 + std::mem::size_of::<YuvTransformRaw>() as u64;
2679 self.image_bytes_total += bytes;
2680
2681 self.images.insert(
2682 handle,
2683 ImageTex::Nv12 {
2684 tex_y,
2685 tex_uv,
2686 bind,
2687 yuv_buf,
2688 w,
2689 h,
2690 color_info,
2691 last_used_frame: self.frame_index,
2692 bytes,
2693 },
2694 );
2695 } else {
2696 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
2698 self.queue
2699 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2700 }
2701 }
2702
2703 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
2704 Some(ImageTex::Nv12 {
2705 tex_y,
2706 tex_uv,
2707 bind,
2708 ..
2709 }) => (tex_y, tex_uv, bind),
2710 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
2711 };
2712
2713 self.queue.write_texture(
2714 wgpu::TexelCopyTextureInfo {
2715 texture: tex_y,
2716 mip_level: 0,
2717 origin: wgpu::Origin3d::ZERO,
2718 aspect: wgpu::TextureAspect::All,
2719 },
2720 &y[..y_expected],
2721 wgpu::TexelCopyBufferLayout {
2722 offset: 0,
2723 bytes_per_row: Some(w),
2724 rows_per_image: Some(h),
2725 },
2726 wgpu::Extent3d {
2727 width: w,
2728 height: h,
2729 depth_or_array_layers: 1,
2730 },
2731 );
2732
2733 self.queue.write_texture(
2734 wgpu::TexelCopyTextureInfo {
2735 texture: tex_uv,
2736 mip_level: 0,
2737 origin: wgpu::Origin3d::ZERO,
2738 aspect: wgpu::TextureAspect::All,
2739 },
2740 &uv[..uv_expected],
2741 wgpu::TexelCopyBufferLayout {
2742 offset: 0,
2743 bytes_per_row: Some(2 * uv_w),
2744 rows_per_image: Some(uv_h),
2745 },
2746 wgpu::Extent3d {
2747 width: uv_w,
2748 height: uv_h,
2749 depth_or_array_layers: 1,
2750 },
2751 );
2752
2753 self.evict_budget_excess();
2754 Ok(())
2755 }
2756
2757 pub fn set_image_planes(
2758 &mut self,
2759 handle: u64,
2760 w: u32,
2761 h: u32,
2762 pixel_format: PixelFormat,
2763 planes: &[&[u8]],
2764 color_info: ColorInfo,
2765 ) -> anyhow::Result<()> {
2766 match pixel_format {
2767 PixelFormat::Nv12 => {
2768 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
2769 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
2770 self.set_image_nv12(handle, w, h, y, uv, color_info)
2771 }
2772 PixelFormat::P010 => {
2773 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
2774 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
2775 self.set_image_p010(handle, w, h, y, uv, color_info)
2776 }
2777 PixelFormat::I420 | PixelFormat::I444 => Err(anyhow::anyhow!(
2778 "I420/I444 not implemented and unlikely -> cheap to convert to NV12 (better for the GPU too)"
2779 )),
2780 PixelFormat::Rgba => {
2781 let rgba = planes
2782 .first()
2783 .ok_or(anyhow::anyhow!("missing RGBA plane"))?;
2784 self.set_image_rgba8(handle, w, h, rgba, false)
2785 }
2786 }
2787 }
2788
2789 fn set_image_p010(
2790 &mut self,
2791 handle: u64,
2792 w: u32,
2793 h: u32,
2794 y: &[u8],
2795 uv: &[u8],
2796 color_info: ColorInfo,
2797 ) -> anyhow::Result<()> {
2798 let uv_w = w.div_ceil(2);
2799 let uv_h = h.div_ceil(2);
2800
2801 let y_expected = (w as usize) * 2;
2802 let uv_expected = (uv_w as usize) * (uv_h as usize) * 4;
2803
2804 if y.len() < y_expected {
2805 return Err(anyhow::anyhow!("P010 Y plane too small"));
2806 }
2807 if uv.len() < uv_expected {
2808 return Err(anyhow::anyhow!("P010 UV plane too small"));
2809 }
2810
2811 let needs_recreate = match self.images.get(&handle) {
2815 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
2816 _ => true,
2817 };
2818
2819 let yuv = color_info.to_yuv_transform();
2820 let yuv_raw = YuvTransformRaw {
2821 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2822 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2823 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2824 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
2825 };
2826
2827 if needs_recreate {
2828 self.remove_image(handle);
2829
2830 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
2831 label: Some("p010 Y"),
2832 size: wgpu::Extent3d {
2833 width: w,
2834 height: h,
2835 depth_or_array_layers: 1,
2836 },
2837 mip_level_count: 1,
2838 sample_count: 1,
2839 dimension: wgpu::TextureDimension::D2,
2840 format: wgpu::TextureFormat::R16Unorm,
2841 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2842 view_formats: &[],
2843 });
2844 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
2845
2846 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
2847 label: Some("p010 UV"),
2848 size: wgpu::Extent3d {
2849 width: uv_w,
2850 height: uv_h,
2851 depth_or_array_layers: 1,
2852 },
2853 mip_level_count: 1,
2854 sample_count: 1,
2855 dimension: wgpu::TextureDimension::D2,
2856 format: wgpu::TextureFormat::Rg16Unorm,
2857 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2858 view_formats: &[],
2859 });
2860 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
2861
2862 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2863 label: Some("p010 yuv transform"),
2864 size: std::mem::size_of::<YuvTransformRaw>() as u64,
2865 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2866 mapped_at_creation: false,
2867 });
2868 self.queue
2869 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2870
2871 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2872 label: Some("p010 bind"),
2873 layout: &self.image_bind_layout_nv12,
2874 entries: &[
2875 wgpu::BindGroupEntry {
2876 binding: 0,
2877 resource: wgpu::BindingResource::TextureView(&view_y),
2878 },
2879 wgpu::BindGroupEntry {
2880 binding: 1,
2881 resource: wgpu::BindingResource::TextureView(&view_uv),
2882 },
2883 wgpu::BindGroupEntry {
2884 binding: 2,
2885 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2886 },
2887 wgpu::BindGroupEntry {
2888 binding: 3,
2889 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2890 buffer: &yuv_buf,
2891 offset: 0,
2892 size: None,
2893 }),
2894 },
2895 ],
2896 });
2897
2898 let bytes = (w as u64) * 2
2899 + (uv_w as u64) * (uv_h as u64) * 4
2900 + std::mem::size_of::<YuvTransformRaw>() as u64;
2901 self.image_bytes_total += bytes;
2902
2903 self.images.insert(
2904 handle,
2905 ImageTex::Nv12 {
2906 tex_y,
2907 tex_uv,
2908 bind,
2909 yuv_buf,
2910 w,
2911 h,
2912 color_info,
2913 last_used_frame: self.frame_index,
2914 bytes,
2915 },
2916 );
2917 } else {
2918 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
2919 self.queue
2920 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2921 }
2922 }
2923
2924 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
2925 Some(ImageTex::Nv12 {
2926 tex_y,
2927 tex_uv,
2928 bind,
2929 ..
2930 }) => (tex_y, tex_uv, bind),
2931 _ => return Err(anyhow::anyhow!("Handle is not P010/NV12")),
2932 };
2933
2934 self.queue.write_texture(
2935 wgpu::TexelCopyTextureInfo {
2936 texture: tex_y,
2937 mip_level: 0,
2938 origin: wgpu::Origin3d::ZERO,
2939 aspect: wgpu::TextureAspect::All,
2940 },
2941 &y[..y_expected],
2942 wgpu::TexelCopyBufferLayout {
2943 offset: 0,
2944 bytes_per_row: Some(w * 2),
2945 rows_per_image: Some(h),
2946 },
2947 wgpu::Extent3d {
2948 width: w,
2949 height: h,
2950 depth_or_array_layers: 1,
2951 },
2952 );
2953 self.queue.write_texture(
2954 wgpu::TexelCopyTextureInfo {
2955 texture: tex_uv,
2956 mip_level: 0,
2957 origin: wgpu::Origin3d::ZERO,
2958 aspect: wgpu::TextureAspect::All,
2959 },
2960 &uv[..uv_expected],
2961 wgpu::TexelCopyBufferLayout {
2962 offset: 0,
2963 bytes_per_row: Some(uv_w * 4),
2964 rows_per_image: Some(uv_h),
2965 },
2966 wgpu::Extent3d {
2967 width: uv_w,
2968 height: uv_h,
2969 depth_or_array_layers: 1,
2970 },
2971 );
2972
2973 self.evict_budget_excess();
2974 Ok(())
2975 }
2976
2977 #[cfg(target_os = "linux")]
2978 pub fn set_image_dmabuf(
2979 &mut self,
2980 handle: u64,
2981 w: u32,
2982 h: u32,
2983 fds: Vec<std::os::unix::io::OwnedFd>,
2984 modifier: u64,
2985 strides: Vec<u32>,
2986 offsets: Vec<u64>,
2987 color_info: ColorInfo,
2988 ) -> anyhow::Result<()> {
2989 log::info!(
2990 "set_image_dmabuf handle={handle} {}x{} fds={} modifier=0x{modifier:x}",
2991 w,
2992 h,
2993 fds.len()
2994 );
2995
2996 self.remove_image(handle);
2997
2998 let yuv = color_info.to_yuv_transform();
2999 let yuv_raw = YuvTransformRaw {
3000 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
3001 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
3002 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
3003 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
3004 };
3005
3006 if fds.len() != 2 {
3007 return Err(anyhow::anyhow!(
3008 "unsupported fd count {} - need exactly 2 for separate Y/UV planes",
3009 fds.len()
3010 ));
3011 }
3012
3013 let uv_w = w.div_ceil(2);
3014 let uv_h = h.div_ceil(2);
3015
3016 let hal_y_desc = wgpu::hal::TextureDescriptor {
3017 label: Some("dmabuf y"),
3018 size: wgpu::Extent3d {
3019 width: w,
3020 height: h,
3021 depth_or_array_layers: 1,
3022 },
3023 mip_level_count: 1,
3024 sample_count: 1,
3025 dimension: wgpu::TextureDimension::D2,
3026 format: wgpu::TextureFormat::R8Unorm,
3027 usage: wgpu::wgt::TextureUses::RESOURCE,
3028 memory_flags: wgpu::hal::MemoryFlags::empty(),
3029 view_formats: vec![],
3030 };
3031 let hal_uv_desc = wgpu::hal::TextureDescriptor {
3032 label: Some("dmabuf uv"),
3033 size: wgpu::Extent3d {
3034 width: uv_w,
3035 height: uv_h,
3036 depth_or_array_layers: 1,
3037 },
3038 mip_level_count: 1,
3039 sample_count: 1,
3040 dimension: wgpu::TextureDimension::D2,
3041 format: wgpu::TextureFormat::Rg8Unorm,
3042 usage: wgpu::wgt::TextureUses::RESOURCE,
3043 memory_flags: wgpu::hal::MemoryFlags::empty(),
3044 view_formats: vec![],
3045 };
3046
3047 let wgpu_y_desc = wgpu::TextureDescriptor {
3048 label: Some("dmabuf y"),
3049 size: wgpu::Extent3d {
3050 width: w,
3051 height: h,
3052 depth_or_array_layers: 1,
3053 },
3054 mip_level_count: 1,
3055 sample_count: 1,
3056 dimension: wgpu::TextureDimension::D2,
3057 format: wgpu::TextureFormat::R8Unorm,
3058 usage: wgpu::TextureUsages::TEXTURE_BINDING,
3059 view_formats: &[],
3060 };
3061 let wgpu_uv_desc = wgpu::TextureDescriptor {
3062 label: Some("dmabuf uv"),
3063 size: wgpu::Extent3d {
3064 width: uv_w,
3065 height: uv_h,
3066 depth_or_array_layers: 1,
3067 },
3068 mip_level_count: 1,
3069 sample_count: 1,
3070 dimension: wgpu::TextureDimension::D2,
3071 format: wgpu::TextureFormat::Rg8Unorm,
3072 usage: wgpu::TextureUsages::TEXTURE_BINDING,
3073 view_formats: &[],
3074 };
3075
3076 let (tex_y, view_y, tex_uv, view_uv) = unsafe {
3077 let hal_guard = self
3078 .device
3079 .as_hal::<wgpu::hal::vulkan::Api>()
3080 .ok_or_else(|| {
3081 log::warn!("as_hal::<vulkan::Api> returned None");
3082 anyhow::anyhow!("Device is not Vulkan")
3083 })?;
3084
3085 let mut fds = fds;
3086 let uv_fd = fds.remove(1);
3087 let y_fd = fds.remove(0);
3088
3089 let yt = hal_guard
3090 .texture_from_dmabuf_fd(y_fd, &hal_y_desc, modifier, strides[0] as u64, offsets[0])
3091 .map_err(|e| anyhow::anyhow!("import Y dmabuf: {e:?}"))?;
3092 log::info!("imported Y dmabuf OK");
3093
3094 let uvt = hal_guard
3095 .texture_from_dmabuf_fd(
3096 uv_fd,
3097 &hal_uv_desc,
3098 modifier,
3099 strides[1] as u64,
3100 offsets[1],
3101 )
3102 .map_err(|e| anyhow::anyhow!("import UV dmabuf: {e:?}"))?;
3103 log::info!("imported UV dmabuf OK");
3104
3105 drop(hal_guard);
3106
3107 let tex_y = self
3108 .device
3109 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
3110 yt,
3111 &wgpu_y_desc,
3112 wgpu::wgt::TextureUses::UNINITIALIZED,
3113 );
3114 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
3115
3116 let tex_uv = self
3117 .device
3118 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
3119 uvt,
3120 &wgpu_uv_desc,
3121 wgpu::wgt::TextureUses::UNINITIALIZED,
3122 );
3123 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
3124
3125 (tex_y, view_y, tex_uv, view_uv)
3126 };
3127
3128 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3129 label: Some("dmabuf yuv transform"),
3130 size: std::mem::size_of::<YuvTransformRaw>() as u64,
3131 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3132 mapped_at_creation: false,
3133 });
3134 self.queue
3135 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3136
3137 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3138 label: Some("dmabuf nv12 bind"),
3139 layout: &self.image_bind_layout_nv12,
3140 entries: &[
3141 wgpu::BindGroupEntry {
3142 binding: 0,
3143 resource: wgpu::BindingResource::TextureView(&view_y),
3144 },
3145 wgpu::BindGroupEntry {
3146 binding: 1,
3147 resource: wgpu::BindingResource::TextureView(&view_uv),
3148 },
3149 wgpu::BindGroupEntry {
3150 binding: 2,
3151 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3152 },
3153 wgpu::BindGroupEntry {
3154 binding: 3,
3155 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3156 buffer: &yuv_buf,
3157 offset: 0,
3158 size: None,
3159 }),
3160 },
3161 ],
3162 });
3163
3164 let bytes = (w as u64) * (h as u64)
3165 + (uv_w as u64) * (uv_h as u64) * 2
3166 + std::mem::size_of::<YuvTransformRaw>() as u64;
3167
3168 self.images.insert(
3169 handle,
3170 ImageTex::Nv12 {
3171 tex_y,
3172 tex_uv,
3173 bind,
3174 yuv_buf,
3175 w,
3176 h,
3177 color_info,
3178 last_used_frame: self.frame_index,
3179 bytes,
3180 },
3181 );
3182
3183 self.evict_budget_excess();
3184 Ok(())
3185 }
3186
3187 pub fn remove_image(&mut self, handle: u64) {
3188 if let Some(img) = self.images.remove(&handle) {
3189 let b = match &img {
3190 ImageTex::Rgba { bytes, .. } => *bytes,
3191 ImageTex::Nv12 { bytes, .. } => *bytes,
3192 ImageTex::User { bytes, .. } => *bytes,
3193 };
3194 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
3195 }
3196 self.retained.remove(&handle);
3197 }
3198
3199 fn evict_image_gpu(&mut self, handle: u64) -> u64 {
3200 let Some(img) = self.images.remove(&handle) else {
3201 return 0;
3202 };
3203 let b = match &img {
3204 ImageTex::Rgba { bytes, .. } => *bytes,
3205 ImageTex::Nv12 { bytes, .. } => *bytes,
3206 ImageTex::User { bytes, .. } => *bytes,
3207 };
3208 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
3209 b
3210 }
3211
3212 fn revive_retained_image(&mut self, handle: u64) -> bool {
3213 if self.images.contains_key(&handle) {
3214 return true;
3215 }
3216 let Some(r) = self.retained.get(&handle).cloned() else {
3217 return false;
3218 };
3219 let (tex, bind) = self.create_rgba_tex(r.w, r.h, r.format);
3220
3221 self.queue.write_texture(
3222 wgpu::TexelCopyTextureInfo {
3223 texture: &tex,
3224 mip_level: 0,
3225 origin: wgpu::Origin3d::ZERO,
3226 aspect: wgpu::TextureAspect::All,
3227 },
3228 &r.rgba,
3229 wgpu::TexelCopyBufferLayout {
3230 offset: 0,
3231 bytes_per_row: Some(4 * r.w),
3232 rows_per_image: Some(r.h),
3233 },
3234 wgpu::Extent3d {
3235 width: r.w,
3236 height: r.h,
3237 depth_or_array_layers: 1,
3238 },
3239 );
3240
3241 let bytes = (r.w as u64) * (r.h as u64) * 4;
3242 self.image_bytes_total += bytes;
3243 self.images.insert(
3244 handle,
3245 ImageTex::Rgba {
3246 tex,
3247 bind,
3248 w: r.w,
3249 h: r.h,
3250 format: r.format,
3251 last_used_frame: self.frame_index,
3252 bytes,
3253 },
3254 );
3255 true
3256 }
3257
3258 fn resolve_image_for_draw(&mut self, handle: u64) -> Option<(u32, u32, bool)> {
3259 if let Some(t) = self.images.get_mut(&handle) {
3260 return match t {
3261 ImageTex::Rgba {
3262 w,
3263 h,
3264 last_used_frame,
3265 ..
3266 } => {
3267 *last_used_frame = self.frame_index;
3268 Some((*w, *h, false))
3269 }
3270 ImageTex::User {
3271 w,
3272 h,
3273 last_used_frame,
3274 ..
3275 } => {
3276 *last_used_frame = self.frame_index;
3277 Some((*w, *h, false))
3278 }
3279 ImageTex::Nv12 {
3280 w,
3281 h,
3282 last_used_frame,
3283 ..
3284 } => {
3285 *last_used_frame = self.frame_index;
3286 Some((*w, *h, true))
3287 }
3288 };
3289 }
3290 if self.revive_retained_image(handle)
3291 && let Some(ImageTex::Rgba {
3292 w,
3293 h,
3294 last_used_frame,
3295 ..
3296 }) = self.images.get_mut(&handle)
3297 {
3298 *last_used_frame = self.frame_index;
3299 return Some((*w, *h, false));
3300 }
3301 None
3302 }
3303
3304 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
3306 let handle = self.next_image_handle;
3307 self.next_image_handle += 1;
3308 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
3309 log::error!("Failed to register image: {e}");
3310 }
3311 handle
3312 }
3313
3314 fn evict_unused_images(&mut self) {
3315 let now = self.frame_index;
3316 let evict_after = self.image_evict_after_frames;
3317
3318 let mut to_evict = Vec::new();
3321 for (h, t) in self.images.iter() {
3322 let last = match t {
3323 ImageTex::Rgba {
3324 last_used_frame, ..
3325 } => *last_used_frame,
3326 ImageTex::User {
3327 last_used_frame, ..
3328 } => *last_used_frame,
3329 ImageTex::Nv12 {
3330 last_used_frame, ..
3331 } => *last_used_frame,
3332 };
3333 if now.saturating_sub(last) > evict_after {
3334 to_evict.push(*h);
3335 }
3336 }
3337 for h in to_evict {
3338 if self.retained.contains_key(&h) {
3339 self.evict_image_gpu(h);
3340 } else {
3341 self.remove_image(h);
3342 }
3343 }
3344
3345 self.evict_budget_excess();
3346 }
3347
3348 fn evict_budget_excess(&mut self) {
3349 if self.image_bytes_total <= self.image_budget_bytes {
3350 return;
3351 }
3352 let mut candidates: Vec<(u64, u64, u64)> = self
3354 .images
3355 .iter()
3356 .map(|(h, t)| {
3357 let (last, bytes) = match t {
3358 ImageTex::Rgba {
3359 last_used_frame,
3360 bytes,
3361 ..
3362 } => (*last_used_frame, *bytes),
3363 ImageTex::User {
3364 last_used_frame,
3365 bytes,
3366 ..
3367 } => (*last_used_frame, *bytes),
3368 ImageTex::Nv12 {
3369 last_used_frame,
3370 bytes,
3371 ..
3372 } => (*last_used_frame, *bytes),
3373 };
3374 (*h, last, bytes)
3375 })
3376 .collect();
3377
3378 candidates.sort_by_key(|k| k.1);
3380
3381 let now = self.frame_index;
3382 for (h, last, _bytes) in candidates {
3383 if self.image_bytes_total <= self.image_budget_bytes {
3384 break;
3385 }
3386 if last == now {
3388 continue;
3389 }
3390 if self.retained.contains_key(&h) {
3391 self.evict_image_gpu(h);
3392 } else {
3393 self.remove_image(h);
3394 }
3395 }
3396 }
3397
3398 pub fn set_pixels_per_point(&mut self, ppp: f32) {
3400 self.pixels_per_point = ppp.max(0.5).min(8.0);
3401 }
3402
3403 pub fn set_working_space(&mut self, enabled: bool) {
3407 if enabled == self.working_space {
3408 return;
3409 }
3410 self.working_space = enabled;
3411 if enabled {
3412 self.ensure_display_pipeline();
3413 self.recreate_working_space_texture();
3414 } else {
3415 self.ws_tex = None;
3416 self.ws_view = None;
3417 self.ws_bind = None;
3418 }
3419 }
3420
3421 fn ensure_display_pipeline(&mut self) {
3422 if self.display_pipeline.is_some() {
3423 return;
3424 }
3425
3426 let layout = self
3427 .device
3428 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3429 label: Some("display transform layout"),
3430 entries: &[
3431 wgpu::BindGroupLayoutEntry {
3432 binding: 0,
3433 visibility: wgpu::ShaderStages::FRAGMENT,
3434 ty: wgpu::BindingType::Texture {
3435 multisampled: false,
3436 view_dimension: wgpu::TextureViewDimension::D2,
3437 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3438 },
3439 count: None,
3440 },
3441 wgpu::BindGroupLayoutEntry {
3442 binding: 1,
3443 visibility: wgpu::ShaderStages::FRAGMENT,
3444 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3445 count: None,
3446 },
3447 ],
3448 });
3449 self.display_layout = Some(layout);
3450
3451 let shader = self
3452 .device
3453 .create_shader_module(wgpu::ShaderModuleDescriptor {
3454 label: Some("display_transform.wgsl"),
3455 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
3456 "shaders/display_transform.wgsl"
3457 ))),
3458 });
3459
3460 let pipeline_layout = self
3461 .device
3462 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3463 label: Some("display transform pipeline layout"),
3464 bind_group_layouts: &[None, self.display_layout.as_ref()],
3465 immediate_size: 0,
3466 });
3467
3468 let pipeline = self
3469 .device
3470 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3471 label: Some("display transform pipeline"),
3472 layout: Some(&pipeline_layout),
3473 vertex: wgpu::VertexState {
3474 module: &shader,
3475 entry_point: Some("vs_main"),
3476 buffers: &[],
3477 compilation_options: wgpu::PipelineCompilationOptions::default(),
3478 },
3479 fragment: Some(wgpu::FragmentState {
3480 module: &shader,
3481 entry_point: Some("fs_main"),
3482 targets: &[Some(wgpu::ColorTargetState {
3483 format: self.output_format,
3484 blend: None,
3485 write_mask: wgpu::ColorWrites::ALL,
3486 })],
3487 compilation_options: wgpu::PipelineCompilationOptions::default(),
3488 }),
3489 primitive: wgpu::PrimitiveState::default(),
3490 depth_stencil: None,
3491 multisample: wgpu::MultisampleState::default(),
3492 multiview_mask: None,
3493 cache: None,
3494 });
3495 self.display_pipeline = Some(pipeline);
3496 }
3497
3498 pub fn resize(&mut self, width: u32, height: u32) {
3503 self.output_width = width;
3504 self.output_height = height;
3505 self.recreate_msaa_and_depth_stencil();
3506 self.recreate_working_space_texture();
3507 }
3508
3509 fn recreate_working_space_texture(&mut self) {
3510 if !self.working_space {
3511 return;
3512 }
3513 let w = self.output_width.max(1);
3514 let h = self.output_height.max(1);
3515
3516 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3517 label: Some("working space"),
3518 size: wgpu::Extent3d {
3519 width: w,
3520 height: h,
3521 depth_or_array_layers: 1,
3522 },
3523 mip_level_count: 1,
3524 sample_count: 1,
3525 dimension: wgpu::TextureDimension::D2,
3526 format: wgpu::TextureFormat::Rgba16Float,
3527 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
3528 view_formats: &[],
3529 });
3530 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3531
3532 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3533 label: Some("working space bind"),
3534 layout: self.display_layout.as_ref().unwrap(),
3535 entries: &[
3536 wgpu::BindGroupEntry {
3537 binding: 0,
3538 resource: wgpu::BindingResource::TextureView(&view),
3539 },
3540 wgpu::BindGroupEntry {
3541 binding: 1,
3542 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3543 },
3544 ],
3545 });
3546
3547 self.ws_tex = Some(tex);
3548 self.ws_view = Some(view);
3549 self.ws_bind = Some(bind);
3550 }
3551
3552 fn recreate_msaa_and_depth_stencil(&mut self) {
3553 if self.msaa_samples > 1 {
3554 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3555 label: Some("msaa color"),
3556 size: wgpu::Extent3d {
3557 width: self.output_width.max(1),
3558 height: self.output_height.max(1),
3559 depth_or_array_layers: 1,
3560 },
3561 mip_level_count: 1,
3562 sample_count: self.msaa_samples,
3563 dimension: wgpu::TextureDimension::D2,
3564 format: self.output_format,
3565 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3566 view_formats: &[],
3567 });
3568 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3569 self.msaa_tex = Some(tex);
3570 self.msaa_view = Some(view);
3571 } else {
3572 self.msaa_tex = None;
3573 self.msaa_view = None;
3574 }
3575
3576 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
3577 label: Some("depth-stencil (stencil clips)"),
3578 size: wgpu::Extent3d {
3579 width: self.output_width.max(1),
3580 height: self.output_height.max(1),
3581 depth_or_array_layers: 1,
3582 },
3583 mip_level_count: 1,
3584 sample_count: self.msaa_samples,
3585 dimension: wgpu::TextureDimension::D2,
3586 format: wgpu::TextureFormat::Depth24PlusStencil8,
3587 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3588 view_formats: &[],
3589 });
3590 self.depth_stencil_view = self
3591 .depth_stencil_tex
3592 .create_view(&wgpu::TextureViewDescriptor::default());
3593 }
3594
3595 fn get_or_create_layer(
3596 &mut self,
3597 layer_id: u32,
3598 width: u32,
3599 height: u32,
3600 rect: repose_core::Rect,
3601 ) {
3602 let needs_alloc = match self.layer_pool.get(&layer_id) {
3603 Some(lt) => lt.width != width || lt.height != height,
3604 None => true,
3605 };
3606 if !needs_alloc {
3607 if let Some(lt) = self.layer_pool.get_mut(&layer_id) {
3608 lt.rect_px = (rect.x, rect.y, rect.w, rect.h);
3609 }
3610 return;
3611 }
3612 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3613 label: Some("graphics layer"),
3614 size: wgpu::Extent3d {
3615 width: width.max(1),
3616 height: height.max(1),
3617 depth_or_array_layers: 1,
3618 },
3619 mip_level_count: 1,
3620 sample_count: 1,
3621 dimension: wgpu::TextureDimension::D2,
3622 format: self.output_format,
3623 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
3624 view_formats: &[],
3625 });
3626 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3627 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3628 label: Some("layer bind"),
3629 layout: &self.image_bind_layout_rgba,
3630 entries: &[
3631 wgpu::BindGroupEntry {
3632 binding: 0,
3633 resource: wgpu::BindingResource::TextureView(&view),
3634 },
3635 wgpu::BindGroupEntry {
3636 binding: 1,
3637 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
3638 },
3639 ],
3640 });
3641 let bind_linear = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3642 label: Some("layer bind linear"),
3643 layout: &self.image_bind_layout_rgba,
3644 entries: &[
3645 wgpu::BindGroupEntry {
3646 binding: 0,
3647 resource: wgpu::BindingResource::TextureView(&view),
3648 },
3649 wgpu::BindGroupEntry {
3650 binding: 1,
3651 resource: wgpu::BindingResource::Sampler(&self.layer_sampler_linear),
3652 },
3653 ],
3654 });
3655 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
3656 label: Some("graphics layer depth-stencil"),
3657 size: wgpu::Extent3d {
3658 width: width.max(1),
3659 height: height.max(1),
3660 depth_or_array_layers: 1,
3661 },
3662 mip_level_count: 1,
3663 sample_count: 1,
3664 dimension: wgpu::TextureDimension::D2,
3665 format: wgpu::TextureFormat::Depth24PlusStencil8,
3666 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3667 view_formats: &[],
3668 });
3669 let depth_stencil_view =
3670 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
3671 self.layer_pool.insert(
3672 layer_id,
3673 LayerTarget {
3674 view,
3675 bind,
3676 bind_linear,
3677 depth_stencil_view,
3678 width,
3679 height,
3680 rect_px: (rect.x, rect.y, rect.w, rect.h),
3681 },
3682 );
3683 }
3684
3685 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
3686 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3687 label: Some("atlas bind"),
3688 layout: &self.text_bind_layout,
3689 entries: &[
3690 wgpu::BindGroupEntry {
3691 binding: 0,
3692 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
3693 },
3694 wgpu::BindGroupEntry {
3695 binding: 1,
3696 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
3697 },
3698 ],
3699 })
3700 }
3701
3702 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
3703 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3704 label: Some("atlas bind color"),
3705 layout: &self.text_bind_layout,
3706 entries: &[
3707 wgpu::BindGroupEntry {
3708 binding: 0,
3709 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
3710 },
3711 wgpu::BindGroupEntry {
3712 binding: 1,
3713 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
3714 },
3715 ],
3716 })
3717 }
3718
3719 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
3720 let keyp = (key, px.to_bits());
3721 if let Some(info) = self.atlas_mask.map.get(&keyp) {
3722 return Some(*info);
3723 }
3724
3725 let gb = repose_text::rasterize(key, px)?;
3726 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
3727 return None;
3728 }
3729
3730 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
3731
3732 let w = gb.w.max(1);
3733 let h = gb.h.max(1);
3734
3735 if !self.alloc_space_mask(w, h) {
3736 self.grow_mask_and_rebuild();
3737 }
3738 if !self.alloc_space_mask(w, h) {
3739 return None;
3740 }
3741 let x = self.atlas_mask.next_x;
3742 let y = self.atlas_mask.next_y;
3743 self.atlas_mask.next_x += w + 1;
3744 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
3745
3746 let layout = wgpu::TexelCopyBufferLayout {
3747 offset: 0,
3748 bytes_per_row: Some(w),
3749 rows_per_image: Some(h),
3750 };
3751 let size = wgpu::Extent3d {
3752 width: w,
3753 height: h,
3754 depth_or_array_layers: 1,
3755 };
3756 self.queue.write_texture(
3757 wgpu::TexelCopyTextureInfoBase {
3758 texture: &self.atlas_mask.tex,
3759 mip_level: 0,
3760 origin: wgpu::Origin3d { x, y, z: 0 },
3761 aspect: wgpu::TextureAspect::All,
3762 },
3763 &coverage,
3764 layout,
3765 size,
3766 );
3767
3768 let info = GlyphInfo {
3769 u0: x as f32 / self.atlas_mask.size as f32,
3770 v0: y as f32 / self.atlas_mask.size as f32,
3771 u1: (x + w) as f32 / self.atlas_mask.size as f32,
3772 v1: (y + h) as f32 / self.atlas_mask.size as f32,
3773 w: w as f32,
3774 h: h as f32,
3775 };
3776 self.atlas_mask.map.insert(keyp, info);
3777 Some(info)
3778 }
3779
3780 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
3781 let keyp = (key, px.to_bits());
3782 if let Some(info) = self.atlas_color.map.get(&keyp) {
3783 return Some(*info);
3784 }
3785 let gb = repose_text::rasterize(key, px)?;
3786 if !matches!(gb.content, repose_text::SwashContent::Color) {
3787 return None;
3788 }
3789 let w = gb.w.max(1);
3790 let h = gb.h.max(1);
3791 if !self.alloc_space_color(w, h) {
3792 self.grow_color_and_rebuild();
3793 }
3794 if !self.alloc_space_color(w, h) {
3795 return None;
3796 }
3797 let x = self.atlas_color.next_x;
3798 let y = self.atlas_color.next_y;
3799 self.atlas_color.next_x += w + 1;
3800 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
3801
3802 let layout = wgpu::TexelCopyBufferLayout {
3803 offset: 0,
3804 bytes_per_row: Some(w * 4),
3805 rows_per_image: Some(h),
3806 };
3807 let size = wgpu::Extent3d {
3808 width: w,
3809 height: h,
3810 depth_or_array_layers: 1,
3811 };
3812 self.queue.write_texture(
3813 wgpu::TexelCopyTextureInfoBase {
3814 texture: &self.atlas_color.tex,
3815 mip_level: 0,
3816 origin: wgpu::Origin3d { x, y, z: 0 },
3817 aspect: wgpu::TextureAspect::All,
3818 },
3819 &gb.data,
3820 layout,
3821 size,
3822 );
3823 let info = GlyphInfo {
3824 u0: x as f32 / self.atlas_color.size as f32,
3825 v0: y as f32 / self.atlas_color.size as f32,
3826 u1: (x + w) as f32 / self.atlas_color.size as f32,
3827 v1: (y + h) as f32 / self.atlas_color.size as f32,
3828 w: w as f32,
3829 h: h as f32,
3830 };
3831 self.atlas_color.map.insert(keyp, info);
3832 Some(info)
3833 }
3834
3835 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
3836 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
3837 self.atlas_mask.next_x = 1;
3838 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
3839 self.atlas_mask.row_h = 0;
3840 }
3841 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
3842 return false;
3843 }
3844 true
3845 }
3846
3847 fn grow_mask_and_rebuild(&mut self) {
3848 let new_size = (self.atlas_mask.size * 2).min(4096);
3849 if new_size == self.atlas_mask.size {
3850 return;
3851 }
3852 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3853 label: Some("glyph atlas A8 (grown)"),
3854 size: wgpu::Extent3d {
3855 width: new_size,
3856 height: new_size,
3857 depth_or_array_layers: 1,
3858 },
3859 mip_level_count: 1,
3860 sample_count: 1,
3861 dimension: wgpu::TextureDimension::D2,
3862 format: wgpu::TextureFormat::R8Unorm,
3863 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3864 view_formats: &[],
3865 });
3866 self.atlas_mask.tex = tex;
3867 self.atlas_mask.view = self
3868 .atlas_mask
3869 .tex
3870 .create_view(&wgpu::TextureViewDescriptor::default());
3871 self.atlas_mask.size = new_size;
3872 self.atlas_mask.next_x = 1;
3873 self.atlas_mask.next_y = 1;
3874 self.atlas_mask.row_h = 0;
3875 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
3876 self.atlas_mask.map.clear();
3877 for (k, px_bits) in keys {
3878 let _ = self.upload_glyph_mask(k, f32::from_bits(px_bits));
3879 }
3880 }
3881
3882 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
3883 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
3884 self.atlas_color.next_x = 1;
3885 self.atlas_color.next_y += self.atlas_color.row_h + 1;
3886 self.atlas_color.row_h = 0;
3887 }
3888 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
3889 return false;
3890 }
3891 true
3892 }
3893
3894 fn grow_color_and_rebuild(&mut self) {
3895 let new_size = (self.atlas_color.size * 2).min(4096);
3896 if new_size == self.atlas_color.size {
3897 return;
3898 }
3899 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3900 label: Some("glyph atlas RGBA (grown)"),
3901 size: wgpu::Extent3d {
3902 width: new_size,
3903 height: new_size,
3904 depth_or_array_layers: 1,
3905 },
3906 mip_level_count: 1,
3907 sample_count: 1,
3908 dimension: wgpu::TextureDimension::D2,
3909 format: wgpu::TextureFormat::Rgba8UnormSrgb,
3910 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3911 view_formats: &[],
3912 });
3913 self.atlas_color.tex = tex;
3914 self.atlas_color.view = self
3915 .atlas_color
3916 .tex
3917 .create_view(&wgpu::TextureViewDescriptor::default());
3918 self.atlas_color.size = new_size;
3919 self.atlas_color.next_x = 1;
3920 self.atlas_color.next_y = 1;
3921 self.atlas_color.row_h = 0;
3922 let keys: Vec<(repose_text::GlyphKey, u32)> =
3923 self.atlas_color.map.keys().copied().collect();
3924 self.atlas_color.map.clear();
3925 for (k, px_bits) in keys {
3926 let _ = self.upload_glyph_color(k, f32::from_bits(px_bits));
3927 }
3928 }
3929}
3930
3931fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
3932 match brush {
3933 Brush::Solid(c) => (
3934 0u32,
3935 c.to_linear(),
3936 [0.0, 0.0, 0.0, 0.0],
3937 [0.0, 0.0],
3938 [0.0, 1.0],
3939 ),
3940 Brush::Linear {
3941 start,
3942 end,
3943 start_color,
3944 end_color,
3945 } => (
3946 1u32,
3947 start_color.to_linear(),
3948 end_color.to_linear(),
3949 [start.x, start.y],
3950 [end.x, end.y],
3951 ),
3952 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
3953 }
3954}
3955
3956fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
3957 match brush {
3958 Brush::Solid(c) => c.to_linear(),
3959 Brush::Linear { start_color, .. } => start_color.to_linear(),
3960 _ => [0.0; 4],
3961 }
3962}
3963
3964fn init_atlas_mask(device: &wgpu::Device) -> AtlasA8 {
3965 let size = 1024u32;
3966 let tex = device.create_texture(&wgpu::TextureDescriptor {
3967 label: Some("glyph atlas A8"),
3968 size: wgpu::Extent3d {
3969 width: size,
3970 height: size,
3971 depth_or_array_layers: 1,
3972 },
3973 mip_level_count: 1,
3974 sample_count: 1,
3975 dimension: wgpu::TextureDimension::D2,
3976 format: wgpu::TextureFormat::R8Unorm,
3977 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3978 view_formats: &[],
3979 });
3980 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3981 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
3982 label: Some("glyph atlas sampler A8"),
3983 address_mode_u: wgpu::AddressMode::ClampToEdge,
3984 address_mode_v: wgpu::AddressMode::ClampToEdge,
3985 address_mode_w: wgpu::AddressMode::ClampToEdge,
3986 mag_filter: wgpu::FilterMode::Linear,
3987 min_filter: wgpu::FilterMode::Linear,
3988 mipmap_filter: wgpu::MipmapFilterMode::Linear,
3989 ..Default::default()
3990 });
3991
3992 AtlasA8 {
3993 tex,
3994 view,
3995 sampler,
3996 size,
3997 next_x: 1,
3998 next_y: 1,
3999 row_h: 0,
4000 map: HashMap::new(),
4001 }
4002}
4003
4004fn init_atlas_color(device: &wgpu::Device) -> AtlasRGBA {
4005 let size = 1024u32;
4006 let tex = device.create_texture(&wgpu::TextureDescriptor {
4007 label: Some("glyph atlas RGBA"),
4008 size: wgpu::Extent3d {
4009 width: size,
4010 height: size,
4011 depth_or_array_layers: 1,
4012 },
4013 mip_level_count: 1,
4014 sample_count: 1,
4015 dimension: wgpu::TextureDimension::D2,
4016 format: wgpu::TextureFormat::Rgba8UnormSrgb,
4017 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4018 view_formats: &[],
4019 });
4020 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4021 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4022 label: Some("glyph atlas sampler RGBA"),
4023 address_mode_u: wgpu::AddressMode::ClampToEdge,
4024 address_mode_v: wgpu::AddressMode::ClampToEdge,
4025 address_mode_w: wgpu::AddressMode::ClampToEdge,
4026 mag_filter: wgpu::FilterMode::Linear,
4027 min_filter: wgpu::FilterMode::Linear,
4028 mipmap_filter: wgpu::MipmapFilterMode::Linear,
4029 ..Default::default()
4030 });
4031 AtlasRGBA {
4032 tex,
4033 view,
4034 sampler,
4035 size,
4036 next_x: 1,
4037 next_y: 1,
4038 row_h: 0,
4039 map: HashMap::new(),
4040 }
4041}
4042
4043#[cfg(feature = "winit-surface")]
4044impl RenderBackend for WgpuSurfaceBackend {
4045 fn configure_surface(&mut self, width: u32, height: u32) {
4046 if width == 0 || height == 0 {
4047 return;
4048 }
4049 self.renderer.output_width = width;
4050 self.renderer.output_height = height;
4051 if let Some(ref mut config) = self.surface_config {
4052 config.width = width;
4053 config.height = height;
4054 }
4055 if let (Some(surface), Some(config)) = (self.surface.as_ref(), self.surface_config.as_ref())
4056 {
4057 surface.configure(&self.renderer.device, config);
4058 }
4059 self.renderer.recreate_msaa_and_depth_stencil();
4060 self.renderer.recreate_working_space_texture();
4061 }
4062
4063 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
4064 let surface = self.surface.as_ref().expect("WgpuSurfaceBackend::frame() requires a surface (use from_device + render_to_view instead)");
4065 let surface_config = self
4066 .surface_config
4067 .as_ref()
4068 .expect("surface_config required for frame()");
4069
4070 self.renderer.frame_index = self.renderer.frame_index.wrapping_add(1);
4071 self.renderer.slug_cache.next_frame();
4072
4073 if self.renderer.output_width == 0 || self.renderer.output_height == 0 {
4074 return;
4075 }
4076
4077 let mut retries = 0u32;
4078 const MAX_RETRIES: u32 = 4;
4079 let frame = loop {
4080 match surface.get_current_texture() {
4081 wgpu::CurrentSurfaceTexture::Success(f) => break f,
4082 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
4083 log::warn!("suboptimal surface; reconfiguring");
4084 surface.configure(&self.renderer.device, surface_config);
4085 break f;
4086 }
4087 wgpu::CurrentSurfaceTexture::Outdated => {
4088 retries += 1;
4089 if retries >= MAX_RETRIES {
4090 log::warn!(
4091 "surface outdated persisted after {MAX_RETRIES} retries; skipping frame"
4092 );
4093 return;
4094 }
4095 log::warn!("surface outdated; reconfiguring");
4096 surface.configure(&self.renderer.device, surface_config);
4097 }
4098 wgpu::CurrentSurfaceTexture::Lost => {
4099 retries += 1;
4100 if retries >= MAX_RETRIES {
4101 log::warn!(
4102 "surface lost persisted after {MAX_RETRIES} retries; skipping frame"
4103 );
4104 return;
4105 }
4106 log::warn!("surface lost; reconfiguring");
4107 surface.configure(&self.renderer.device, surface_config);
4108 }
4109 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
4110 request_frame();
4111 return;
4112 }
4113 wgpu::CurrentSurfaceTexture::Validation => {
4114 retries += 1;
4115 if retries >= MAX_RETRIES {
4116 log::warn!(
4117 "surface validation persisted after {MAX_RETRIES} retries; skipping frame"
4118 );
4119 return;
4120 }
4121 surface.configure(&self.renderer.device, surface_config);
4122 }
4123 }
4124 };
4125
4126 let swap_view = if let Some(view_format) = self
4127 .surface_config
4128 .as_ref()
4129 .and_then(|c| c.view_formats.iter().find(|f| f.is_srgb()).copied())
4130 {
4131 frame.texture.create_view(&wgpu::TextureViewDescriptor {
4132 format: Some(view_format),
4133 ..Default::default()
4134 })
4135 } else {
4136 frame
4137 .texture
4138 .create_view(&wgpu::TextureViewDescriptor::default())
4139 };
4140 let mut encoder =
4141 self.renderer
4142 .device
4143 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4144 label: Some("frame encoder"),
4145 });
4146
4147 let clear_color = Some([
4148 scene.clear_color.0 as f64 / 255.0,
4149 scene.clear_color.1 as f64 / 255.0,
4150 scene.clear_color.2 as f64 / 255.0,
4151 scene.clear_color.3 as f64 / 255.0,
4152 ]);
4153
4154 self.renderer
4155 .render_scene_to_encoder(scene, &mut encoder, &swap_view, clear_color);
4156
4157 {
4160 let _reset = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
4161 label: Some("webgl color_mask reset before present"),
4162 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
4163 view: &swap_view,
4164 resolve_target: None,
4165 ops: wgpu::Operations {
4166 load: wgpu::LoadOp::Load,
4167 store: wgpu::StoreOp::Store,
4168 },
4169 depth_slice: None,
4170 })],
4171 depth_stencil_attachment: None,
4172 timestamp_writes: None,
4173 occlusion_query_set: None,
4174 multiview_mask: None,
4175 });
4176 }
4177
4178 self.renderer
4179 .queue
4180 .submit(std::iter::once(encoder.finish()));
4181 if let Err(e) = catch_unwind(AssertUnwindSafe(|| self.renderer.queue.present(frame))) {
4182 log::warn!("queue.present panicked: {:?}", e);
4183 }
4184 }
4185}
4186
4187impl WgpuSceneRenderer {
4188 fn upload_mesh_geometry(&mut self, mesh: &repose_core::VectorMeshData) -> (u64, u32, u64, u32) {
4189 let verts: Vec<MeshVertex> = mesh
4190 .vertices
4191 .iter()
4192 .map(|v| MeshVertex {
4193 pos: v.pos,
4194 color: v.color,
4195 uv: v.uv,
4196 })
4197 .collect();
4198 let vbytes = bytemuck::cast_slice(&verts);
4199 self.mesh_verts
4200 .grow_to_fit(&self.device, vbytes.len() as u64);
4201 let (voff, _) = self.mesh_verts.alloc_write(&self.queue, vbytes);
4202 let ibytes = bytemuck::cast_slice(&mesh.indices);
4203 self.mesh_indices
4204 .grow_to_fit(&self.device, ibytes.len() as u64);
4205 let (ioff, _) = self.mesh_indices.alloc_write(&self.queue, ibytes);
4206 (voff, verts.len() as u32, ioff, mesh.indices.len() as u32)
4207 }
4208
4209 fn alloc_mesh_uniform(&mut self, u: MeshUniform) -> u64 {
4210 if self.mesh_uniform_head + MESH_UNIFORM_SLOT > MESH_UNIFORM_CAP {
4211 log::warn!("mesh uniform buffer overflow; regenerating");
4212 self.recreate_mesh_uniform_buffer();
4213 }
4214 let slot = self.mesh_uniform_head;
4215 self.queue
4216 .write_buffer(&self.mesh_uniform_buf, slot, bytemuck::bytes_of(&u));
4217 self.mesh_uniform_head = slot + MESH_UNIFORM_SLOT;
4218 slot
4219 }
4220
4221 fn recreate_mesh_uniform_buffer(&mut self) {
4222 let new_cap = self.mesh_uniform_head + MESH_UNIFORM_SLOT;
4223 self.mesh_uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4224 label: Some("mesh uniform buffer"),
4225 size: new_cap,
4226 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4227 mapped_at_creation: false,
4228 });
4229 self.mesh_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4230 label: Some("mesh uniform bind"),
4231 layout: &self.mesh_bind_layout,
4232 entries: &[wgpu::BindGroupEntry {
4233 binding: 0,
4234 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
4235 buffer: &self.mesh_uniform_buf,
4236 offset: 0,
4237 size: NonZero::new(MESH_UNIFORM_SLOT),
4238 }),
4239 }],
4240 });
4241 self.mesh_uniform_head = 0;
4242 }
4243
4244 #[allow(clippy::too_many_arguments)]
4245 fn emit_vector_mesh(
4246 &mut self,
4247 current_transform: &Transform,
4248 mesh: &repose_core::VectorMeshData,
4249 transform: [f32; 6],
4250 paint: &repose_core::PaintDesc,
4251 cmds: &mut Vec<Cmd>,
4252 ) {
4253 let affine = combine_mesh_affine(current_transform, transform);
4254 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
4255 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(affine, paint));
4256 cmds.push(Cmd::VectorMesh {
4257 voff,
4258 vcnt,
4259 ioff,
4260 icnt,
4261 uoff,
4262 });
4263 }
4264
4265 pub fn render_scene_to_encoder(
4266 &mut self,
4267 scene: &Scene,
4268 encoder: &mut wgpu::CommandEncoder,
4269 target_view: &wgpu::TextureView,
4270 clear_color_override: Option<[f64; 4]>,
4271 ) {
4272 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
4273 let x0 = (x / fb_w) * 2.0 - 1.0;
4274 let y0 = 1.0 - (y / fb_h) * 2.0;
4275 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
4276 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
4277 let min_x = x0.min(x1);
4278 let min_y = y0.min(y1);
4279 let w_ndc = (x1 - x0).abs();
4280 let h_ndc = (y1 - y0).abs();
4281 [min_x, min_y, w_ndc, h_ndc]
4282 }
4283
4284 fn rect_to_instance_ndc(
4286 rect: repose_core::Rect,
4287 transform: &Transform,
4288 fb_w: f32,
4289 fb_h: f32,
4290 ) -> ([f32; 4], [f32; 2]) {
4291 let cx = rect.x + rect.w * 0.5;
4292 let cy = rect.y + rect.h * 0.5;
4293
4294 let sx = cx * transform.scale_x;
4295 let sy = cy * transform.scale_y;
4296 let cos_a = transform.rotate.cos();
4297 let sin_a = transform.rotate.sin();
4298 let tx = sx * cos_a - sy * sin_a + transform.translate_x;
4299 let ty = sx * sin_a + sy * cos_a + transform.translate_y;
4300
4301 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
4303 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
4304 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
4306 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
4307
4308 ([ndc_cx, ndc_cy, ndc_w, ndc_h], [cos_a, sin_a])
4309 }
4310
4311 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
4312 let mut x = r.x.floor() as i64;
4313 let mut y = r.y.floor() as i64;
4314 let fb_wi = fb_w as i64;
4315 let fb_hi = fb_h as i64;
4316 x = x.clamp(0, fb_wi.saturating_sub(1));
4317 y = y.clamp(0, fb_hi.saturating_sub(1));
4318 let w_req = r.w.ceil().max(1.0) as i64;
4319 let h_req = r.h.ceil().max(1.0) as i64;
4320 let w = (w_req).min(fb_wi - x).max(1);
4321 let h = (h_req).min(fb_hi - y).max(1);
4322 (x as u32, y as u32, w as u32, h as u32)
4323 }
4324
4325 let fb_w = self.output_width as f32;
4326 let fb_h = self.output_height as f32;
4327
4328 let mut passes: Vec<Pass> = Vec::with_capacity(1);
4329 let clear_color = clear_color_override.unwrap_or_else(|| {
4330 [
4331 scene.clear_color.0 as f64 / 255.0,
4332 scene.clear_color.1 as f64 / 255.0,
4333 scene.clear_color.2 as f64 / 255.0,
4334 scene.clear_color.3 as f64 / 255.0,
4335 ]
4336 });
4337 let mut current_pass: Pass = Pass {
4338 target: PassTarget::Surface,
4339 initial_scissor: (0, 0, self.output_width, self.output_height),
4340 clear_color: Some([
4341 clear_color[0] as f32,
4342 clear_color[1] as f32,
4343 clear_color[2] as f32,
4344 clear_color[3] as f32,
4345 ]),
4346 cmds: Vec::with_capacity(scene.nodes.len()),
4347 };
4348 let mut target_stack: Vec<PassTarget> = Vec::new();
4349 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
4350 let mut layer_blurs: Vec<(u32, f32, f32)> = Vec::new();
4351 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
4352
4353 struct Batch {
4354 rects: Vec<RectInstance>,
4355 borders: Vec<BorderInstance>,
4356 ellipses: Vec<EllipseInstance>,
4357 e_borders: Vec<EllipseBorderInstance>,
4358 arcs: Vec<ArcInstance>,
4359 masks: Vec<GlyphInstance>,
4360 colors: Vec<GlyphInstance>,
4361 nv12s: Vec<Nv12Instance>,
4362 }
4363
4364 impl Batch {
4365 fn new() -> Self {
4366 Self {
4367 rects: vec![],
4368 borders: vec![],
4369 ellipses: vec![],
4370 e_borders: vec![],
4371 arcs: vec![],
4372 masks: vec![],
4373 colors: vec![],
4374 nv12s: vec![],
4375 }
4376 }
4377
4378 fn is_empty(&self) -> bool {
4379 self.rects.is_empty()
4380 && self.borders.is_empty()
4381 && self.ellipses.is_empty()
4382 && self.e_borders.is_empty()
4383 && self.arcs.is_empty()
4384 && self.masks.is_empty()
4385 && self.colors.is_empty()
4386 && self.nv12s.is_empty()
4387 }
4388
4389 fn flush(
4390 &mut self,
4391 pipes: (
4392 &mut InstancedPipe<RectInstance>,
4393 &mut InstancedPipe<BorderInstance>,
4394 &mut InstancedPipe<EllipseInstance>,
4395 &mut InstancedPipe<EllipseBorderInstance>,
4396 &mut InstancedPipe<ArcInstance>,
4397 ),
4398 glyph_pipes: (
4399 &mut InstancedPipe<GlyphInstance>,
4400 &mut InstancedPipe<GlyphInstance>,
4401 ),
4402 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
4403 device: &wgpu::Device,
4404 queue: &wgpu::Queue,
4405 cmds: &mut Vec<Cmd>,
4406 ) {
4407 let (rects, borders, ellipses, e_borders, arcs) = pipes;
4408 let (masks, colors) = glyph_pipes;
4409
4410 macro_rules! flush_one {
4411 ($buf:ident, $pipe:expr, $variant:ident) => {
4412 if !self.$buf.is_empty() {
4413 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
4414 cmds.push(Cmd::$variant { off, cnt });
4415 }
4416 self.$buf.clear();
4417 }
4418 };
4419 }
4420
4421 flush_one!(rects, rects, Rect);
4422 flush_one!(borders, borders, Border);
4423 flush_one!(ellipses, ellipses, Ellipse);
4424 flush_one!(e_borders, e_borders, EllipseBorder);
4425 flush_one!(arcs, arcs, Arc);
4426 flush_one!(masks, masks, GlyphsMask);
4427 flush_one!(colors, colors, GlyphsColor);
4428
4429 if !self.nv12s.is_empty() {
4430 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
4431 let _ = (off, cnt);
4432 }
4433 self.nv12s.clear();
4434 }
4435 }
4436 }
4437
4438 self.rects.reset();
4439 self.borders.reset();
4440 self.ellipses.reset();
4441 self.ellipse_borders.reset();
4442 self.arcs.reset();
4443 self.glyph_mask.reset();
4444 self.glyph_color.reset();
4445 self.clip_ring.reset();
4446 self.blur_ring.reset();
4447 self.nv12.reset();
4448
4449 self.slug_ring.reset();
4450 self.mesh_verts.reset();
4451 self.mesh_indices.reset();
4452 self.mesh_uniform_head = 0;
4453 self.mesh_clip_stack.clear();
4454 let mut batch = Batch::new();
4455 let mut slug_verts_local: Vec<slug::TessVertex> = Vec::new();
4456 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
4457 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
4458 let mut clip_cmd_stack: Vec<(u64, u32, bool)> = Vec::with_capacity(8);
4462 let mut root_clip_rect = repose_core::Rect {
4463 x: 0.0,
4464 y: 0.0,
4465 w: fb_w,
4466 h: fb_h,
4467 };
4468 let mut saved_scissor_stack: Vec<repose_core::Rect> = Vec::new();
4469 let mut saved_root_clip_rect = root_clip_rect;
4470
4471 let mut current_prim: Option<&'static str> = None;
4472
4473 macro_rules! flush_if_prim_changed {
4474 ($prim:literal, $pipe:expr) => {
4475 if current_prim != Some($prim) {
4476 flush_batch!();
4477 current_prim = Some($prim);
4478 }
4479 };
4480 }
4481
4482 macro_rules! flush_batch {
4483 () => {
4484 if !batch.is_empty() {
4485 batch.flush(
4486 (
4487 &mut self.rects,
4488 &mut self.borders,
4489 &mut self.ellipses,
4490 &mut self.ellipse_borders,
4491 &mut self.arcs,
4492 ),
4493 (&mut self.glyph_mask, &mut self.glyph_color),
4494 &mut self.nv12,
4495 &self.device,
4496 &self.queue,
4497 &mut current_pass.cmds,
4498 )
4499 }
4500 };
4501 }
4502 for node in &scene.nodes {
4503 let t_identity = Transform::identity();
4504 let current_transform = transform_stack.last().unwrap_or(&t_identity);
4505
4506 match node {
4507 SceneNode::Rect {
4508 rect,
4509 brush,
4510 radius,
4511 } => {
4512 flush_if_prim_changed!("rect", &self.rects);
4513 let (ndc, sin_cos) = rect_to_instance_ndc(
4514 *rect,
4515 current_transform,
4516 current_target_size.0,
4517 current_target_size.1,
4518 );
4519 let (brush_type, color0, color1, grad_start, grad_end) =
4520 brush_to_instance_fields(brush);
4521 batch.rects.push(RectInstance {
4522 xywh: ndc,
4523 radii: *radius,
4524 brush_type,
4525 _pad: [0.0; 3],
4526 color0,
4527 color1,
4528 grad_start,
4529 grad_end,
4530 sin_cos,
4531 });
4532 }
4533 SceneNode::Border {
4534 rect,
4535 color,
4536 width,
4537 radius,
4538 } => {
4539 flush_if_prim_changed!("border", &self.borders);
4540 let (ndc, sin_cos) = rect_to_instance_ndc(
4541 *rect,
4542 current_transform,
4543 current_target_size.0,
4544 current_target_size.1,
4545 );
4546 batch.borders.push(BorderInstance {
4547 xywh: ndc,
4548 radii: *radius,
4549 stroke: *width,
4550 color: color.to_linear(),
4551 sin_cos,
4552 });
4553 }
4554 SceneNode::Ellipse { rect, brush } => {
4555 flush_if_prim_changed!("ellipse", &self.ellipses);
4556 let (ndc, sin_cos) = rect_to_instance_ndc(
4557 *rect,
4558 current_transform,
4559 current_target_size.0,
4560 current_target_size.1,
4561 );
4562 let color = brush_to_solid_color(brush);
4563 batch.ellipses.push(EllipseInstance {
4564 xywh: ndc,
4565 color,
4566 sin_cos,
4567 });
4568 }
4569 SceneNode::EllipseBorder { rect, color, width } => {
4570 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
4571 let (ndc, sin_cos) = rect_to_instance_ndc(
4572 *rect,
4573 current_transform,
4574 current_target_size.0,
4575 current_target_size.1,
4576 );
4577 let pad_px = *width * 0.5 + 2.0;
4578 let pad = (pad_px / current_target_size.0) * 2.0;
4579 batch.e_borders.push(EllipseBorderInstance {
4580 xywh: ndc,
4581 stroke: *width,
4582 pad,
4583 color: color.to_linear(),
4584 sin_cos,
4585 });
4586 }
4587 SceneNode::Arc {
4588 rect,
4589 start_angle,
4590 sweep_angle,
4591 stroke_width,
4592 color,
4593 cap,
4594 } => {
4595 flush_if_prim_changed!("arc", &self.arcs);
4596 let (ndc, sin_cos) = rect_to_instance_ndc(
4597 *rect,
4598 current_transform,
4599 current_target_size.0,
4600 current_target_size.1,
4601 );
4602 let pad_px = *stroke_width * 0.5 + 2.0;
4603 let pad = (pad_px / current_target_size.0) * 2.0;
4604 let cap_val = match cap {
4605 StrokeCap::Butt => 0.0,
4606 StrokeCap::Round => 1.0,
4607 StrokeCap::Square => 2.0,
4608 };
4609 batch.arcs.push(ArcInstance {
4610 xywh: ndc,
4611 start_angle: *start_angle,
4612 sweep_angle: *sweep_angle,
4613 stroke: *stroke_width,
4614 pad,
4615 color: color.to_linear(),
4616 sin_cos,
4617 cap: cap_val,
4618 });
4619 }
4620 SceneNode::Text {
4621 rect,
4622 text,
4623 color,
4624 size,
4625 font_family,
4626 text_align: _,
4627 font_weight,
4628 font_style,
4629 text_decoration,
4630 letter_spacing,
4631 line_height: _,
4632 extra_style,
4633 url: _,
4634 font_variation_settings,
4635 } => {
4636 flush_batch!(); let px = *size;
4639 let lh_ratio = rect.h / px;
4640 let fw = font_weight.0;
4641 let fs = if *font_style == FontStyle::Italic {
4642 1
4643 } else {
4644 0
4645 };
4646 let shaped = repose_text::shape_line(
4647 text.as_ref(),
4648 px,
4649 lh_ratio,
4650 *font_family,
4651 fw,
4652 fs,
4653 *letter_spacing,
4654 font_variation_settings.as_deref(),
4655 );
4656 let baseline_y = shaped.first().map(|g| rect.y + g.y);
4657
4658 let cos_a = current_transform.rotate.cos();
4659 let sin_a = current_transform.rotate.sin();
4660 let has_rotation = current_transform.rotate != 0.0;
4661
4662 let pivot_x = rect.x + rect.w * 0.5;
4664 let pivot_y = rect.y + rect.h * 0.5;
4665
4666 let make_glyph_instance =
4668 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 2]) {
4669 if has_rotation {
4670 let corners =
4671 [(gx, gy), (gx + gw, gy), (gx + gw, gy + gh), (gx, gy + gh)];
4672 let mut min_x = f32::MAX;
4673 let mut max_x = f32::MIN;
4674 let mut min_y = f32::MAX;
4675 let mut max_y = f32::MIN;
4676 for &(x, y) in &corners {
4677 let dx = x - pivot_x;
4678 let dy = y - pivot_y;
4679 let rx = pivot_x + dx * cos_a - dy * sin_a;
4680 let ry = pivot_y + dx * sin_a + dy * cos_a;
4681 min_x = min_x.min(rx);
4682 max_x = max_x.max(rx);
4683 min_y = min_y.min(ry);
4684 max_y = max_y.max(ry);
4685 }
4686 let bb_w = max_x - min_x;
4687 let bb_h = max_y - min_y;
4688 let ndc_tl = to_ndc(
4689 min_x,
4690 min_y,
4691 bb_w,
4692 bb_h,
4693 current_target_size.0,
4694 current_target_size.1,
4695 );
4696 let ndc = [
4697 ndc_tl[0] + ndc_tl[2] * 0.5,
4698 ndc_tl[1] + ndc_tl[3] * 0.5,
4699 ndc_tl[2],
4700 ndc_tl[3],
4701 ];
4702 (ndc, [cos_a, sin_a])
4703 } else {
4704 let (sx, sy) = if current_transform.scale_x == 1.0
4706 && current_transform.scale_y == 1.0
4707 {
4708 (gx.round(), gy.round())
4709 } else {
4710 (gx, gy)
4711 };
4712 rect_to_instance_ndc(
4713 repose_core::Rect {
4714 x: sx,
4715 y: sy,
4716 w: gw,
4717 h: gh,
4718 },
4719 current_transform,
4720 current_target_size.0,
4721 current_target_size.1,
4722 )
4723 }
4724 };
4725
4726 let baseline_shift_y: f32 = px * extra_style.baseline_shift.0;
4727
4728 let (
4729 is_stroke,
4730 stroke_width,
4731 stroke_cap,
4732 stroke_join,
4733 stroke_miter,
4734 stroke_path_effect,
4735 ) = match &extra_style.draw_style {
4736 repose_core::DrawStyle::Stroke {
4737 width,
4738 cap,
4739 join,
4740 miter,
4741 path_effect,
4742 } => (true, *width, *cap, *join, *miter, path_effect.clone()),
4743 _ => (
4744 false,
4745 0.0,
4746 repose_core::StrokeCap::Butt,
4747 repose_core::StrokeJoin::Miter,
4748 4.0,
4749 None,
4750 ),
4751 };
4752 let stroke_tess_key = if is_stroke {
4753 Some(slug::StrokeTessKey::new(
4754 stroke_width,
4755 stroke_cap,
4756 stroke_join,
4757 stroke_miter,
4758 &stroke_path_effect,
4759 ))
4760 } else {
4761 None
4762 };
4763
4764 for sg in shaped {
4765 let gx = rect.x + sg.x + sg.bearing_x;
4766 let gy = rect.y + sg.y - sg.bearing_y + baseline_shift_y;
4767
4768 if self.slug_enabled {
4770 let ck = repose_text::lookup_cache_key(sg.key, sg.px);
4771 if let Some(ref ck) = ck {
4772 let need_tessellate = self.slug_cache.get(ck).is_none_or(|g| {
4774 if is_stroke {
4775 let key = stroke_tess_key.as_ref().unwrap();
4776 !g.stroke_variants.contains_key(key)
4777 } else {
4778 g.fill_vertices.is_none()
4779 }
4780 });
4781 if need_tessellate {
4782 if let Some((ck2, commands)) =
4783 repose_text::lookup_and_extract_outline(sg.key, sg.px)
4784 {
4785 let font_size_px = f32::from_bits(ck2.font_size_bits);
4786 if is_stroke {
4787 self.slug_cache.get_or_insert_stroke(
4788 ck2,
4789 font_size_px,
4790 &commands,
4791 stroke_width,
4792 stroke_cap,
4793 stroke_join,
4794 stroke_miter,
4795 &stroke_path_effect,
4796 );
4797 } else {
4798 self.slug_cache.get_or_insert(
4799 ck2,
4800 font_size_px,
4801 &commands,
4802 );
4803 }
4804 }
4805 } else {
4806 self.slug_cache.touch(ck);
4807 }
4808 }
4809 if let Some(entry) = ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
4810 {
4811 let ox = rect.x + sg.x;
4812 let oy = rect.y + sg.y + baseline_shift_y;
4813 let scx = current_transform.scale_x;
4814 let scy = current_transform.scale_y;
4815 let ttx = current_transform.translate_x;
4816 let tty = current_transform.translate_y;
4817
4818 let tf = |x: f32, y: f32| -> (f32, f32) {
4819 if has_rotation {
4820 let dx = x - pivot_x;
4821 let dy = y - pivot_y;
4822 let rx = pivot_x + dx * cos_a - dy * sin_a;
4823 let ry = pivot_y + dx * sin_a + dy * cos_a;
4824 (rx, ry)
4825 } else {
4826 (x * scx + ttx, y * scy + tty)
4827 }
4828 };
4829
4830 let tw = current_target_size.0;
4831 let th = current_target_size.1;
4832
4833 let verts = if is_stroke {
4834 let key = stroke_tess_key.as_ref().unwrap();
4835 entry
4836 .stroke_variants
4837 .get(key)
4838 .map(|v| v.as_slice())
4839 .unwrap_or(&[])
4840 } else {
4841 entry.fill_vertices.as_deref().unwrap_or(&[])
4842 };
4843
4844 for &v in verts {
4845 let (sx, sy) = tf(ox + v[0] * px, oy - v[1] * px);
4846 let ndc_x = sx / tw * 2.0 - 1.0;
4847 let ndc_y = -(sy / th) * 2.0 + 1.0;
4848 slug_verts_local.push(slug::TessVertex {
4849 ndc_pos: [ndc_x, ndc_y],
4850 color: color.to_linear(),
4851 });
4852 }
4853
4854 if is_stroke {
4855 continue;
4857 }
4858 continue;
4859 }
4860 }
4861
4862 if is_stroke {
4864 continue;
4865 }
4866
4867 if let Some(info) = self.upload_glyph_color(sg.key, sg.px) {
4869 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
4870 batch.colors.push(GlyphInstance {
4871 xywh: ndc,
4872 uv: [info.u0, info.v1, info.u1, info.v0],
4873 color: color.to_linear(),
4874 sin_cos,
4875 });
4876 } else if let Some(info) = self.upload_glyph_mask(sg.key, sg.px) {
4877 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
4878 batch.masks.push(GlyphInstance {
4879 xywh: ndc,
4880 uv: [info.u0, info.v1, info.u1, info.v0],
4881 color: color.to_linear(),
4882 sin_cos,
4883 });
4884 }
4885 }
4886
4887 if !slug_verts_local.is_empty() {
4889 let bytes = bytemuck::cast_slice(&slug_verts_local);
4890 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
4891 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
4892 current_pass.cmds.push(Cmd::GlyphsVector {
4893 off,
4894 cnt: slug_verts_local.len() as u32,
4895 });
4896 slug_verts_local.clear();
4897 }
4898
4899 if (text_decoration.underline || text_decoration.strikethrough)
4901 && let Some(baseline_y) = baseline_y
4902 {
4903 flush_batch!();
4904 current_prim = Some("rect");
4905 let deco_color = text_decoration.color.unwrap_or(*color);
4906 let thickness = (px * 0.07).max(1.0);
4907
4908 if text_decoration.underline {
4909 let dy = baseline_y + px * 0.1;
4910 let (ndc, sin_cos) = rect_to_instance_ndc(
4911 repose_core::Rect {
4912 x: rect.x,
4913 y: dy,
4914 w: rect.w,
4915 h: thickness,
4916 },
4917 current_transform,
4918 current_target_size.0,
4919 current_target_size.1,
4920 );
4921 batch.rects.push(RectInstance {
4922 xywh: ndc,
4923 radii: [0.0; 4],
4924 brush_type: 0,
4925 _pad: [0.0; 3],
4926 color0: deco_color.to_linear(),
4927 color1: [0.0; 4],
4928 grad_start: [0.0; 2],
4929 grad_end: [0.0; 2],
4930 sin_cos,
4931 });
4932 }
4933 if text_decoration.strikethrough {
4934 let sy = baseline_y - px * 0.3;
4935 let (ndc, sin_cos) = rect_to_instance_ndc(
4936 repose_core::Rect {
4937 x: rect.x,
4938 y: sy,
4939 w: rect.w,
4940 h: thickness,
4941 },
4942 current_transform,
4943 current_target_size.0,
4944 current_target_size.1,
4945 );
4946 batch.rects.push(RectInstance {
4947 xywh: ndc,
4948 radii: [0.0; 4],
4949 brush_type: 0,
4950 _pad: [0.0; 3],
4951 color0: deco_color.to_linear(),
4952 color1: [0.0; 4],
4953 grad_start: [0.0; 2],
4954 grad_end: [0.0; 2],
4955 sin_cos,
4956 });
4957 }
4958 }
4959 }
4960 SceneNode::Image {
4961 rect,
4962 handle,
4963 tint,
4964 fit,
4965 } => {
4966 flush_batch!();
4967
4968 let (img_w, img_h, is_nv12) = match self.resolve_image_for_draw(*handle) {
4971 Some(wh) => wh,
4972 None => {
4973 log::warn!("Image handle {} not found", handle);
4974 continue;
4975 }
4976 };
4977
4978 let src_w = img_w as f32;
4979 let src_h = img_h as f32;
4980
4981 let dst_w = rect.w.max(0.0);
4982 let dst_h = rect.h.max(0.0);
4983 if dst_w <= 0.0 || dst_h <= 0.0 {
4984 continue;
4985 }
4986
4987 let (draw_rect, uv_rect) = match fit {
4988 repose_core::view::ImageFit::Contain => {
4989 let scale = (dst_w / src_w).min(dst_h / src_h);
4990 let w = src_w * scale;
4991 let h = src_h * scale;
4992 (
4993 repose_core::Rect {
4994 x: rect.x + (dst_w - w) * 0.5,
4995 y: rect.y + (dst_h - h) * 0.5,
4996 w,
4997 h,
4998 },
4999 [0.0, 1.0, 1.0, 0.0],
5000 )
5001 }
5002 repose_core::view::ImageFit::Cover => {
5003 let scale = (dst_w / src_w).max(dst_h / src_h);
5004 let content_w = src_w * scale;
5005 let content_h = src_h * scale;
5006 let overflow_x = (content_w - dst_w) * 0.5;
5007 let overflow_y = (content_h - dst_h) * 0.5;
5008 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
5009 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
5010 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
5011 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
5012 (*rect, [u0, 1.0 - v0, u1, 1.0 - v1])
5013 }
5014 repose_core::view::ImageFit::FitWidth => {
5015 let scale = dst_w / src_w;
5016 (
5017 repose_core::Rect {
5018 x: rect.x,
5019 y: rect.y + (dst_h - src_h * scale) * 0.5,
5020 w: dst_w,
5021 h: src_h * scale,
5022 },
5023 [0.0, 1.0, 1.0, 0.0],
5024 )
5025 }
5026 repose_core::view::ImageFit::FitHeight => {
5027 let scale = dst_h / src_h;
5028 (
5029 repose_core::Rect {
5030 x: rect.x + (dst_w - src_w * scale) * 0.5,
5031 y: rect.y,
5032 w: src_w * scale,
5033 h: dst_h,
5034 },
5035 [0.0, 1.0, 1.0, 0.0],
5036 )
5037 }
5038 repose_core::view::ImageFit::FillBounds => (*rect, [0.0, 1.0, 1.0, 0.0]),
5039 repose_core::view::ImageFit::Inside => {
5040 let scale = (dst_w / src_w).min(dst_h / src_h).min(1.0);
5041 let w = src_w * scale;
5042 let h = src_h * scale;
5043 (
5044 repose_core::Rect {
5045 x: rect.x + (dst_w - w) * 0.5,
5046 y: rect.y + (dst_h - h) * 0.5,
5047 w,
5048 h,
5049 },
5050 [0.0, 1.0, 1.0, 0.0],
5051 )
5052 }
5053 repose_core::view::ImageFit::None => {
5054 (
5055 repose_core::Rect {
5056 x: rect.x,
5057 y: rect.y,
5058 w: src_w.min(dst_w),
5059 h: src_h.min(dst_h),
5060 },
5061 [
5063 0.0,
5064 1.0,
5065 (dst_w / src_w).min(1.0),
5066 1.0 - (dst_h / src_h).min(1.0),
5067 ],
5068 )
5069 }
5070 _ => continue,
5071 };
5072
5073 let (ndc_center, sin_cos) = rect_to_instance_ndc(
5074 draw_rect,
5075 current_transform,
5076 current_target_size.0,
5077 current_target_size.1,
5078 );
5079
5080 if is_nv12 {
5081 let uv_x_offset = if let Some(ImageTex::Nv12 { w, color_info, .. }) =
5082 self.images.get(handle)
5083 {
5084 match color_info.chroma_siting {
5085 ChromaSiting::Center | ChromaSiting::TopLeft => 0.0,
5086 ChromaSiting::Left => -1.0 / *w as f32,
5087 }
5088 } else {
5089 0.0
5090 };
5091
5092 let inst = Nv12Instance {
5093 xywh: ndc_center,
5094 uv: uv_rect,
5095 color: tint.to_linear(),
5096 uv_x_offset,
5097 sin_cos,
5098 _pad: [0.0],
5099 };
5100 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
5101 {
5102 current_pass.cmds.push(Cmd::ImageNv12 {
5103 off,
5104 cnt: 1,
5105 handle: *handle,
5106 });
5107 }
5108 } else {
5109 let inst = GlyphInstance {
5111 xywh: ndc_center,
5112 uv: uv_rect,
5113 color: tint.to_linear(),
5114 sin_cos,
5115 };
5116 if let Some((off, _)) =
5117 self.glyph_color.upload(&self.device, &self.queue, &[inst])
5118 {
5119 current_pass.cmds.push(Cmd::ImageRgba {
5120 off,
5121 cnt: 1,
5122 handle: *handle,
5123 });
5124 }
5125 }
5126 }
5127 SceneNode::PushClip { rect, radius, op } => {
5128 flush_batch!(); let is_diff = matches!(op, repose_core::ClipOp::Difference);
5131
5132 let t_identity = Transform::identity();
5133 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5134 let transformed = current_transform.apply_to_rect(*rect);
5135
5136 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5137 let next_scissor = if is_diff {
5138 top
5139 } else {
5140 intersect(top, transformed)
5141 };
5142 scissor_stack.push(next_scissor);
5143 let scissor = to_scissor(
5144 &next_scissor,
5145 current_target_size.0 as u32,
5146 current_target_size.1 as u32,
5147 );
5148
5149 let clip_ndc_tl = to_ndc(
5150 transformed.x,
5151 transformed.y,
5152 transformed.w,
5153 transformed.h,
5154 current_target_size.0,
5155 current_target_size.1,
5156 );
5157 let inst = ClipInstance {
5158 xywh: [
5159 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
5160 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
5161 clip_ndc_tl[2],
5162 clip_ndc_tl[3],
5163 ],
5164 radii: *radius,
5165 sin_cos: [1.0, 0.0],
5166 };
5167 let bytes = bytemuck::bytes_of(&inst);
5168 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
5169 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
5170
5171 let rounded = radius.iter().any(|&r| r > 0.5);
5172
5173 current_pass.cmds.push(Cmd::ClipPush {
5174 off,
5175 cnt: 1,
5176 scissor,
5177 difference: is_diff,
5178 rounded,
5179 });
5180 clip_cmd_stack.push((off, 1, is_diff));
5181 }
5182 SceneNode::PopClip => {
5183 flush_batch!();
5184
5185 if !scissor_stack.is_empty() {
5186 scissor_stack.pop();
5187 } else {
5188 log::warn!("PopClip with empty stack");
5189 }
5190
5191 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5192 let scissor = to_scissor(
5193 &top,
5194 current_target_size.0 as u32,
5195 current_target_size.1 as u32,
5196 );
5197 let (off, cnt, difference) = clip_cmd_stack.pop().unwrap_or((0, 0, false));
5198 current_pass.cmds.push(Cmd::ClipPop {
5199 off,
5200 cnt,
5201 scissor,
5202 difference,
5203 });
5204 }
5205 SceneNode::Shadow {
5206 rect,
5207 radius,
5208 elevation: _,
5209 color,
5210 } => {
5211 flush_if_prim_changed!("rect", &self.rects);
5212 let (ndc, sin_cos) = rect_to_instance_ndc(
5213 *rect,
5214 current_transform,
5215 current_target_size.0,
5216 current_target_size.1,
5217 );
5218 let (brush_type, color0, _color1, _grad_start, _grad_end) =
5219 brush_to_instance_fields(&Brush::Solid(*color));
5220 batch.rects.push(RectInstance {
5221 xywh: ndc,
5222 radii: *radius,
5223 brush_type,
5224 _pad: [0.0; 3],
5225 color0,
5226 color1: [0.0; 4],
5227 grad_start: [0.0; 2],
5228 grad_end: [0.0; 2],
5229 sin_cos,
5230 });
5231 }
5232 SceneNode::PushTransform { transform } => {
5233 flush_batch!(); let combined = current_transform.combine(transform);
5235 transform_stack.push(combined);
5236 }
5237 SceneNode::PopTransform => {
5238 flush_batch!(); transform_stack.pop();
5240 }
5241 SceneNode::BeginLayer {
5242 rect,
5243 layer_id,
5244 alpha,
5245 blur_radius_x,
5246 blur_radius_y,
5247 rectangle_edge: _,
5248 } => {
5249 flush_batch!();
5250 let w = (rect.w.round().max(1.0)) as u32;
5253 let h = (rect.h.round().max(1.0)) as u32;
5254 saved_scissor_stack =
5255 std::mem::replace(&mut scissor_stack, Vec::with_capacity(8));
5256 saved_root_clip_rect = std::mem::replace(
5257 &mut root_clip_rect,
5258 repose_core::Rect {
5259 x: 0.0,
5260 y: 0.0,
5261 w: w as f32,
5262 h: h as f32,
5263 },
5264 );
5265 scissor_stack.push(root_clip_rect);
5266 let prev_target = current_pass.target;
5268 let prev_scissor = current_pass.initial_scissor;
5269 let saved = std::mem::replace(
5270 &mut current_pass,
5271 Pass {
5272 target: PassTarget::Layer(*layer_id),
5273 initial_scissor: (0, 0, w, h),
5274 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
5275 cmds: Vec::new(),
5276 },
5277 );
5278 passes.push(saved);
5279 target_stack.push(prev_target);
5280 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
5284 current_target_size = (w as f32, h as f32);
5285 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
5286 if *blur_radius_x > 0.0 || *blur_radius_y > 0.0 {
5288 layer_blurs.push((*layer_id, *blur_radius_x, *blur_radius_y));
5289 }
5290 }
5291 SceneNode::EndLayer { layer_id } => {
5292 flush_batch!();
5293 scissor_stack = std::mem::replace(&mut saved_scissor_stack, Vec::new());
5294 root_clip_rect = saved_root_clip_rect;
5295 let saved = std::mem::replace(
5297 &mut current_pass,
5298 Pass {
5299 target: target_stack.pop().unwrap_or(PassTarget::Surface),
5300 initial_scissor: (0, 0, self.output_width, self.output_height),
5301 clear_color: None, cmds: Vec::new(),
5303 },
5304 );
5305 passes.push(saved);
5306 current_target_size = (fb_w, fb_h);
5307 if let Some((_, layer_alpha, _)) = layer_alphas
5309 .iter()
5310 .find(|(id, _, _)| id == layer_id)
5311 .copied()
5312 {
5313 let layer = self.layer_pool.get(layer_id).expect("layer target");
5314 let ndc_tl = to_ndc(
5315 layer.rect_px.0,
5316 layer.rect_px.1,
5317 layer.rect_px.2,
5318 layer.rect_px.3,
5319 fb_w,
5320 fb_h,
5321 );
5322 let uv_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
5323 let uv_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
5324 let blur_px_val = layer_blurs
5326 .iter()
5327 .find(|(id, _, _)| id == layer_id)
5328 .map(|(_, bx, by)| (*bx, *by));
5329 if let Some((blur_x, blur_y)) =
5330 blur_px_val.filter(|(bx, by)| *bx > 0.0 || *by > 0.0)
5331 {
5332 let bw_uv = (blur_x * 1.5) / layer.width.max(1) as f32;
5334 let bh_uv = (blur_y * 1.5) / layer.height.max(1) as f32;
5335 let inst = BlurInstance {
5336 xywh: [
5337 ndc_tl[0] + ndc_tl[2] * 0.5,
5338 ndc_tl[1] + ndc_tl[3] * 0.5,
5339 ndc_tl[2],
5340 ndc_tl[3],
5341 ],
5342 uv: [0.0, 0.0, uv_u1, uv_v1],
5343 color: [1.0, 1.0, 1.0, layer_alpha],
5344 blur_uv: [bw_uv, bh_uv],
5345 sin_cos: [1.0, 0.0],
5346 };
5347 self.blur_ring.grow_to_fit(
5348 &self.device,
5349 std::mem::size_of::<BlurInstance>() as u64,
5350 );
5351 let bytes = bytemuck::bytes_of(&inst);
5352 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
5353 current_pass.cmds.push(Cmd::CompositeBlur {
5354 off,
5355 cnt: 1,
5356 layer_id: *layer_id,
5357 });
5358 } else {
5359 let inst = GlyphInstance {
5361 xywh: [
5362 ndc_tl[0] + ndc_tl[2] * 0.5,
5363 ndc_tl[1] + ndc_tl[3] * 0.5,
5364 ndc_tl[2],
5365 ndc_tl[3],
5366 ],
5367 uv: [0.0, uv_v1, uv_u1, 0.0],
5368 color: [1.0, 1.0, 1.0, layer_alpha],
5369 sin_cos: [1.0, 0.0],
5370 };
5371 if let Some((off, cnt)) =
5372 self.glyph_color.upload(&self.device, &self.queue, &[inst])
5373 {
5374 current_pass.cmds.push(Cmd::CompositeLayer {
5375 off,
5376 cnt,
5377 layer_id: *layer_id,
5378 });
5379 }
5380 }
5381 }
5382 }
5383 SceneNode::CompositeShadow {
5384 layer_id,
5385 blur_px,
5386 offset_px,
5387 color,
5388 } => {
5389 flush_batch!();
5390 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
5391 let sx = layer.rect_px.0 + offset_px.0;
5393 let sy = layer.rect_px.1 + offset_px.1;
5394 let sw = layer.rect_px.2;
5395 let sh = layer.rect_px.3;
5396 let bw_uv = (blur_px * 1.5) / layer.width.max(1) as f32;
5399 let bh_uv = (blur_px * 1.5) / layer.height.max(1) as f32;
5400 let shadow_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
5401 let shadow_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
5402 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
5403 let inst = BlurInstance {
5404 xywh: [
5405 ndc_tl[0] + ndc_tl[2] * 0.5,
5406 ndc_tl[1] + ndc_tl[3] * 0.5,
5407 ndc_tl[2],
5408 ndc_tl[3],
5409 ],
5410 uv: [0.0, 0.0, shadow_u1, shadow_v1],
5411 color: [
5412 color.0 as f32 / 255.0,
5413 color.1 as f32 / 255.0,
5414 color.2 as f32 / 255.0,
5415 color.3 as f32 / 255.0,
5416 ],
5417 blur_uv: [bw_uv, bh_uv],
5418 sin_cos: [1.0, 0.0],
5419 };
5420 self.blur_ring
5421 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
5422 let bytes = bytemuck::bytes_of(&inst);
5423 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
5424 current_pass.cmds.push(Cmd::CompositeShadow {
5425 off,
5426 cnt: 1,
5427 layer_id: *layer_id,
5428 });
5429 }
5430 }
5431 SceneNode::VectorMesh {
5432 mesh,
5433 transform,
5434 paint,
5435 clip: _,
5436 blend: _,
5437 } => {
5438 flush_batch!();
5439 let t_identity = Transform::identity();
5440 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5441 self.emit_vector_mesh(
5442 current_transform,
5443 mesh,
5444 *transform,
5445 paint,
5446 &mut current_pass.cmds,
5447 );
5448 }
5449 SceneNode::VectorOverlay { meshes } => {
5450 flush_batch!();
5451 for m in meshes.iter() {
5452 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(m);
5453 let uoff = self.alloc_mesh_uniform(MeshUniform::identity());
5454 current_pass.cmds.push(Cmd::VectorOverlay {
5455 voff,
5456 vcnt,
5457 ioff,
5458 icnt,
5459 uoff,
5460 });
5461 }
5462 }
5463 SceneNode::PushVectorClip { mesh } => {
5464 flush_batch!();
5465 let t_identity = Transform::identity();
5466 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5467 let affine =
5468 combine_mesh_affine(current_transform, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
5469 let aabb = mesh_aabb(mesh, affine);
5470 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5471 let next = intersect(top, aabb);
5472 scissor_stack.push(next);
5473 let scissor = to_scissor(
5474 &next,
5475 current_target_size.0 as u32,
5476 current_target_size.1 as u32,
5477 );
5478 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
5479 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(
5480 affine,
5481 &repose_core::PaintDesc::Solid,
5482 ));
5483 current_pass.cmds.push(Cmd::VectorClipPush {
5484 voff,
5485 vcnt,
5486 ioff,
5487 icnt,
5488 uoff,
5489 scissor,
5490 });
5491 self.mesh_clip_stack.push((voff, vcnt, ioff, icnt, uoff));
5492 }
5493 SceneNode::PopVectorClip => {
5494 flush_batch!();
5495 if !scissor_stack.is_empty() {
5496 scissor_stack.pop();
5497 } else {
5498 log::warn!("PopVectorClip with empty scissor stack");
5499 }
5500 if let Some((voff, vcnt, ioff, icnt, uoff)) = self.mesh_clip_stack.pop() {
5501 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5502 let scissor = to_scissor(
5503 &top,
5504 current_target_size.0 as u32,
5505 current_target_size.1 as u32,
5506 );
5507 current_pass.cmds.push(Cmd::VectorClipPop {
5508 voff,
5509 vcnt,
5510 ioff,
5511 icnt,
5512 uoff,
5513 scissor,
5514 });
5515 } else {
5516 log::warn!("PopVectorClip with empty clip stack");
5517 }
5518 }
5519 SceneNode::Callback { rect, payload } => {
5520 flush_batch!();
5521 let t = transform_stack
5522 .last()
5523 .copied()
5524 .unwrap_or(Transform::identity());
5525 let transformed = t.apply_to_rect(*rect);
5526 current_pass.cmds.push(Cmd::Callback {
5527 rect: transformed,
5528 payload: payload.clone(),
5529 });
5530 }
5531 _ => {}
5532 }
5533 }
5534
5535 flush_batch!();
5536
5537 {
5538 let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
5539 let mut prepare_list: Vec<Arc<Callback>> = Vec::new();
5540 for node in &scene.nodes {
5541 if let SceneNode::Callback { payload, .. } = node
5542 && payload.downcast_ref::<Callback>().is_some()
5543 {
5544 let ptr = Arc::as_ptr(payload) as *const () as usize;
5545 if seen.insert(ptr) {
5546 if let Ok(cb_arc) = payload.clone().downcast::<Callback>() {
5547 prepare_list.push(cb_arc);
5548 }
5549 }
5550 }
5551 }
5552 if !prepare_list.is_empty() {
5553 let screen_desc = ScreenDescriptor {
5554 size_in_pixels: [self.output_width, self.output_height],
5555 pixels_per_point: self.pixels_per_point,
5556 target_format: self.output_format,
5557 sample_count: self.msaa_samples.max(1),
5558 };
5559 let mut user_cmd_bufs: Vec<wgpu::CommandBuffer> = Vec::new();
5560 for cb in &prepare_list {
5561 user_cmd_bufs.extend(cb.0.prepare(
5562 &self.device,
5563 &self.queue,
5564 encoder,
5565 &screen_desc,
5566 &mut self.callback_resources,
5567 ));
5568 }
5569 for cb in &prepare_list {
5570 user_cmd_bufs.extend(cb.0.finish_prepare(
5571 &self.device,
5572 &self.queue,
5573 encoder,
5574 &screen_desc,
5575 &mut self.callback_resources,
5576 ));
5577 }
5578 if !user_cmd_bufs.is_empty() {
5581 self.queue.submit(user_cmd_bufs);
5582 }
5583 }
5584 }
5585
5586 passes.push(current_pass);
5588
5589 let globals_bytes = std::mem::size_of::<Globals>() as u64;
5590 let globals_staging = self.device.create_buffer(&wgpu::BufferDescriptor {
5591 label: Some("globals staging"),
5592 size: (passes.len().max(1) as u64) * globals_bytes,
5593 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
5594 mapped_at_creation: false,
5595 });
5596 for (i, pass) in passes.iter().enumerate() {
5597 let (target_w, target_h) = match pass.target {
5598 PassTarget::Surface => (fb_w, fb_h),
5599 PassTarget::Layer(layer_id) => {
5600 let lt = self.layer_pool.get(&layer_id);
5601 (
5602 lt.map_or(fb_w, |l| l.width as f32),
5603 lt.map_or(fb_h, |l| l.height as f32),
5604 )
5605 }
5606 };
5607 self.queue.write_buffer(
5608 &globals_staging,
5609 (i as u64) * globals_bytes,
5610 bytemuck::bytes_of(&make_globals(target_w, target_h)),
5611 );
5612 }
5613
5614 let bind_mask = self.atlas_bind_group_mask();
5615 let bind_color = self.atlas_bind_group_color();
5616 let mut clip_depth: u32 = 0;
5617
5618 for (pass_index, pass) in std::mem::take(&mut passes).into_iter().enumerate() {
5619 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
5620 PassTarget::Surface => {
5621 let swap_view = target_view.clone();
5622 let use_ws = self.working_space && self.ws_view.is_some();
5623 let (color, resolve) = if use_ws {
5624 let ws_view = self.ws_view.as_ref().unwrap();
5625 if let Some(msaa_view) = &self.msaa_view {
5626 (msaa_view.clone(), Some(ws_view.clone()))
5628 } else {
5629 (ws_view.clone(), None)
5631 }
5632 } else if let Some(msaa_view) = &self.msaa_view {
5633 (msaa_view.clone(), Some(swap_view))
5634 } else {
5635 (swap_view, None)
5636 };
5637 (color, resolve, self.depth_stencil_view.clone(), false)
5638 }
5639 PassTarget::Layer(layer_id) => {
5640 if let Some(lt) = self.layer_pool.get(&layer_id) {
5641 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
5642 } else {
5643 log::warn!("missing layer target {layer_id}");
5644 continue;
5645 }
5646 }
5647 };
5648
5649 encoder.copy_buffer_to_buffer(
5650 &globals_staging,
5651 (pass_index as u64) * globals_bytes,
5652 &self.globals_buf,
5653 0,
5654 globals_bytes,
5655 );
5656
5657 if is_layer {
5658 clip_depth = 0;
5659 }
5660
5661 let (tw, th) = match pass.target {
5662 PassTarget::Surface => (self.output_width, self.output_height),
5663 PassTarget::Layer(layer_id) => self
5664 .layer_pool
5665 .get(&layer_id)
5666 .map(|l| (l.width, l.height))
5667 .unwrap_or((self.output_width, self.output_height)),
5668 };
5669 let initial_scissor = clamp_scissor(
5670 pass.initial_scissor.0,
5671 pass.initial_scissor.1,
5672 pass.initial_scissor.2,
5673 pass.initial_scissor.3,
5674 tw,
5675 th,
5676 );
5677
5678 let pipes: &Pipelines = if is_layer {
5679 &self.layer_pipes
5680 } else {
5681 &self.surface_pipes
5682 };
5683
5684 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
5685 label: Some("pass"),
5686 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
5687 view: &color_view,
5688 resolve_target: resolve_target.as_ref(),
5689 ops: wgpu::Operations {
5690 load: match pass.clear_color {
5691 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
5692 r: c[0] as f64,
5693 g: c[1] as f64,
5694 b: c[2] as f64,
5695 a: c[3] as f64,
5696 }),
5697 None => wgpu::LoadOp::Load,
5698 },
5699 store: wgpu::StoreOp::Store,
5700 },
5701 depth_slice: None,
5702 })],
5703 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
5704 view: &depth_stencil_view,
5705 depth_ops: None,
5706 stencil_ops: Some(wgpu::Operations {
5707 load: if is_layer || pass.clear_color.is_some() {
5708 wgpu::LoadOp::Clear(0)
5709 } else {
5710 wgpu::LoadOp::Load
5711 },
5712 store: wgpu::StoreOp::Store,
5713 }),
5714 }),
5715 timestamp_writes: None,
5716 occlusion_query_set: None,
5717 multiview_mask: None,
5718 });
5719
5720 rpass.set_bind_group(0, &self.globals_bind, &[]);
5721 rpass.set_stencil_reference(clip_depth);
5722 rpass.set_scissor_rect(
5723 initial_scissor.0,
5724 initial_scissor.1,
5725 initial_scissor.2,
5726 initial_scissor.3,
5727 );
5728
5729 macro_rules! draw_simple {
5730 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
5731 rpass.set_pipeline($pipeline);
5732 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
5733 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
5734 rpass.draw(0..6, 0..$n);
5735 }};
5736 }
5737
5738 macro_rules! draw_with_bind {
5739 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
5740 rpass.set_pipeline($pipeline);
5741 rpass.set_bind_group(1, $bind, &[]);
5742 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
5743 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
5744 rpass.draw(0..6, 0..$n);
5745 }};
5746 }
5747
5748 macro_rules! draw_indexed_mesh {
5749 ($pipeline:expr, $uoff:ident, $voff:ident, $vcnt:ident, $ioff:ident, $icnt:ident) => {{
5750 rpass.set_pipeline($pipeline);
5751 rpass.set_bind_group(1, &self.mesh_bind, &[$uoff as u32]);
5752 let vbytes = ($vcnt as u64) * std::mem::size_of::<MeshVertex>() as u64;
5753 rpass.set_vertex_buffer(0, self.mesh_verts.buf.slice($voff..$voff + vbytes));
5754 let ibytes = ($icnt as u64) * std::mem::size_of::<u32>() as u64;
5755 rpass.set_index_buffer(
5756 self.mesh_indices.buf.slice($ioff..$ioff + ibytes),
5757 wgpu::IndexFormat::Uint32,
5758 );
5759 rpass.draw_indexed(0..$icnt, 0, 0..1);
5760 }};
5761 }
5762
5763 for cmd in pass.cmds {
5764 match cmd {
5765 Cmd::ClipPush {
5766 off,
5767 cnt: n,
5768 scissor,
5769 difference,
5770 rounded,
5771 } => {
5772 let scissor =
5773 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
5774 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5775 rpass.set_stencil_reference(clip_depth);
5776
5777 if difference {
5778 rpass.set_pipeline(&pipes.clip_dec);
5779 } else if self.msaa_samples > 1 && !is_layer && rounded {
5780 rpass.set_pipeline(&pipes.clip_a2c);
5781 } else {
5782 rpass.set_pipeline(&pipes.clip_bin);
5783 }
5784
5785 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
5786 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
5787 rpass.draw(0..6, 0..n);
5788
5789 if !difference {
5790 clip_depth = (clip_depth + 1).min(255);
5791 rpass.set_stencil_reference(clip_depth);
5792 }
5793 }
5794
5795 Cmd::ClipPop {
5796 off,
5797 cnt: n,
5798 scissor,
5799 difference,
5800 } => {
5801 let scissor =
5802 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
5803 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5804
5805 if !difference && n > 0 {
5806 rpass.set_stencil_reference(clip_depth);
5807 rpass.set_pipeline(&pipes.clip_dec);
5808 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
5809 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
5810 rpass.draw(0..6, 0..n);
5811 clip_depth = clip_depth.saturating_sub(1);
5812 } else if !difference {
5813 clip_depth = clip_depth.saturating_sub(1);
5814 }
5815 rpass.set_stencil_reference(clip_depth);
5816 }
5817
5818 Cmd::Rect { off, cnt: n } => {
5819 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
5820 }
5821
5822 Cmd::Border { off, cnt: n } => {
5823 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
5824 }
5825
5826 Cmd::GlyphsMask { off, cnt: n } => {
5827 draw_with_bind!(
5828 &pipes.text_mask,
5829 self.glyph_mask.ring,
5830 GlyphInstance,
5831 &bind_mask,
5832 off,
5833 n
5834 );
5835 }
5836
5837 Cmd::GlyphsColor { off, cnt: n } => {
5838 draw_with_bind!(
5839 &pipes.text_color,
5840 self.glyph_color.ring,
5841 GlyphInstance,
5842 &bind_color,
5843 off,
5844 n
5845 );
5846 }
5847
5848 Cmd::GlyphsVector { off, cnt: n } => {
5849 if let Some(slug_pipe) = pipes.slug.as_ref() {
5850 rpass.set_pipeline(slug_pipe);
5851 let bytes = (n as u64) * std::mem::size_of::<slug::TessVertex>() as u64;
5852 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
5853 rpass.draw(0..n, 0..1);
5854 }
5855 }
5856
5857 Cmd::ImageRgba {
5858 off,
5859 cnt: n,
5860 handle,
5861 } => {
5862 let bind_opt = match self.images.get(&handle) {
5863 Some(ImageTex::Rgba { bind, .. }) => Some(bind),
5864 Some(ImageTex::User { bind, .. }) => Some(bind),
5865 _ => None,
5866 };
5867 if let Some(bind) = bind_opt {
5868 draw_with_bind!(
5869 &pipes.image_rgba,
5870 self.glyph_color.ring,
5871 GlyphInstance,
5872 bind,
5873 off,
5874 n
5875 );
5876 }
5877 }
5878
5879 Cmd::ImageNv12 {
5880 off,
5881 cnt: n,
5882 handle,
5883 } => {
5884 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
5885 draw_with_bind!(
5886 &pipes.image_nv12,
5887 self.nv12.ring,
5888 Nv12Instance,
5889 bind,
5890 off,
5891 n
5892 );
5893 }
5894 }
5895
5896 Cmd::Ellipse { off, cnt: n } => {
5897 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
5898 }
5899
5900 Cmd::EllipseBorder { off, cnt: n } => {
5901 draw_simple!(
5902 &pipes.ellipse_borders,
5903 self.ellipse_borders.ring,
5904 EllipseBorderInstance,
5905 off,
5906 n
5907 );
5908 }
5909
5910 Cmd::Arc { off, cnt: n } => {
5911 draw_simple!(&pipes.arcs, self.arcs.ring, ArcInstance, off, n);
5912 }
5913
5914 Cmd::CompositeLayer {
5915 off,
5916 cnt: n,
5917 layer_id,
5918 } => {
5919 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5920 draw_with_bind!(
5921 &pipes.image_rgba,
5922 self.glyph_color.ring,
5923 GlyphInstance,
5924 <.bind,
5925 off,
5926 n
5927 );
5928 }
5929 }
5930 Cmd::CompositeShadow {
5931 off,
5932 cnt: n,
5933 layer_id,
5934 } => {
5935 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5936 draw_with_bind!(
5937 &pipes.blur,
5938 self.blur_ring,
5939 BlurInstance,
5940 <.bind_linear,
5941 off,
5942 n
5943 );
5944 }
5945 }
5946 Cmd::CompositeBlur {
5947 off,
5948 cnt: n,
5949 layer_id,
5950 } => {
5951 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5952 draw_with_bind!(
5953 &pipes.blur_content,
5954 self.blur_ring,
5955 BlurInstance,
5956 <.bind_linear,
5957 off,
5958 n
5959 );
5960 }
5961 }
5962
5963 Cmd::VectorMesh {
5964 voff,
5965 vcnt,
5966 ioff,
5967 icnt,
5968 uoff,
5969 } => {
5970 draw_indexed_mesh!(&pipes.mesh, uoff, voff, vcnt, ioff, icnt);
5971 }
5972
5973 Cmd::VectorOverlay {
5974 voff,
5975 vcnt,
5976 ioff,
5977 icnt,
5978 uoff,
5979 } => {
5980 draw_indexed_mesh!(&pipes.mesh_overlay, uoff, voff, vcnt, ioff, icnt);
5981 }
5982
5983 Cmd::VectorClipPush {
5984 voff,
5985 vcnt,
5986 ioff,
5987 icnt,
5988 uoff,
5989 scissor,
5990 } => {
5991 let scissor =
5992 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
5993 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5994 rpass.set_stencil_reference(clip_depth);
5995 draw_indexed_mesh!(&pipes.mesh_clip_inc, uoff, voff, vcnt, ioff, icnt);
5996 clip_depth = (clip_depth + 1).min(255);
5997 rpass.set_stencil_reference(clip_depth);
5998 }
5999
6000 Cmd::VectorClipPop {
6001 voff,
6002 vcnt,
6003 ioff,
6004 icnt,
6005 uoff,
6006 scissor,
6007 } => {
6008 rpass.set_stencil_reference(clip_depth);
6012 let scissor =
6013 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
6014 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
6015 draw_indexed_mesh!(&pipes.mesh_clip_dec, uoff, voff, vcnt, ioff, icnt);
6016 clip_depth = clip_depth.saturating_sub(1);
6017 rpass.set_stencil_reference(clip_depth);
6018 }
6019
6020 Cmd::Callback { rect, payload } => {
6021 if let Some(cb) = payload.downcast_ref::<Callback>() {
6022 let vp_x = rect.x.floor().max(0.0) as f32;
6023 let vp_y = rect.y.floor().max(0.0) as f32;
6024 let vp_w = rect.w.ceil().max(1.0);
6025 let vp_h = rect.h.ceil().max(1.0);
6026 if vp_w > 0.0 && vp_h > 0.0 {
6027 rpass.set_viewport(vp_x, vp_y, vp_w, vp_h, 0.0, 1.0);
6028 let info = repose_core::PaintCallbackInfo {
6029 viewport: rect,
6030 clip_rect: rect,
6031 pixels_per_point: self.pixels_per_point,
6032 screen_size_px: [tw, th],
6033 };
6034 let rpass_static: &mut wgpu::RenderPass<'static> = unsafe {
6035 std::mem::transmute::<
6036 &mut wgpu::RenderPass<'_>,
6037 &mut wgpu::RenderPass<'static>,
6038 >(&mut rpass)
6039 };
6040 cb.0.paint(info, rpass_static, &self.callback_resources);
6041 rpass.set_viewport(0.0, 0.0, tw as f32, th as f32, 0.0, 1.0);
6042 rpass.set_bind_group(0, &self.globals_bind, &[]);
6043 rpass.set_stencil_reference(clip_depth);
6044 }
6045 } else {
6046 log::warn!("Unknown paint callback payload");
6047 }
6048 }
6049 }
6050 }
6051 }
6052
6053 if self.working_space
6055 && let (Some(_ws_view), Some(ws_bind), Some(display_pipeline)) =
6056 (&self.ws_view, &self.ws_bind, &self.display_pipeline)
6057 {
6058 let swap_view = target_view.clone();
6059 let mut display_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
6060 label: Some("display transform"),
6061 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
6062 view: &swap_view,
6063 resolve_target: None,
6064 ops: wgpu::Operations {
6065 load: wgpu::LoadOp::Load,
6066 store: wgpu::StoreOp::Store,
6067 },
6068 depth_slice: None,
6069 })],
6070 depth_stencil_attachment: None,
6071 timestamp_writes: None,
6072 occlusion_query_set: None,
6073 multiview_mask: None,
6074 });
6075 display_pass.set_pipeline(display_pipeline);
6076 display_pass.set_bind_group(1, ws_bind, &[]);
6077 display_pass.draw(0..3, 0..1);
6078 }
6079
6080 self.evict_unused_images();
6082 }
6083
6084 pub fn render_to_view(
6088 &mut self,
6089 scene: &Scene,
6090 encoder: &mut wgpu::CommandEncoder,
6091 target_view: &wgpu::TextureView,
6092 width: u32,
6093 height: u32,
6094 clear_color: Option<[f64; 4]>,
6095 ) {
6096 self.resize(width, height);
6097
6098 self.frame_index = self.frame_index.wrapping_add(1);
6099 self.slug_cache.next_frame();
6100
6101 if width == 0 || height == 0 {
6102 return;
6103 }
6104
6105 self.render_scene_to_encoder(scene, encoder, target_view, clear_color);
6106 }
6107}
6108
6109fn clamp_scissor(x: u32, y: u32, w: u32, h: u32, tw: u32, th: u32) -> (u32, u32, u32, u32) {
6110 let x = x.min(tw.saturating_sub(1));
6111 let y = y.min(th.saturating_sub(1));
6112 let w = w.min(tw.saturating_sub(x)).max(1);
6113 let h = h.min(th.saturating_sub(y)).max(1);
6114 (x, y, w, h)
6115}
6116
6117fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
6118 let x0 = a.x.max(b.x);
6119 let y0 = a.y.max(b.y);
6120 let x1 = (a.x + a.w).min(b.x + b.w);
6121 let y1 = (a.y + a.h).min(b.y + b.h);
6122 repose_core::Rect {
6123 x: x0,
6124 y: y0,
6125 w: (x1 - x0).max(0.0),
6126 h: (y1 - y0).max(0.0),
6127 }
6128}