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.as_chunks::<4>().0 {
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 = if cfg!(target_arch = "wasm32") {
2071 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
2072 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
2073 wgpu::util::new_instance_with_webgpu_detection(desc).await
2074 } else {
2075 wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle())
2076 };
2077
2078 let surface = instance.create_surface(window.clone())?;
2079
2080 let adapter = instance
2081 .request_adapter(&wgpu::RequestAdapterOptions {
2082 power_preference: wgpu::PowerPreference::HighPerformance,
2083 compatible_surface: Some(&surface),
2084 force_fallback_adapter: false,
2085 apply_limit_buckets: false,
2086 })
2087 .await
2088 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
2089
2090 let limits = adapter.limits();
2091
2092 #[cfg(target_os = "linux")]
2093 let features = {
2094 let af = adapter.features();
2095 let mut f = wgpu::Features::empty();
2096 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD) {
2097 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_FD;
2098 }
2099 if af.contains(wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF) {
2100 f |= wgpu::Features::VULKAN_EXTERNAL_MEMORY_DMA_BUF;
2101 }
2102 f
2103 };
2104 #[cfg(not(target_os = "linux"))]
2105 let features = wgpu::Features::empty();
2106
2107 let (device, queue) = adapter
2108 .request_device(&wgpu::DeviceDescriptor {
2109 label: Some("repose-rs device"),
2110 required_features: features,
2111 required_limits: limits,
2112 experimental_features: wgpu::ExperimentalFeatures::disabled(),
2113 memory_hints: wgpu::MemoryHints::default(),
2114 trace: wgpu::Trace::Off,
2115 })
2116 .await
2117 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
2118
2119 let size = window.inner_size();
2120
2121 let caps = surface.get_capabilities(&adapter);
2122
2123 let (format, view_format) = if cfg!(target_arch = "wasm32")
2124 && adapter
2125 .get_downlevel_capabilities()
2126 .flags
2127 .contains(wgpu::DownlevelFlags::SURFACE_VIEW_FORMATS)
2128 {
2129 let non_srgb = caps
2130 .formats
2131 .iter()
2132 .copied()
2133 .find(|f| !f.is_srgb())
2134 .unwrap_or(caps.formats[0]);
2135 (non_srgb, Some(non_srgb.add_srgb_suffix()))
2136 } else if cfg!(target_arch = "wasm32") {
2137 let fmt = caps
2138 .formats
2139 .iter()
2140 .copied()
2141 .find(|f| f.is_srgb())
2142 .unwrap_or(caps.formats[0]);
2143 (fmt, None)
2144 } else {
2145 let fmt = caps
2146 .formats
2147 .iter()
2148 .copied()
2149 .find(|f| f.is_srgb())
2150 .unwrap_or(caps.formats[0]);
2151 (fmt, None)
2152 };
2153
2154 let present_mode = pick_present_mode(&caps, present_mode);
2155 let alpha_mode = caps.alpha_modes[0];
2156
2157 let render_format = view_format.unwrap_or(format);
2158 let msaa_samples = pick_surface_msaa(&adapter, format, msaa_samples);
2159 let renderer = WgpuSceneRenderer::from_device(device, queue, render_format, msaa_samples);
2160
2161 let view_formats = view_format.into_iter().collect::<Vec<_>>();
2162
2163 let config = wgpu::SurfaceConfiguration {
2164 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
2165 format,
2166 width: size.width.max(1),
2167 height: size.height.max(1),
2168 present_mode,
2169 alpha_mode,
2170 color_space: wgpu::SurfaceColorSpace::Auto,
2171 view_formats,
2172 desired_maximum_frame_latency: 1,
2173 };
2174 surface.configure(&renderer.device, &config);
2175
2176 Ok(WgpuSurfaceBackend {
2177 surface: Some(surface),
2178 surface_config: Some(config),
2179 renderer,
2180 })
2181 }
2182
2183 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2184 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2185 pollster::block_on(Self::new_async(window))
2186 }
2187
2188 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2189 pub fn new_with_msaa(
2190 window: Arc<winit::window::Window>,
2191 msaa_samples: u32,
2192 ) -> anyhow::Result<WgpuSurfaceBackend> {
2193 pollster::block_on(Self::new_async_with_msaa(window, msaa_samples))
2194 }
2195
2196 #[cfg(all(feature = "winit-surface", not(target_arch = "wasm32")))]
2197 pub fn new_with_options(
2198 window: Arc<winit::window::Window>,
2199 msaa_samples: u32,
2200 present_mode: PresentModePref,
2201 ) -> anyhow::Result<WgpuSurfaceBackend> {
2202 pollster::block_on(Self::new_async_with_options(
2203 window,
2204 msaa_samples,
2205 present_mode,
2206 ))
2207 }
2208
2209 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2210 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<WgpuSurfaceBackend> {
2211 anyhow::bail!("Use WgpuSurfaceBackend::new_async(window).await on wasm32")
2212 }
2213
2214 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2215 pub fn new_with_msaa(
2216 _window: Arc<winit::window::Window>,
2217 _msaa_samples: u32,
2218 ) -> anyhow::Result<WgpuSurfaceBackend> {
2219 anyhow::bail!("Use WgpuSurfaceBackend::new_async_with_msaa(window, msaa).await on wasm32")
2220 }
2221
2222 #[cfg(all(feature = "winit-surface", target_arch = "wasm32"))]
2223 pub fn new_with_options(
2224 _window: Arc<winit::window::Window>,
2225 _msaa_samples: u32,
2226 _present_mode: PresentModePref,
2227 ) -> anyhow::Result<WgpuSurfaceBackend> {
2228 anyhow::bail!(
2229 "Use WgpuSurfaceBackend::new_async_with_options(window, msaa, mode).await on wasm32"
2230 )
2231 }
2232}
2233
2234fn pick_present_mode(caps: &wgpu::SurfaceCapabilities, pref: PresentModePref) -> wgpu::PresentMode {
2237 let auto = || {
2238 caps.present_modes
2239 .iter()
2240 .copied()
2241 .find(|m| *m == wgpu::PresentMode::Fifo)
2242 .or_else(|| {
2243 caps.present_modes
2244 .iter()
2245 .copied()
2246 .find(|m| *m == wgpu::PresentMode::Mailbox)
2247 })
2248 .unwrap_or(wgpu::PresentMode::Immediate)
2249 };
2250 match pref {
2251 PresentModePref::Auto => auto(),
2252 PresentModePref::Fifo if caps.present_modes.contains(&wgpu::PresentMode::Fifo) => {
2253 wgpu::PresentMode::Fifo
2254 }
2255 PresentModePref::Mailbox if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) => {
2256 wgpu::PresentMode::Mailbox
2257 }
2258 PresentModePref::Immediate
2259 if caps.present_modes.contains(&wgpu::PresentMode::Immediate) =>
2260 {
2261 wgpu::PresentMode::Immediate
2262 }
2263 _ => auto(),
2264 }
2265}
2266
2267pub fn pick_surface_msaa(
2270 adapter: &wgpu::Adapter,
2271 format: wgpu::TextureFormat,
2272 requested: u32,
2273) -> u32 {
2274 let requested = requested.max(1);
2275 let color_feat = adapter.get_texture_format_features(format);
2276 let depth_feat = adapter.get_texture_format_features(wgpu::TextureFormat::Depth24PlusStencil8);
2277 let supported = |n: u32| {
2278 color_feat.flags.sample_count_supported(n)
2279 && color_feat
2280 .flags
2281 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
2282 && depth_feat.flags.sample_count_supported(n)
2283 };
2284 let mut candidates = vec![requested];
2285 for n in [8, 4, 2, 1] {
2286 if n < requested {
2287 candidates.push(n);
2288 }
2289 }
2290 let chosen = candidates.into_iter().find(|&n| supported(n)).unwrap_or(1);
2291 if chosen != requested {
2292 log::info!("requested MSAA x{requested}, using x{chosen}");
2293 }
2294 chosen
2295}
2296
2297impl WgpuSceneRenderer {
2298 pub fn set_image_from_bytes(
2301 &mut self,
2302 handle: u64,
2303 data: &[u8],
2304 srgb: bool,
2305 ) -> anyhow::Result<()> {
2306 let img = image::load_from_memory(data)?;
2307 let rgba = img.to_rgba8();
2308 let (w, h) = rgba.dimensions();
2309 self.set_image_rgba8(handle, w, h, &rgba, srgb)
2310 }
2311
2312 pub fn set_image_rgba8(
2313 &mut self,
2314 handle: u64,
2315 w: u32,
2316 h: u32,
2317 rgba: &[u8],
2318 srgb: bool,
2319 ) -> anyhow::Result<()> {
2320 let expected = (w as usize) * (h as usize) * 4;
2321 if rgba.len() < expected {
2322 return Err(anyhow::anyhow!(
2323 "RGBA buffer too small: {} < {}",
2324 rgba.len(),
2325 expected
2326 ));
2327 }
2328
2329 let format = if srgb {
2330 wgpu::TextureFormat::Rgba8UnormSrgb
2331 } else {
2332 wgpu::TextureFormat::Rgba8Unorm
2333 };
2334
2335 let needs_recreate = match self.images.get(&handle) {
2336 Some(ImageTex::Rgba {
2337 w: cw,
2338 h: ch,
2339 format: cf,
2340 ..
2341 }) => *cw != w || *ch != h || *cf != format,
2342 _ => true,
2343 };
2344
2345 if needs_recreate {
2346 self.remove_image(handle);
2347
2348 let (tex, bind) = self.create_rgba_tex(w, h, format);
2349 let bytes = (w as u64) * (h as u64) * 4;
2350 self.image_bytes_total += bytes;
2351
2352 self.images.insert(
2353 handle,
2354 ImageTex::Rgba {
2355 tex,
2356 bind,
2357 w,
2358 h,
2359 format,
2360 last_used_frame: self.frame_index,
2361 bytes,
2362 },
2363 );
2364 }
2365
2366 self.retained.insert(
2367 handle,
2368 RetainedImage {
2369 w,
2370 h,
2371 format,
2372 rgba: rgba[..expected].to_vec(),
2373 },
2374 );
2375
2376 let tex = match self.images.get(&handle) {
2377 Some(ImageTex::Rgba { tex, .. }) => tex,
2378 _ => unreachable!(),
2379 };
2380
2381 self.queue.write_texture(
2382 wgpu::TexelCopyTextureInfo {
2383 texture: tex,
2384 mip_level: 0,
2385 origin: wgpu::Origin3d::ZERO,
2386 aspect: wgpu::TextureAspect::All,
2387 },
2388 &rgba[..expected],
2389 wgpu::TexelCopyBufferLayout {
2390 offset: 0,
2391 bytes_per_row: Some(4 * w),
2392 rows_per_image: Some(h),
2393 },
2394 wgpu::Extent3d {
2395 width: w,
2396 height: h,
2397 depth_or_array_layers: 1,
2398 },
2399 );
2400
2401 self.evict_budget_excess();
2403
2404 Ok(())
2405 }
2406
2407 fn create_rgba_tex(
2410 &self,
2411 w: u32,
2412 h: u32,
2413 format: wgpu::TextureFormat,
2414 ) -> (wgpu::Texture, wgpu::BindGroup) {
2415 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2416 label: Some("user image rgba"),
2417 size: wgpu::Extent3d {
2418 width: w,
2419 height: h,
2420 depth_or_array_layers: 1,
2421 },
2422 mip_level_count: 1,
2423 sample_count: 1,
2424 dimension: wgpu::TextureDimension::D2,
2425 format,
2426 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2427 view_formats: &[],
2428 });
2429 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
2430
2431 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2432 label: Some("image bind rgba"),
2433 layout: &self.image_bind_layout_rgba,
2434 entries: &[
2435 wgpu::BindGroupEntry {
2436 binding: 0,
2437 resource: wgpu::BindingResource::TextureView(&view),
2438 },
2439 wgpu::BindGroupEntry {
2440 binding: 1,
2441 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2442 },
2443 ],
2444 });
2445
2446 (tex, bind)
2447 }
2448
2449 pub fn register_native_texture(
2451 &mut self,
2452 view: &wgpu::TextureView,
2453 width: u32,
2454 height: u32,
2455 ) -> u64 {
2456 let handle = self.next_image_handle;
2457 self.next_image_handle += 1;
2458 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2459 label: Some("user native image"),
2460 layout: &self.image_bind_layout_rgba,
2461 entries: &[
2462 wgpu::BindGroupEntry {
2463 binding: 0,
2464 resource: wgpu::BindingResource::TextureView(view),
2465 },
2466 wgpu::BindGroupEntry {
2467 binding: 1,
2468 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2469 },
2470 ],
2471 });
2472 self.images.insert(
2473 handle,
2474 ImageTex::User {
2475 bind,
2476 w: width,
2477 h: height,
2478 last_used_frame: self.frame_index,
2479 bytes: 0,
2480 },
2481 );
2482 handle
2483 }
2484
2485 pub fn register_native_texture_with_sampler(
2487 &mut self,
2488 view: &wgpu::TextureView,
2489 sampler_desc: wgpu::SamplerDescriptor<'_>,
2490 width: u32,
2491 height: u32,
2492 ) -> u64 {
2493 let handle = self.next_image_handle;
2494 self.next_image_handle += 1;
2495 let sampler = self.device.create_sampler(&sampler_desc);
2496 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2497 label: Some("user native image sampleropts"),
2498 layout: &self.image_bind_layout_rgba,
2499 entries: &[
2500 wgpu::BindGroupEntry {
2501 binding: 0,
2502 resource: wgpu::BindingResource::TextureView(view),
2503 },
2504 wgpu::BindGroupEntry {
2505 binding: 1,
2506 resource: wgpu::BindingResource::Sampler(&sampler),
2507 },
2508 ],
2509 });
2510 self.images.insert(
2511 handle,
2512 ImageTex::User {
2513 bind,
2514 w: width,
2515 h: height,
2516 last_used_frame: self.frame_index,
2517 bytes: 0,
2518 },
2519 );
2520 handle
2521 }
2522
2523 pub fn update_native_texture(&mut self, handle: u64, view: &wgpu::TextureView) {
2525 let Some(entry) = self.images.get_mut(&handle) else {
2526 log::warn!("update_native_texture: handle {handle} not found");
2527 return;
2528 };
2529 let w = match entry {
2530 ImageTex::User { w, .. } => *w,
2531 ImageTex::Rgba { w, .. } => *w,
2532 _ => {
2533 log::warn!("update_native_texture: handle {handle} is not rgba/user");
2534 return;
2535 }
2536 };
2537 let h = match entry {
2538 ImageTex::User { h, .. } => *h,
2539 ImageTex::Rgba { h, .. } => *h,
2540 _ => 0,
2541 };
2542 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2543 label: Some("user native image update"),
2544 layout: &self.image_bind_layout_rgba,
2545 entries: &[
2546 wgpu::BindGroupEntry {
2547 binding: 0,
2548 resource: wgpu::BindingResource::TextureView(view),
2549 },
2550 wgpu::BindGroupEntry {
2551 binding: 1,
2552 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2553 },
2554 ],
2555 });
2556 *entry = ImageTex::User {
2557 bind,
2558 w,
2559 h,
2560 last_used_frame: self.frame_index,
2561 bytes: 0,
2562 };
2563 }
2564
2565 pub fn set_image_nv12(
2566 &mut self,
2567 handle: u64,
2568 w: u32,
2569 h: u32,
2570 y: &[u8],
2571 uv: &[u8],
2572 color_info: ColorInfo,
2573 ) -> anyhow::Result<()> {
2574 let y_expected = (w as usize) * (h as usize);
2575 let uv_w = w.div_ceil(2);
2576 let uv_h = h.div_ceil(2);
2577 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
2578
2579 if y.len() < y_expected {
2580 return Err(anyhow::anyhow!("Y plane too small"));
2581 }
2582 if uv.len() < uv_expected {
2583 return Err(anyhow::anyhow!("UV plane too small"));
2584 }
2585
2586 let needs_recreate = match self.images.get(&handle) {
2587 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
2588 _ => true,
2589 };
2590
2591 let yuv = color_info.to_yuv_transform();
2593 let yuv_raw = YuvTransformRaw {
2594 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2595 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2596 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2597 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
2598 };
2599
2600 if needs_recreate {
2601 self.remove_image(handle);
2602
2603 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
2604 label: Some("nv12 Y"),
2605 size: wgpu::Extent3d {
2606 width: w,
2607 height: h,
2608 depth_or_array_layers: 1,
2609 },
2610 mip_level_count: 1,
2611 sample_count: 1,
2612 dimension: wgpu::TextureDimension::D2,
2613 format: wgpu::TextureFormat::R8Unorm,
2614 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2615 view_formats: &[],
2616 });
2617 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
2618
2619 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
2620 label: Some("nv12 UV"),
2621 size: wgpu::Extent3d {
2622 width: uv_w,
2623 height: uv_h,
2624 depth_or_array_layers: 1,
2625 },
2626 mip_level_count: 1,
2627 sample_count: 1,
2628 dimension: wgpu::TextureDimension::D2,
2629 format: wgpu::TextureFormat::Rg8Unorm,
2630 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2631 view_formats: &[],
2632 });
2633 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
2634
2635 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2637 label: Some("nv12 yuv transform"),
2638 size: std::mem::size_of::<YuvTransformRaw>() as u64,
2639 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2640 mapped_at_creation: false,
2641 });
2642
2643 self.queue
2645 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2646
2647 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2648 label: Some("nv12 bind"),
2649 layout: &self.image_bind_layout_nv12,
2650 entries: &[
2651 wgpu::BindGroupEntry {
2652 binding: 0,
2653 resource: wgpu::BindingResource::TextureView(&view_y),
2654 },
2655 wgpu::BindGroupEntry {
2656 binding: 1,
2657 resource: wgpu::BindingResource::TextureView(&view_uv),
2658 },
2659 wgpu::BindGroupEntry {
2660 binding: 2,
2661 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2662 },
2663 wgpu::BindGroupEntry {
2664 binding: 3,
2665 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2666 buffer: &yuv_buf,
2667 offset: 0,
2668 size: None,
2669 }),
2670 },
2671 ],
2672 });
2673
2674 let bytes = (w as u64) * (h as u64)
2675 + (uv_w as u64) * (uv_h as u64) * 2
2676 + std::mem::size_of::<YuvTransformRaw>() as u64;
2677 self.image_bytes_total += bytes;
2678
2679 self.images.insert(
2680 handle,
2681 ImageTex::Nv12 {
2682 tex_y,
2683 tex_uv,
2684 bind,
2685 yuv_buf,
2686 w,
2687 h,
2688 color_info,
2689 last_used_frame: self.frame_index,
2690 bytes,
2691 },
2692 );
2693 } else {
2694 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
2696 self.queue
2697 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2698 }
2699 }
2700
2701 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
2702 Some(ImageTex::Nv12 {
2703 tex_y,
2704 tex_uv,
2705 bind,
2706 ..
2707 }) => (tex_y, tex_uv, bind),
2708 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
2709 };
2710
2711 self.queue.write_texture(
2712 wgpu::TexelCopyTextureInfo {
2713 texture: tex_y,
2714 mip_level: 0,
2715 origin: wgpu::Origin3d::ZERO,
2716 aspect: wgpu::TextureAspect::All,
2717 },
2718 &y[..y_expected],
2719 wgpu::TexelCopyBufferLayout {
2720 offset: 0,
2721 bytes_per_row: Some(w),
2722 rows_per_image: Some(h),
2723 },
2724 wgpu::Extent3d {
2725 width: w,
2726 height: h,
2727 depth_or_array_layers: 1,
2728 },
2729 );
2730
2731 self.queue.write_texture(
2732 wgpu::TexelCopyTextureInfo {
2733 texture: tex_uv,
2734 mip_level: 0,
2735 origin: wgpu::Origin3d::ZERO,
2736 aspect: wgpu::TextureAspect::All,
2737 },
2738 &uv[..uv_expected],
2739 wgpu::TexelCopyBufferLayout {
2740 offset: 0,
2741 bytes_per_row: Some(2 * uv_w),
2742 rows_per_image: Some(uv_h),
2743 },
2744 wgpu::Extent3d {
2745 width: uv_w,
2746 height: uv_h,
2747 depth_or_array_layers: 1,
2748 },
2749 );
2750
2751 self.evict_budget_excess();
2752 Ok(())
2753 }
2754
2755 pub fn set_image_planes(
2756 &mut self,
2757 handle: u64,
2758 w: u32,
2759 h: u32,
2760 pixel_format: PixelFormat,
2761 planes: &[&[u8]],
2762 color_info: ColorInfo,
2763 ) -> anyhow::Result<()> {
2764 match pixel_format {
2765 PixelFormat::Nv12 => {
2766 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
2767 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
2768 self.set_image_nv12(handle, w, h, y, uv, color_info)
2769 }
2770 PixelFormat::P010 => {
2771 let y = planes.first().ok_or(anyhow::anyhow!("missing Y plane"))?;
2772 let uv = planes.get(1).ok_or(anyhow::anyhow!("missing UV plane"))?;
2773 self.set_image_p010(handle, w, h, y, uv, color_info)
2774 }
2775 PixelFormat::I420 | PixelFormat::I444 => Err(anyhow::anyhow!(
2776 "I420/I444 not implemented and unlikely -> cheap to convert to NV12 (better for the GPU too)"
2777 )),
2778 PixelFormat::Rgba => {
2779 let rgba = planes
2780 .first()
2781 .ok_or(anyhow::anyhow!("missing RGBA plane"))?;
2782 self.set_image_rgba8(handle, w, h, rgba, false)
2783 }
2784 }
2785 }
2786
2787 fn set_image_p010(
2788 &mut self,
2789 handle: u64,
2790 w: u32,
2791 h: u32,
2792 y: &[u8],
2793 uv: &[u8],
2794 color_info: ColorInfo,
2795 ) -> anyhow::Result<()> {
2796 let uv_w = w.div_ceil(2);
2797 let uv_h = h.div_ceil(2);
2798
2799 let y_expected = (w as usize) * 2;
2800 let uv_expected = (uv_w as usize) * (uv_h as usize) * 4;
2801
2802 if y.len() < y_expected {
2803 return Err(anyhow::anyhow!("P010 Y plane too small"));
2804 }
2805 if uv.len() < uv_expected {
2806 return Err(anyhow::anyhow!("P010 UV plane too small"));
2807 }
2808
2809 let needs_recreate = match self.images.get(&handle) {
2813 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
2814 _ => true,
2815 };
2816
2817 let yuv = color_info.to_yuv_transform();
2818 let yuv_raw = YuvTransformRaw {
2819 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2820 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
2821 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
2822 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
2823 };
2824
2825 if needs_recreate {
2826 self.remove_image(handle);
2827
2828 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
2829 label: Some("p010 Y"),
2830 size: wgpu::Extent3d {
2831 width: w,
2832 height: h,
2833 depth_or_array_layers: 1,
2834 },
2835 mip_level_count: 1,
2836 sample_count: 1,
2837 dimension: wgpu::TextureDimension::D2,
2838 format: wgpu::TextureFormat::R16Unorm,
2839 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2840 view_formats: &[],
2841 });
2842 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
2843
2844 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
2845 label: Some("p010 UV"),
2846 size: wgpu::Extent3d {
2847 width: uv_w,
2848 height: uv_h,
2849 depth_or_array_layers: 1,
2850 },
2851 mip_level_count: 1,
2852 sample_count: 1,
2853 dimension: wgpu::TextureDimension::D2,
2854 format: wgpu::TextureFormat::Rg16Unorm,
2855 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2856 view_formats: &[],
2857 });
2858 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
2859
2860 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
2861 label: Some("p010 yuv transform"),
2862 size: std::mem::size_of::<YuvTransformRaw>() as u64,
2863 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
2864 mapped_at_creation: false,
2865 });
2866 self.queue
2867 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2868
2869 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
2870 label: Some("p010 bind"),
2871 layout: &self.image_bind_layout_nv12,
2872 entries: &[
2873 wgpu::BindGroupEntry {
2874 binding: 0,
2875 resource: wgpu::BindingResource::TextureView(&view_y),
2876 },
2877 wgpu::BindGroupEntry {
2878 binding: 1,
2879 resource: wgpu::BindingResource::TextureView(&view_uv),
2880 },
2881 wgpu::BindGroupEntry {
2882 binding: 2,
2883 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
2884 },
2885 wgpu::BindGroupEntry {
2886 binding: 3,
2887 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
2888 buffer: &yuv_buf,
2889 offset: 0,
2890 size: None,
2891 }),
2892 },
2893 ],
2894 });
2895
2896 let bytes = (w as u64) * 2
2897 + (uv_w as u64) * (uv_h as u64) * 4
2898 + std::mem::size_of::<YuvTransformRaw>() as u64;
2899 self.image_bytes_total += bytes;
2900
2901 self.images.insert(
2902 handle,
2903 ImageTex::Nv12 {
2904 tex_y,
2905 tex_uv,
2906 bind,
2907 yuv_buf,
2908 w,
2909 h,
2910 color_info,
2911 last_used_frame: self.frame_index,
2912 bytes,
2913 },
2914 );
2915 } else {
2916 if let Some(ImageTex::Nv12 { yuv_buf, .. }) = self.images.get(&handle) {
2917 self.queue
2918 .write_buffer(yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
2919 }
2920 }
2921
2922 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
2923 Some(ImageTex::Nv12 {
2924 tex_y,
2925 tex_uv,
2926 bind,
2927 ..
2928 }) => (tex_y, tex_uv, bind),
2929 _ => return Err(anyhow::anyhow!("Handle is not P010/NV12")),
2930 };
2931
2932 self.queue.write_texture(
2933 wgpu::TexelCopyTextureInfo {
2934 texture: tex_y,
2935 mip_level: 0,
2936 origin: wgpu::Origin3d::ZERO,
2937 aspect: wgpu::TextureAspect::All,
2938 },
2939 &y[..y_expected],
2940 wgpu::TexelCopyBufferLayout {
2941 offset: 0,
2942 bytes_per_row: Some(w * 2),
2943 rows_per_image: Some(h),
2944 },
2945 wgpu::Extent3d {
2946 width: w,
2947 height: h,
2948 depth_or_array_layers: 1,
2949 },
2950 );
2951 self.queue.write_texture(
2952 wgpu::TexelCopyTextureInfo {
2953 texture: tex_uv,
2954 mip_level: 0,
2955 origin: wgpu::Origin3d::ZERO,
2956 aspect: wgpu::TextureAspect::All,
2957 },
2958 &uv[..uv_expected],
2959 wgpu::TexelCopyBufferLayout {
2960 offset: 0,
2961 bytes_per_row: Some(uv_w * 4),
2962 rows_per_image: Some(uv_h),
2963 },
2964 wgpu::Extent3d {
2965 width: uv_w,
2966 height: uv_h,
2967 depth_or_array_layers: 1,
2968 },
2969 );
2970
2971 self.evict_budget_excess();
2972 Ok(())
2973 }
2974
2975 #[cfg(target_os = "linux")]
2976 pub fn set_image_dmabuf(
2977 &mut self,
2978 handle: u64,
2979 w: u32,
2980 h: u32,
2981 fds: Vec<std::os::unix::io::OwnedFd>,
2982 modifier: u64,
2983 strides: Vec<u32>,
2984 offsets: Vec<u64>,
2985 color_info: ColorInfo,
2986 ) -> anyhow::Result<()> {
2987 log::info!(
2988 "set_image_dmabuf handle={handle} {}x{} fds={} modifier=0x{modifier:x}",
2989 w,
2990 h,
2991 fds.len()
2992 );
2993
2994 self.remove_image(handle);
2995
2996 let yuv = color_info.to_yuv_transform();
2997 let yuv_raw = YuvTransformRaw {
2998 row0: [yuv.m[0][0], yuv.m[0][1], yuv.m[0][2], 0.0],
2999 row1: [yuv.m[1][0], yuv.m[1][1], yuv.m[1][2], 0.0],
3000 row2: [yuv.m[2][0], yuv.m[2][1], yuv.m[2][2], 0.0],
3001 b: [yuv.b[0], yuv.b[1], yuv.b[2], 0.0],
3002 };
3003
3004 if fds.len() != 2 {
3005 return Err(anyhow::anyhow!(
3006 "unsupported fd count {} - need exactly 2 for separate Y/UV planes",
3007 fds.len()
3008 ));
3009 }
3010
3011 let uv_w = w.div_ceil(2);
3012 let uv_h = h.div_ceil(2);
3013
3014 let hal_y_desc = wgpu::hal::TextureDescriptor {
3015 label: Some("dmabuf y"),
3016 size: wgpu::Extent3d {
3017 width: w,
3018 height: h,
3019 depth_or_array_layers: 1,
3020 },
3021 mip_level_count: 1,
3022 sample_count: 1,
3023 dimension: wgpu::TextureDimension::D2,
3024 format: wgpu::TextureFormat::R8Unorm,
3025 usage: wgpu::wgt::TextureUses::RESOURCE,
3026 memory_flags: wgpu::hal::MemoryFlags::empty(),
3027 view_formats: vec![],
3028 };
3029 let hal_uv_desc = wgpu::hal::TextureDescriptor {
3030 label: Some("dmabuf uv"),
3031 size: wgpu::Extent3d {
3032 width: uv_w,
3033 height: uv_h,
3034 depth_or_array_layers: 1,
3035 },
3036 mip_level_count: 1,
3037 sample_count: 1,
3038 dimension: wgpu::TextureDimension::D2,
3039 format: wgpu::TextureFormat::Rg8Unorm,
3040 usage: wgpu::wgt::TextureUses::RESOURCE,
3041 memory_flags: wgpu::hal::MemoryFlags::empty(),
3042 view_formats: vec![],
3043 };
3044
3045 let wgpu_y_desc = wgpu::TextureDescriptor {
3046 label: Some("dmabuf y"),
3047 size: wgpu::Extent3d {
3048 width: w,
3049 height: h,
3050 depth_or_array_layers: 1,
3051 },
3052 mip_level_count: 1,
3053 sample_count: 1,
3054 dimension: wgpu::TextureDimension::D2,
3055 format: wgpu::TextureFormat::R8Unorm,
3056 usage: wgpu::TextureUsages::TEXTURE_BINDING,
3057 view_formats: &[],
3058 };
3059 let wgpu_uv_desc = wgpu::TextureDescriptor {
3060 label: Some("dmabuf uv"),
3061 size: wgpu::Extent3d {
3062 width: uv_w,
3063 height: uv_h,
3064 depth_or_array_layers: 1,
3065 },
3066 mip_level_count: 1,
3067 sample_count: 1,
3068 dimension: wgpu::TextureDimension::D2,
3069 format: wgpu::TextureFormat::Rg8Unorm,
3070 usage: wgpu::TextureUsages::TEXTURE_BINDING,
3071 view_formats: &[],
3072 };
3073
3074 let (tex_y, view_y, tex_uv, view_uv) = unsafe {
3075 let hal_guard = self
3076 .device
3077 .as_hal::<wgpu::hal::vulkan::Api>()
3078 .ok_or_else(|| {
3079 log::warn!("as_hal::<vulkan::Api> returned None");
3080 anyhow::anyhow!("Device is not Vulkan")
3081 })?;
3082
3083 let mut fds = fds;
3084 let uv_fd = fds.remove(1);
3085 let y_fd = fds.remove(0);
3086
3087 let yt = hal_guard
3088 .texture_from_dmabuf_fd(y_fd, &hal_y_desc, modifier, strides[0] as u64, offsets[0])
3089 .map_err(|e| anyhow::anyhow!("import Y dmabuf: {e:?}"))?;
3090 log::info!("imported Y dmabuf OK");
3091
3092 let uvt = hal_guard
3093 .texture_from_dmabuf_fd(
3094 uv_fd,
3095 &hal_uv_desc,
3096 modifier,
3097 strides[1] as u64,
3098 offsets[1],
3099 )
3100 .map_err(|e| anyhow::anyhow!("import UV dmabuf: {e:?}"))?;
3101 log::info!("imported UV dmabuf OK");
3102
3103 drop(hal_guard);
3104
3105 let tex_y = self
3106 .device
3107 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
3108 yt,
3109 &wgpu_y_desc,
3110 wgpu::wgt::TextureUses::UNINITIALIZED,
3111 );
3112 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
3113
3114 let tex_uv = self
3115 .device
3116 .create_texture_from_hal::<wgpu::hal::vulkan::Api>(
3117 uvt,
3118 &wgpu_uv_desc,
3119 wgpu::wgt::TextureUses::UNINITIALIZED,
3120 );
3121 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
3122
3123 (tex_y, view_y, tex_uv, view_uv)
3124 };
3125
3126 let yuv_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
3127 label: Some("dmabuf yuv transform"),
3128 size: std::mem::size_of::<YuvTransformRaw>() as u64,
3129 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
3130 mapped_at_creation: false,
3131 });
3132 self.queue
3133 .write_buffer(&yuv_buf, 0, bytemuck::bytes_of(&yuv_raw));
3134
3135 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3136 label: Some("dmabuf nv12 bind"),
3137 layout: &self.image_bind_layout_nv12,
3138 entries: &[
3139 wgpu::BindGroupEntry {
3140 binding: 0,
3141 resource: wgpu::BindingResource::TextureView(&view_y),
3142 },
3143 wgpu::BindGroupEntry {
3144 binding: 1,
3145 resource: wgpu::BindingResource::TextureView(&view_uv),
3146 },
3147 wgpu::BindGroupEntry {
3148 binding: 2,
3149 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3150 },
3151 wgpu::BindGroupEntry {
3152 binding: 3,
3153 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
3154 buffer: &yuv_buf,
3155 offset: 0,
3156 size: None,
3157 }),
3158 },
3159 ],
3160 });
3161
3162 let bytes = (w as u64) * (h as u64)
3163 + (uv_w as u64) * (uv_h as u64) * 2
3164 + std::mem::size_of::<YuvTransformRaw>() as u64;
3165
3166 self.images.insert(
3167 handle,
3168 ImageTex::Nv12 {
3169 tex_y,
3170 tex_uv,
3171 bind,
3172 yuv_buf,
3173 w,
3174 h,
3175 color_info,
3176 last_used_frame: self.frame_index,
3177 bytes,
3178 },
3179 );
3180
3181 self.evict_budget_excess();
3182 Ok(())
3183 }
3184
3185 pub fn remove_image(&mut self, handle: u64) {
3186 if let Some(img) = self.images.remove(&handle) {
3187 let b = match &img {
3188 ImageTex::Rgba { bytes, .. } => *bytes,
3189 ImageTex::Nv12 { bytes, .. } => *bytes,
3190 ImageTex::User { bytes, .. } => *bytes,
3191 };
3192 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
3193 }
3194 self.retained.remove(&handle);
3195 }
3196
3197 fn evict_image_gpu(&mut self, handle: u64) -> u64 {
3198 let Some(img) = self.images.remove(&handle) else {
3199 return 0;
3200 };
3201 let b = match &img {
3202 ImageTex::Rgba { bytes, .. } => *bytes,
3203 ImageTex::Nv12 { bytes, .. } => *bytes,
3204 ImageTex::User { bytes, .. } => *bytes,
3205 };
3206 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
3207 b
3208 }
3209
3210 fn revive_retained_image(&mut self, handle: u64) -> bool {
3211 if self.images.contains_key(&handle) {
3212 return true;
3213 }
3214 let Some(r) = self.retained.get(&handle).cloned() else {
3215 return false;
3216 };
3217 let (tex, bind) = self.create_rgba_tex(r.w, r.h, r.format);
3218
3219 self.queue.write_texture(
3220 wgpu::TexelCopyTextureInfo {
3221 texture: &tex,
3222 mip_level: 0,
3223 origin: wgpu::Origin3d::ZERO,
3224 aspect: wgpu::TextureAspect::All,
3225 },
3226 &r.rgba,
3227 wgpu::TexelCopyBufferLayout {
3228 offset: 0,
3229 bytes_per_row: Some(4 * r.w),
3230 rows_per_image: Some(r.h),
3231 },
3232 wgpu::Extent3d {
3233 width: r.w,
3234 height: r.h,
3235 depth_or_array_layers: 1,
3236 },
3237 );
3238
3239 let bytes = (r.w as u64) * (r.h as u64) * 4;
3240 self.image_bytes_total += bytes;
3241 self.images.insert(
3242 handle,
3243 ImageTex::Rgba {
3244 tex,
3245 bind,
3246 w: r.w,
3247 h: r.h,
3248 format: r.format,
3249 last_used_frame: self.frame_index,
3250 bytes,
3251 },
3252 );
3253 true
3254 }
3255
3256 fn resolve_image_for_draw(&mut self, handle: u64) -> Option<(u32, u32, bool)> {
3257 if let Some(t) = self.images.get_mut(&handle) {
3258 return match t {
3259 ImageTex::Rgba {
3260 w,
3261 h,
3262 last_used_frame,
3263 ..
3264 } => {
3265 *last_used_frame = self.frame_index;
3266 Some((*w, *h, false))
3267 }
3268 ImageTex::User {
3269 w,
3270 h,
3271 last_used_frame,
3272 ..
3273 } => {
3274 *last_used_frame = self.frame_index;
3275 Some((*w, *h, false))
3276 }
3277 ImageTex::Nv12 {
3278 w,
3279 h,
3280 last_used_frame,
3281 ..
3282 } => {
3283 *last_used_frame = self.frame_index;
3284 Some((*w, *h, true))
3285 }
3286 };
3287 }
3288 if self.revive_retained_image(handle)
3289 && let Some(ImageTex::Rgba {
3290 w,
3291 h,
3292 last_used_frame,
3293 ..
3294 }) = self.images.get_mut(&handle)
3295 {
3296 *last_used_frame = self.frame_index;
3297 return Some((*w, *h, false));
3298 }
3299 None
3300 }
3301
3302 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
3304 let handle = self.next_image_handle;
3305 self.next_image_handle += 1;
3306 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
3307 log::error!("Failed to register image: {e}");
3308 }
3309 handle
3310 }
3311
3312 fn evict_unused_images(&mut self) {
3313 let now = self.frame_index;
3314 let evict_after = self.image_evict_after_frames;
3315
3316 let mut to_evict = Vec::new();
3319 for (h, t) in self.images.iter() {
3320 let last = match t {
3321 ImageTex::Rgba {
3322 last_used_frame, ..
3323 } => *last_used_frame,
3324 ImageTex::User {
3325 last_used_frame, ..
3326 } => *last_used_frame,
3327 ImageTex::Nv12 {
3328 last_used_frame, ..
3329 } => *last_used_frame,
3330 };
3331 if now.saturating_sub(last) > evict_after {
3332 to_evict.push(*h);
3333 }
3334 }
3335 for h in to_evict {
3336 if self.retained.contains_key(&h) {
3337 self.evict_image_gpu(h);
3338 } else {
3339 self.remove_image(h);
3340 }
3341 }
3342
3343 self.evict_budget_excess();
3344 }
3345
3346 fn evict_budget_excess(&mut self) {
3347 if self.image_bytes_total <= self.image_budget_bytes {
3348 return;
3349 }
3350 let mut candidates: Vec<(u64, u64, u64)> = self
3352 .images
3353 .iter()
3354 .map(|(h, t)| {
3355 let (last, bytes) = match t {
3356 ImageTex::Rgba {
3357 last_used_frame,
3358 bytes,
3359 ..
3360 } => (*last_used_frame, *bytes),
3361 ImageTex::User {
3362 last_used_frame,
3363 bytes,
3364 ..
3365 } => (*last_used_frame, *bytes),
3366 ImageTex::Nv12 {
3367 last_used_frame,
3368 bytes,
3369 ..
3370 } => (*last_used_frame, *bytes),
3371 };
3372 (*h, last, bytes)
3373 })
3374 .collect();
3375
3376 candidates.sort_by_key(|k| k.1);
3378
3379 let now = self.frame_index;
3380 for (h, last, _bytes) in candidates {
3381 if self.image_bytes_total <= self.image_budget_bytes {
3382 break;
3383 }
3384 if last == now {
3386 continue;
3387 }
3388 if self.retained.contains_key(&h) {
3389 self.evict_image_gpu(h);
3390 } else {
3391 self.remove_image(h);
3392 }
3393 }
3394 }
3395
3396 pub fn set_pixels_per_point(&mut self, ppp: f32) {
3398 self.pixels_per_point = ppp.clamp(0.5, 8.0);
3399 }
3400
3401 pub fn set_working_space(&mut self, enabled: bool) {
3405 if enabled == self.working_space {
3406 return;
3407 }
3408 self.working_space = enabled;
3409 if enabled {
3410 self.ensure_display_pipeline();
3411 self.recreate_working_space_texture();
3412 } else {
3413 self.ws_tex = None;
3414 self.ws_view = None;
3415 self.ws_bind = None;
3416 }
3417 }
3418
3419 fn ensure_display_pipeline(&mut self) {
3420 if self.display_pipeline.is_some() {
3421 return;
3422 }
3423
3424 let layout = self
3425 .device
3426 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
3427 label: Some("display transform layout"),
3428 entries: &[
3429 wgpu::BindGroupLayoutEntry {
3430 binding: 0,
3431 visibility: wgpu::ShaderStages::FRAGMENT,
3432 ty: wgpu::BindingType::Texture {
3433 multisampled: false,
3434 view_dimension: wgpu::TextureViewDimension::D2,
3435 sample_type: wgpu::TextureSampleType::Float { filterable: true },
3436 },
3437 count: None,
3438 },
3439 wgpu::BindGroupLayoutEntry {
3440 binding: 1,
3441 visibility: wgpu::ShaderStages::FRAGMENT,
3442 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
3443 count: None,
3444 },
3445 ],
3446 });
3447 self.display_layout = Some(layout);
3448
3449 let shader = self
3450 .device
3451 .create_shader_module(wgpu::ShaderModuleDescriptor {
3452 label: Some("display_transform.wgsl"),
3453 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
3454 "shaders/display_transform.wgsl"
3455 ))),
3456 });
3457
3458 let pipeline_layout = self
3459 .device
3460 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
3461 label: Some("display transform pipeline layout"),
3462 bind_group_layouts: &[None, self.display_layout.as_ref()],
3463 immediate_size: 0,
3464 });
3465
3466 let pipeline = self
3467 .device
3468 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3469 label: Some("display transform pipeline"),
3470 layout: Some(&pipeline_layout),
3471 vertex: wgpu::VertexState {
3472 module: &shader,
3473 entry_point: Some("vs_main"),
3474 buffers: &[],
3475 compilation_options: wgpu::PipelineCompilationOptions::default(),
3476 },
3477 fragment: Some(wgpu::FragmentState {
3478 module: &shader,
3479 entry_point: Some("fs_main"),
3480 targets: &[Some(wgpu::ColorTargetState {
3481 format: self.output_format,
3482 blend: None,
3483 write_mask: wgpu::ColorWrites::ALL,
3484 })],
3485 compilation_options: wgpu::PipelineCompilationOptions::default(),
3486 }),
3487 primitive: wgpu::PrimitiveState::default(),
3488 depth_stencil: None,
3489 multisample: wgpu::MultisampleState::default(),
3490 multiview_mask: None,
3491 cache: None,
3492 });
3493 self.display_pipeline = Some(pipeline);
3494 }
3495
3496 pub fn resize(&mut self, width: u32, height: u32) {
3501 self.output_width = width;
3502 self.output_height = height;
3503 self.recreate_msaa_and_depth_stencil();
3504 self.recreate_working_space_texture();
3505 }
3506
3507 fn recreate_working_space_texture(&mut self) {
3508 if !self.working_space {
3509 return;
3510 }
3511 let w = self.output_width.max(1);
3512 let h = self.output_height.max(1);
3513
3514 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3515 label: Some("working space"),
3516 size: wgpu::Extent3d {
3517 width: w,
3518 height: h,
3519 depth_or_array_layers: 1,
3520 },
3521 mip_level_count: 1,
3522 sample_count: 1,
3523 dimension: wgpu::TextureDimension::D2,
3524 format: wgpu::TextureFormat::Rgba16Float,
3525 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
3526 view_formats: &[],
3527 });
3528 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3529
3530 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3531 label: Some("working space bind"),
3532 layout: self.display_layout.as_ref().unwrap(),
3533 entries: &[
3534 wgpu::BindGroupEntry {
3535 binding: 0,
3536 resource: wgpu::BindingResource::TextureView(&view),
3537 },
3538 wgpu::BindGroupEntry {
3539 binding: 1,
3540 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
3541 },
3542 ],
3543 });
3544
3545 self.ws_tex = Some(tex);
3546 self.ws_view = Some(view);
3547 self.ws_bind = Some(bind);
3548 }
3549
3550 fn recreate_msaa_and_depth_stencil(&mut self) {
3551 if self.msaa_samples > 1 {
3552 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3553 label: Some("msaa color"),
3554 size: wgpu::Extent3d {
3555 width: self.output_width.max(1),
3556 height: self.output_height.max(1),
3557 depth_or_array_layers: 1,
3558 },
3559 mip_level_count: 1,
3560 sample_count: self.msaa_samples,
3561 dimension: wgpu::TextureDimension::D2,
3562 format: self.output_format,
3563 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3564 view_formats: &[],
3565 });
3566 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3567 self.msaa_tex = Some(tex);
3568 self.msaa_view = Some(view);
3569 } else {
3570 self.msaa_tex = None;
3571 self.msaa_view = None;
3572 }
3573
3574 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
3575 label: Some("depth-stencil (stencil clips)"),
3576 size: wgpu::Extent3d {
3577 width: self.output_width.max(1),
3578 height: self.output_height.max(1),
3579 depth_or_array_layers: 1,
3580 },
3581 mip_level_count: 1,
3582 sample_count: self.msaa_samples,
3583 dimension: wgpu::TextureDimension::D2,
3584 format: wgpu::TextureFormat::Depth24PlusStencil8,
3585 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3586 view_formats: &[],
3587 });
3588 self.depth_stencil_view = self
3589 .depth_stencil_tex
3590 .create_view(&wgpu::TextureViewDescriptor::default());
3591 }
3592
3593 fn get_or_create_layer(
3594 &mut self,
3595 layer_id: u32,
3596 width: u32,
3597 height: u32,
3598 rect: repose_core::Rect,
3599 ) {
3600 let needs_alloc = match self.layer_pool.get(&layer_id) {
3601 Some(lt) => lt.width != width || lt.height != height,
3602 None => true,
3603 };
3604 if !needs_alloc {
3605 if let Some(lt) = self.layer_pool.get_mut(&layer_id) {
3606 lt.rect_px = (rect.x, rect.y, rect.w, rect.h);
3607 }
3608 return;
3609 }
3610 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3611 label: Some("graphics layer"),
3612 size: wgpu::Extent3d {
3613 width: width.max(1),
3614 height: height.max(1),
3615 depth_or_array_layers: 1,
3616 },
3617 mip_level_count: 1,
3618 sample_count: 1,
3619 dimension: wgpu::TextureDimension::D2,
3620 format: self.output_format,
3621 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
3622 view_formats: &[],
3623 });
3624 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3625 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3626 label: Some("layer bind"),
3627 layout: &self.image_bind_layout_rgba,
3628 entries: &[
3629 wgpu::BindGroupEntry {
3630 binding: 0,
3631 resource: wgpu::BindingResource::TextureView(&view),
3632 },
3633 wgpu::BindGroupEntry {
3634 binding: 1,
3635 resource: wgpu::BindingResource::Sampler(&self.layer_sampler),
3636 },
3637 ],
3638 });
3639 let bind_linear = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3640 label: Some("layer bind linear"),
3641 layout: &self.image_bind_layout_rgba,
3642 entries: &[
3643 wgpu::BindGroupEntry {
3644 binding: 0,
3645 resource: wgpu::BindingResource::TextureView(&view),
3646 },
3647 wgpu::BindGroupEntry {
3648 binding: 1,
3649 resource: wgpu::BindingResource::Sampler(&self.layer_sampler_linear),
3650 },
3651 ],
3652 });
3653 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
3654 label: Some("graphics layer depth-stencil"),
3655 size: wgpu::Extent3d {
3656 width: width.max(1),
3657 height: height.max(1),
3658 depth_or_array_layers: 1,
3659 },
3660 mip_level_count: 1,
3661 sample_count: 1,
3662 dimension: wgpu::TextureDimension::D2,
3663 format: wgpu::TextureFormat::Depth24PlusStencil8,
3664 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3665 view_formats: &[],
3666 });
3667 let depth_stencil_view =
3668 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
3669 self.layer_pool.insert(
3670 layer_id,
3671 LayerTarget {
3672 view,
3673 bind,
3674 bind_linear,
3675 depth_stencil_view,
3676 width,
3677 height,
3678 rect_px: (rect.x, rect.y, rect.w, rect.h),
3679 },
3680 );
3681 }
3682
3683 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
3684 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3685 label: Some("atlas bind"),
3686 layout: &self.text_bind_layout,
3687 entries: &[
3688 wgpu::BindGroupEntry {
3689 binding: 0,
3690 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
3691 },
3692 wgpu::BindGroupEntry {
3693 binding: 1,
3694 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
3695 },
3696 ],
3697 })
3698 }
3699
3700 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
3701 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3702 label: Some("atlas bind color"),
3703 layout: &self.text_bind_layout,
3704 entries: &[
3705 wgpu::BindGroupEntry {
3706 binding: 0,
3707 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
3708 },
3709 wgpu::BindGroupEntry {
3710 binding: 1,
3711 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
3712 },
3713 ],
3714 })
3715 }
3716
3717 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
3718 let keyp = (key, px.to_bits());
3719 if let Some(info) = self.atlas_mask.map.get(&keyp) {
3720 return Some(*info);
3721 }
3722
3723 let gb = repose_text::rasterize(key, px)?;
3724 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
3725 return None;
3726 }
3727
3728 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
3729
3730 let w = gb.w.max(1);
3731 let h = gb.h.max(1);
3732
3733 if !self.alloc_space_mask(w, h) {
3734 self.grow_mask_and_rebuild();
3735 }
3736 if !self.alloc_space_mask(w, h) {
3737 return None;
3738 }
3739 let x = self.atlas_mask.next_x;
3740 let y = self.atlas_mask.next_y;
3741 self.atlas_mask.next_x += w + 1;
3742 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
3743
3744 let layout = wgpu::TexelCopyBufferLayout {
3745 offset: 0,
3746 bytes_per_row: Some(w),
3747 rows_per_image: Some(h),
3748 };
3749 let size = wgpu::Extent3d {
3750 width: w,
3751 height: h,
3752 depth_or_array_layers: 1,
3753 };
3754 self.queue.write_texture(
3755 wgpu::TexelCopyTextureInfoBase {
3756 texture: &self.atlas_mask.tex,
3757 mip_level: 0,
3758 origin: wgpu::Origin3d { x, y, z: 0 },
3759 aspect: wgpu::TextureAspect::All,
3760 },
3761 &coverage,
3762 layout,
3763 size,
3764 );
3765
3766 let info = GlyphInfo {
3767 u0: x as f32 / self.atlas_mask.size as f32,
3768 v0: y as f32 / self.atlas_mask.size as f32,
3769 u1: (x + w) as f32 / self.atlas_mask.size as f32,
3770 v1: (y + h) as f32 / self.atlas_mask.size as f32,
3771 w: w as f32,
3772 h: h as f32,
3773 };
3774 self.atlas_mask.map.insert(keyp, info);
3775 Some(info)
3776 }
3777
3778 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: f32) -> Option<GlyphInfo> {
3779 let keyp = (key, px.to_bits());
3780 if let Some(info) = self.atlas_color.map.get(&keyp) {
3781 return Some(*info);
3782 }
3783 let gb = repose_text::rasterize(key, px)?;
3784 if !matches!(gb.content, repose_text::SwashContent::Color) {
3785 return None;
3786 }
3787 let w = gb.w.max(1);
3788 let h = gb.h.max(1);
3789 if !self.alloc_space_color(w, h) {
3790 self.grow_color_and_rebuild();
3791 }
3792 if !self.alloc_space_color(w, h) {
3793 return None;
3794 }
3795 let x = self.atlas_color.next_x;
3796 let y = self.atlas_color.next_y;
3797 self.atlas_color.next_x += w + 1;
3798 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
3799
3800 let layout = wgpu::TexelCopyBufferLayout {
3801 offset: 0,
3802 bytes_per_row: Some(w * 4),
3803 rows_per_image: Some(h),
3804 };
3805 let size = wgpu::Extent3d {
3806 width: w,
3807 height: h,
3808 depth_or_array_layers: 1,
3809 };
3810 self.queue.write_texture(
3811 wgpu::TexelCopyTextureInfoBase {
3812 texture: &self.atlas_color.tex,
3813 mip_level: 0,
3814 origin: wgpu::Origin3d { x, y, z: 0 },
3815 aspect: wgpu::TextureAspect::All,
3816 },
3817 &gb.data,
3818 layout,
3819 size,
3820 );
3821 let info = GlyphInfo {
3822 u0: x as f32 / self.atlas_color.size as f32,
3823 v0: y as f32 / self.atlas_color.size as f32,
3824 u1: (x + w) as f32 / self.atlas_color.size as f32,
3825 v1: (y + h) as f32 / self.atlas_color.size as f32,
3826 w: w as f32,
3827 h: h as f32,
3828 };
3829 self.atlas_color.map.insert(keyp, info);
3830 Some(info)
3831 }
3832
3833 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
3834 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
3835 self.atlas_mask.next_x = 1;
3836 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
3837 self.atlas_mask.row_h = 0;
3838 }
3839 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
3840 return false;
3841 }
3842 true
3843 }
3844
3845 fn grow_mask_and_rebuild(&mut self) {
3846 let new_size = (self.atlas_mask.size * 2).min(4096);
3847 if new_size == self.atlas_mask.size {
3848 return;
3849 }
3850 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3851 label: Some("glyph atlas A8 (grown)"),
3852 size: wgpu::Extent3d {
3853 width: new_size,
3854 height: new_size,
3855 depth_or_array_layers: 1,
3856 },
3857 mip_level_count: 1,
3858 sample_count: 1,
3859 dimension: wgpu::TextureDimension::D2,
3860 format: wgpu::TextureFormat::R8Unorm,
3861 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3862 view_formats: &[],
3863 });
3864 self.atlas_mask.tex = tex;
3865 self.atlas_mask.view = self
3866 .atlas_mask
3867 .tex
3868 .create_view(&wgpu::TextureViewDescriptor::default());
3869 self.atlas_mask.size = new_size;
3870 self.atlas_mask.next_x = 1;
3871 self.atlas_mask.next_y = 1;
3872 self.atlas_mask.row_h = 0;
3873 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
3874 self.atlas_mask.map.clear();
3875 for (k, px_bits) in keys {
3876 let _ = self.upload_glyph_mask(k, f32::from_bits(px_bits));
3877 }
3878 }
3879
3880 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
3881 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
3882 self.atlas_color.next_x = 1;
3883 self.atlas_color.next_y += self.atlas_color.row_h + 1;
3884 self.atlas_color.row_h = 0;
3885 }
3886 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
3887 return false;
3888 }
3889 true
3890 }
3891
3892 fn grow_color_and_rebuild(&mut self) {
3893 let new_size = (self.atlas_color.size * 2).min(4096);
3894 if new_size == self.atlas_color.size {
3895 return;
3896 }
3897 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
3898 label: Some("glyph atlas RGBA (grown)"),
3899 size: wgpu::Extent3d {
3900 width: new_size,
3901 height: new_size,
3902 depth_or_array_layers: 1,
3903 },
3904 mip_level_count: 1,
3905 sample_count: 1,
3906 dimension: wgpu::TextureDimension::D2,
3907 format: wgpu::TextureFormat::Rgba8UnormSrgb,
3908 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3909 view_formats: &[],
3910 });
3911 self.atlas_color.tex = tex;
3912 self.atlas_color.view = self
3913 .atlas_color
3914 .tex
3915 .create_view(&wgpu::TextureViewDescriptor::default());
3916 self.atlas_color.size = new_size;
3917 self.atlas_color.next_x = 1;
3918 self.atlas_color.next_y = 1;
3919 self.atlas_color.row_h = 0;
3920 let keys: Vec<(repose_text::GlyphKey, u32)> =
3921 self.atlas_color.map.keys().copied().collect();
3922 self.atlas_color.map.clear();
3923 for (k, px_bits) in keys {
3924 let _ = self.upload_glyph_color(k, f32::from_bits(px_bits));
3925 }
3926 }
3927}
3928
3929fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
3930 match brush {
3931 Brush::Solid(c) => (
3932 0u32,
3933 c.to_linear(),
3934 [0.0, 0.0, 0.0, 0.0],
3935 [0.0, 0.0],
3936 [0.0, 1.0],
3937 ),
3938 Brush::Linear {
3939 start,
3940 end,
3941 start_color,
3942 end_color,
3943 } => (
3944 1u32,
3945 start_color.to_linear(),
3946 end_color.to_linear(),
3947 [start.x, start.y],
3948 [end.x, end.y],
3949 ),
3950 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
3951 }
3952}
3953
3954fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
3955 match brush {
3956 Brush::Solid(c) => c.to_linear(),
3957 Brush::Linear { start_color, .. } => start_color.to_linear(),
3958 _ => [0.0; 4],
3959 }
3960}
3961
3962fn init_atlas_mask(device: &wgpu::Device) -> AtlasA8 {
3963 let size = 1024u32;
3964 let tex = device.create_texture(&wgpu::TextureDescriptor {
3965 label: Some("glyph atlas A8"),
3966 size: wgpu::Extent3d {
3967 width: size,
3968 height: size,
3969 depth_or_array_layers: 1,
3970 },
3971 mip_level_count: 1,
3972 sample_count: 1,
3973 dimension: wgpu::TextureDimension::D2,
3974 format: wgpu::TextureFormat::R8Unorm,
3975 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
3976 view_formats: &[],
3977 });
3978 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
3979 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
3980 label: Some("glyph atlas sampler A8"),
3981 address_mode_u: wgpu::AddressMode::ClampToEdge,
3982 address_mode_v: wgpu::AddressMode::ClampToEdge,
3983 address_mode_w: wgpu::AddressMode::ClampToEdge,
3984 mag_filter: wgpu::FilterMode::Linear,
3985 min_filter: wgpu::FilterMode::Linear,
3986 mipmap_filter: wgpu::MipmapFilterMode::Linear,
3987 ..Default::default()
3988 });
3989
3990 AtlasA8 {
3991 tex,
3992 view,
3993 sampler,
3994 size,
3995 next_x: 1,
3996 next_y: 1,
3997 row_h: 0,
3998 map: HashMap::new(),
3999 }
4000}
4001
4002fn init_atlas_color(device: &wgpu::Device) -> AtlasRGBA {
4003 let size = 1024u32;
4004 let tex = device.create_texture(&wgpu::TextureDescriptor {
4005 label: Some("glyph atlas RGBA"),
4006 size: wgpu::Extent3d {
4007 width: size,
4008 height: size,
4009 depth_or_array_layers: 1,
4010 },
4011 mip_level_count: 1,
4012 sample_count: 1,
4013 dimension: wgpu::TextureDimension::D2,
4014 format: wgpu::TextureFormat::Rgba8UnormSrgb,
4015 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
4016 view_formats: &[],
4017 });
4018 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4019 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4020 label: Some("glyph atlas sampler RGBA"),
4021 address_mode_u: wgpu::AddressMode::ClampToEdge,
4022 address_mode_v: wgpu::AddressMode::ClampToEdge,
4023 address_mode_w: wgpu::AddressMode::ClampToEdge,
4024 mag_filter: wgpu::FilterMode::Linear,
4025 min_filter: wgpu::FilterMode::Linear,
4026 mipmap_filter: wgpu::MipmapFilterMode::Linear,
4027 ..Default::default()
4028 });
4029 AtlasRGBA {
4030 tex,
4031 view,
4032 sampler,
4033 size,
4034 next_x: 1,
4035 next_y: 1,
4036 row_h: 0,
4037 map: HashMap::new(),
4038 }
4039}
4040
4041#[cfg(feature = "winit-surface")]
4042impl RenderBackend for WgpuSurfaceBackend {
4043 fn configure_surface(&mut self, width: u32, height: u32) {
4044 if width == 0 || height == 0 {
4045 return;
4046 }
4047 self.renderer.output_width = width;
4048 self.renderer.output_height = height;
4049 if let Some(ref mut config) = self.surface_config {
4050 config.width = width;
4051 config.height = height;
4052 }
4053 if let (Some(surface), Some(config)) = (self.surface.as_ref(), self.surface_config.as_ref())
4054 {
4055 surface.configure(&self.renderer.device, config);
4056 }
4057 self.renderer.recreate_msaa_and_depth_stencil();
4058 self.renderer.recreate_working_space_texture();
4059 }
4060
4061 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
4062 let surface = self.surface.as_ref().expect("WgpuSurfaceBackend::frame() requires a surface (use from_device + render_to_view instead)");
4063 let surface_config = self
4064 .surface_config
4065 .as_ref()
4066 .expect("surface_config required for frame()");
4067
4068 self.renderer.frame_index = self.renderer.frame_index.wrapping_add(1);
4069 self.renderer.slug_cache.next_frame();
4070
4071 if self.renderer.output_width == 0 || self.renderer.output_height == 0 {
4072 return;
4073 }
4074
4075 let mut retries = 0u32;
4076 const MAX_RETRIES: u32 = 4;
4077 let frame = loop {
4078 match surface.get_current_texture() {
4079 wgpu::CurrentSurfaceTexture::Success(f) => break f,
4080 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
4081 log::warn!("suboptimal surface; reconfiguring");
4082 surface.configure(&self.renderer.device, surface_config);
4083 break f;
4084 }
4085 wgpu::CurrentSurfaceTexture::Outdated => {
4086 retries += 1;
4087 if retries >= MAX_RETRIES {
4088 log::warn!(
4089 "surface outdated persisted after {MAX_RETRIES} retries; skipping frame"
4090 );
4091 return;
4092 }
4093 log::warn!("surface outdated; reconfiguring");
4094 surface.configure(&self.renderer.device, surface_config);
4095 }
4096 wgpu::CurrentSurfaceTexture::Lost => {
4097 retries += 1;
4098 if retries >= MAX_RETRIES {
4099 log::warn!(
4100 "surface lost persisted after {MAX_RETRIES} retries; skipping frame"
4101 );
4102 return;
4103 }
4104 log::warn!("surface lost; reconfiguring");
4105 surface.configure(&self.renderer.device, surface_config);
4106 }
4107 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
4108 request_frame();
4109 return;
4110 }
4111 wgpu::CurrentSurfaceTexture::Validation => {
4112 retries += 1;
4113 if retries >= MAX_RETRIES {
4114 log::warn!(
4115 "surface validation persisted after {MAX_RETRIES} retries; skipping frame"
4116 );
4117 return;
4118 }
4119 surface.configure(&self.renderer.device, surface_config);
4120 }
4121 }
4122 };
4123
4124 let swap_view = if let Some(view_format) = self
4125 .surface_config
4126 .as_ref()
4127 .and_then(|c| c.view_formats.iter().find(|f| f.is_srgb()).copied())
4128 {
4129 frame.texture.create_view(&wgpu::TextureViewDescriptor {
4130 format: Some(view_format),
4131 ..Default::default()
4132 })
4133 } else {
4134 frame
4135 .texture
4136 .create_view(&wgpu::TextureViewDescriptor::default())
4137 };
4138 let mut encoder =
4139 self.renderer
4140 .device
4141 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
4142 label: Some("frame encoder"),
4143 });
4144
4145 let clear_color = Some([
4146 scene.clear_color.0 as f64 / 255.0,
4147 scene.clear_color.1 as f64 / 255.0,
4148 scene.clear_color.2 as f64 / 255.0,
4149 scene.clear_color.3 as f64 / 255.0,
4150 ]);
4151
4152 self.renderer
4153 .render_scene_to_encoder(scene, &mut encoder, &swap_view, clear_color);
4154
4155 {
4158 let _reset = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
4159 label: Some("webgl color_mask reset before present"),
4160 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
4161 view: &swap_view,
4162 resolve_target: None,
4163 ops: wgpu::Operations {
4164 load: wgpu::LoadOp::Load,
4165 store: wgpu::StoreOp::Store,
4166 },
4167 depth_slice: None,
4168 })],
4169 depth_stencil_attachment: None,
4170 timestamp_writes: None,
4171 occlusion_query_set: None,
4172 multiview_mask: None,
4173 });
4174 }
4175
4176 self.renderer
4177 .queue
4178 .submit(std::iter::once(encoder.finish()));
4179 if let Err(e) = catch_unwind(AssertUnwindSafe(|| self.renderer.queue.present(frame))) {
4180 log::warn!("queue.present panicked: {:?}", e);
4181 }
4182 }
4183}
4184
4185impl WgpuSceneRenderer {
4186 fn upload_mesh_geometry(&mut self, mesh: &repose_core::VectorMeshData) -> (u64, u32, u64, u32) {
4187 let verts: Vec<MeshVertex> = mesh
4188 .vertices
4189 .iter()
4190 .map(|v| MeshVertex {
4191 pos: v.pos,
4192 color: v.color,
4193 uv: v.uv,
4194 })
4195 .collect();
4196 let vbytes = bytemuck::cast_slice(&verts);
4197 self.mesh_verts
4198 .grow_to_fit(&self.device, vbytes.len() as u64);
4199 let (voff, _) = self.mesh_verts.alloc_write(&self.queue, vbytes);
4200 let ibytes = bytemuck::cast_slice(&mesh.indices);
4201 self.mesh_indices
4202 .grow_to_fit(&self.device, ibytes.len() as u64);
4203 let (ioff, _) = self.mesh_indices.alloc_write(&self.queue, ibytes);
4204 (voff, verts.len() as u32, ioff, mesh.indices.len() as u32)
4205 }
4206
4207 fn alloc_mesh_uniform(&mut self, u: MeshUniform) -> u64 {
4208 if self.mesh_uniform_head + MESH_UNIFORM_SLOT > MESH_UNIFORM_CAP {
4209 log::warn!("mesh uniform buffer overflow; regenerating");
4210 self.recreate_mesh_uniform_buffer();
4211 }
4212 let slot = self.mesh_uniform_head;
4213 self.queue
4214 .write_buffer(&self.mesh_uniform_buf, slot, bytemuck::bytes_of(&u));
4215 self.mesh_uniform_head = slot + MESH_UNIFORM_SLOT;
4216 slot
4217 }
4218
4219 fn recreate_mesh_uniform_buffer(&mut self) {
4220 let new_cap = self.mesh_uniform_head + MESH_UNIFORM_SLOT;
4221 self.mesh_uniform_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4222 label: Some("mesh uniform buffer"),
4223 size: new_cap,
4224 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
4225 mapped_at_creation: false,
4226 });
4227 self.mesh_bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4228 label: Some("mesh uniform bind"),
4229 layout: &self.mesh_bind_layout,
4230 entries: &[wgpu::BindGroupEntry {
4231 binding: 0,
4232 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
4233 buffer: &self.mesh_uniform_buf,
4234 offset: 0,
4235 size: NonZero::new(MESH_UNIFORM_SLOT),
4236 }),
4237 }],
4238 });
4239 self.mesh_uniform_head = 0;
4240 }
4241
4242 #[allow(clippy::too_many_arguments)]
4243 fn emit_vector_mesh(
4244 &mut self,
4245 current_transform: &Transform,
4246 mesh: &repose_core::VectorMeshData,
4247 transform: [f32; 6],
4248 paint: &repose_core::PaintDesc,
4249 cmds: &mut Vec<Cmd>,
4250 ) {
4251 let affine = combine_mesh_affine(current_transform, transform);
4252 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
4253 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(affine, paint));
4254 cmds.push(Cmd::VectorMesh {
4255 voff,
4256 vcnt,
4257 ioff,
4258 icnt,
4259 uoff,
4260 });
4261 }
4262
4263 pub fn render_scene_to_encoder(
4264 &mut self,
4265 scene: &Scene,
4266 encoder: &mut wgpu::CommandEncoder,
4267 target_view: &wgpu::TextureView,
4268 clear_color_override: Option<[f64; 4]>,
4269 ) {
4270 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
4271 let x0 = (x / fb_w) * 2.0 - 1.0;
4272 let y0 = 1.0 - (y / fb_h) * 2.0;
4273 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
4274 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
4275 let min_x = x0.min(x1);
4276 let min_y = y0.min(y1);
4277 let w_ndc = (x1 - x0).abs();
4278 let h_ndc = (y1 - y0).abs();
4279 [min_x, min_y, w_ndc, h_ndc]
4280 }
4281
4282 fn rect_to_instance_ndc(
4284 rect: repose_core::Rect,
4285 transform: &Transform,
4286 fb_w: f32,
4287 fb_h: f32,
4288 ) -> ([f32; 4], [f32; 2]) {
4289 let cx = rect.x + rect.w * 0.5;
4290 let cy = rect.y + rect.h * 0.5;
4291
4292 let sx = cx * transform.scale_x;
4293 let sy = cy * transform.scale_y;
4294 let cos_a = transform.rotate.cos();
4295 let sin_a = transform.rotate.sin();
4296 let tx = sx * cos_a - sy * sin_a + transform.translate_x;
4297 let ty = sx * sin_a + sy * cos_a + transform.translate_y;
4298
4299 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
4301 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
4302 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
4304 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
4305
4306 ([ndc_cx, ndc_cy, ndc_w, ndc_h], [cos_a, sin_a])
4307 }
4308
4309 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
4310 let mut x = r.x.floor() as i64;
4311 let mut y = r.y.floor() as i64;
4312 let fb_wi = fb_w as i64;
4313 let fb_hi = fb_h as i64;
4314 x = x.clamp(0, fb_wi.saturating_sub(1));
4315 y = y.clamp(0, fb_hi.saturating_sub(1));
4316 let w_req = r.w.ceil().max(1.0) as i64;
4317 let h_req = r.h.ceil().max(1.0) as i64;
4318 let w = (w_req).min(fb_wi - x).max(1);
4319 let h = (h_req).min(fb_hi - y).max(1);
4320 (x as u32, y as u32, w as u32, h as u32)
4321 }
4322
4323 let fb_w = self.output_width as f32;
4324 let fb_h = self.output_height as f32;
4325
4326 let mut passes: Vec<Pass> = Vec::with_capacity(1);
4327 let clear_color = clear_color_override.unwrap_or_else(|| {
4328 [
4329 scene.clear_color.0 as f64 / 255.0,
4330 scene.clear_color.1 as f64 / 255.0,
4331 scene.clear_color.2 as f64 / 255.0,
4332 scene.clear_color.3 as f64 / 255.0,
4333 ]
4334 });
4335 let mut current_pass: Pass = Pass {
4336 target: PassTarget::Surface,
4337 initial_scissor: (0, 0, self.output_width, self.output_height),
4338 clear_color: Some([
4339 clear_color[0] as f32,
4340 clear_color[1] as f32,
4341 clear_color[2] as f32,
4342 clear_color[3] as f32,
4343 ]),
4344 cmds: Vec::with_capacity(scene.nodes.len()),
4345 };
4346 let mut target_stack: Vec<PassTarget> = Vec::new();
4347 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
4348 let mut layer_blurs: Vec<(u32, f32, f32)> = Vec::new();
4349 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
4350
4351 struct Batch {
4352 rects: Vec<RectInstance>,
4353 borders: Vec<BorderInstance>,
4354 ellipses: Vec<EllipseInstance>,
4355 e_borders: Vec<EllipseBorderInstance>,
4356 arcs: Vec<ArcInstance>,
4357 masks: Vec<GlyphInstance>,
4358 colors: Vec<GlyphInstance>,
4359 nv12s: Vec<Nv12Instance>,
4360 }
4361
4362 impl Batch {
4363 fn new() -> Self {
4364 Self {
4365 rects: vec![],
4366 borders: vec![],
4367 ellipses: vec![],
4368 e_borders: vec![],
4369 arcs: vec![],
4370 masks: vec![],
4371 colors: vec![],
4372 nv12s: vec![],
4373 }
4374 }
4375
4376 fn is_empty(&self) -> bool {
4377 self.rects.is_empty()
4378 && self.borders.is_empty()
4379 && self.ellipses.is_empty()
4380 && self.e_borders.is_empty()
4381 && self.arcs.is_empty()
4382 && self.masks.is_empty()
4383 && self.colors.is_empty()
4384 && self.nv12s.is_empty()
4385 }
4386
4387 fn flush(
4388 &mut self,
4389 pipes: (
4390 &mut InstancedPipe<RectInstance>,
4391 &mut InstancedPipe<BorderInstance>,
4392 &mut InstancedPipe<EllipseInstance>,
4393 &mut InstancedPipe<EllipseBorderInstance>,
4394 &mut InstancedPipe<ArcInstance>,
4395 ),
4396 glyph_pipes: (
4397 &mut InstancedPipe<GlyphInstance>,
4398 &mut InstancedPipe<GlyphInstance>,
4399 ),
4400 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
4401 device: &wgpu::Device,
4402 queue: &wgpu::Queue,
4403 cmds: &mut Vec<Cmd>,
4404 ) {
4405 let (rects, borders, ellipses, e_borders, arcs) = pipes;
4406 let (masks, colors) = glyph_pipes;
4407
4408 macro_rules! flush_one {
4409 ($buf:ident, $pipe:expr, $variant:ident) => {
4410 if !self.$buf.is_empty() {
4411 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
4412 cmds.push(Cmd::$variant { off, cnt });
4413 }
4414 self.$buf.clear();
4415 }
4416 };
4417 }
4418
4419 flush_one!(rects, rects, Rect);
4420 flush_one!(borders, borders, Border);
4421 flush_one!(ellipses, ellipses, Ellipse);
4422 flush_one!(e_borders, e_borders, EllipseBorder);
4423 flush_one!(arcs, arcs, Arc);
4424 flush_one!(masks, masks, GlyphsMask);
4425 flush_one!(colors, colors, GlyphsColor);
4426
4427 if !self.nv12s.is_empty() {
4428 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
4429 let _ = (off, cnt);
4430 }
4431 self.nv12s.clear();
4432 }
4433 }
4434 }
4435
4436 self.rects.reset();
4437 self.borders.reset();
4438 self.ellipses.reset();
4439 self.ellipse_borders.reset();
4440 self.arcs.reset();
4441 self.glyph_mask.reset();
4442 self.glyph_color.reset();
4443 self.clip_ring.reset();
4444 self.blur_ring.reset();
4445 self.nv12.reset();
4446
4447 self.slug_ring.reset();
4448 self.mesh_verts.reset();
4449 self.mesh_indices.reset();
4450 self.mesh_uniform_head = 0;
4451 self.mesh_clip_stack.clear();
4452 let mut batch = Batch::new();
4453 let mut slug_verts_local: Vec<slug::TessVertex> = Vec::new();
4454 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
4455 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
4456 let mut clip_cmd_stack: Vec<(u64, u32, bool)> = Vec::with_capacity(8);
4460 let mut root_clip_rect = repose_core::Rect {
4461 x: 0.0,
4462 y: 0.0,
4463 w: fb_w,
4464 h: fb_h,
4465 };
4466 let mut saved_scissor_stack: Vec<repose_core::Rect> = Vec::new();
4467 let mut saved_root_clip_rect = root_clip_rect;
4468
4469 let mut current_prim: Option<&'static str> = None;
4470
4471 macro_rules! flush_if_prim_changed {
4472 ($prim:literal, $pipe:expr) => {
4473 if current_prim != Some($prim) {
4474 flush_batch!();
4475 current_prim = Some($prim);
4476 }
4477 };
4478 }
4479
4480 macro_rules! flush_batch {
4481 () => {
4482 if !batch.is_empty() {
4483 batch.flush(
4484 (
4485 &mut self.rects,
4486 &mut self.borders,
4487 &mut self.ellipses,
4488 &mut self.ellipse_borders,
4489 &mut self.arcs,
4490 ),
4491 (&mut self.glyph_mask, &mut self.glyph_color),
4492 &mut self.nv12,
4493 &self.device,
4494 &self.queue,
4495 &mut current_pass.cmds,
4496 )
4497 }
4498 };
4499 }
4500 for node in &scene.nodes {
4501 let t_identity = Transform::identity();
4502 let current_transform = transform_stack.last().unwrap_or(&t_identity);
4503
4504 match node {
4505 SceneNode::Rect {
4506 rect,
4507 brush,
4508 radius,
4509 } => {
4510 flush_if_prim_changed!("rect", &self.rects);
4511 let (ndc, sin_cos) = rect_to_instance_ndc(
4512 *rect,
4513 current_transform,
4514 current_target_size.0,
4515 current_target_size.1,
4516 );
4517 let (brush_type, color0, color1, grad_start, grad_end) =
4518 brush_to_instance_fields(brush);
4519 batch.rects.push(RectInstance {
4520 xywh: ndc,
4521 radii: radius.map(|r| r.0),
4522 brush_type,
4523 _pad: [0.0; 3],
4524 color0,
4525 color1,
4526 grad_start,
4527 grad_end,
4528 sin_cos,
4529 });
4530 }
4531 SceneNode::Border {
4532 rect,
4533 color,
4534 width,
4535 radius,
4536 } => {
4537 flush_if_prim_changed!("border", &self.borders);
4538 let (ndc, sin_cos) = rect_to_instance_ndc(
4539 *rect,
4540 current_transform,
4541 current_target_size.0,
4542 current_target_size.1,
4543 );
4544 batch.borders.push(BorderInstance {
4545 xywh: ndc,
4546 radii: radius.map(|r| r.0),
4547 stroke: width.0,
4548 color: color.to_linear(),
4549 sin_cos,
4550 });
4551 }
4552 SceneNode::Ellipse { rect, brush } => {
4553 flush_if_prim_changed!("ellipse", &self.ellipses);
4554 let (ndc, sin_cos) = rect_to_instance_ndc(
4555 *rect,
4556 current_transform,
4557 current_target_size.0,
4558 current_target_size.1,
4559 );
4560 let color = brush_to_solid_color(brush);
4561 batch.ellipses.push(EllipseInstance {
4562 xywh: ndc,
4563 color,
4564 sin_cos,
4565 });
4566 }
4567 SceneNode::EllipseBorder { rect, color, width } => {
4568 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
4569 let (ndc, sin_cos) = rect_to_instance_ndc(
4570 *rect,
4571 current_transform,
4572 current_target_size.0,
4573 current_target_size.1,
4574 );
4575 let pad_px = width.0 * 0.5 + 2.0;
4576 let pad = (pad_px / current_target_size.0) * 2.0;
4577 batch.e_borders.push(EllipseBorderInstance {
4578 xywh: ndc,
4579 stroke: width.0,
4580 pad,
4581 color: color.to_linear(),
4582 sin_cos,
4583 });
4584 }
4585 SceneNode::Arc {
4586 rect,
4587 start_angle,
4588 sweep_angle,
4589 stroke_width,
4590 color,
4591 cap,
4592 } => {
4593 flush_if_prim_changed!("arc", &self.arcs);
4594 let (ndc, sin_cos) = rect_to_instance_ndc(
4595 *rect,
4596 current_transform,
4597 current_target_size.0,
4598 current_target_size.1,
4599 );
4600 let pad_px = stroke_width.0 * 0.5 + 2.0;
4601 let pad = (pad_px / current_target_size.0) * 2.0;
4602 let cap_val = match cap {
4603 StrokeCap::Butt => 0.0,
4604 StrokeCap::Round => 1.0,
4605 StrokeCap::Square => 2.0,
4606 };
4607 batch.arcs.push(ArcInstance {
4608 xywh: ndc,
4609 start_angle: *start_angle,
4610 sweep_angle: *sweep_angle,
4611 stroke: stroke_width.0,
4612 pad,
4613 color: color.to_linear(),
4614 sin_cos,
4615 cap: cap_val,
4616 });
4617 }
4618 SceneNode::Text {
4619 rect,
4620 text,
4621 color,
4622 size,
4623 font_family,
4624 text_align: _,
4625 font_weight,
4626 font_style,
4627 text_decoration,
4628 letter_spacing,
4629 line_height: _,
4630 extra_style,
4631 url: _,
4632 font_variation_settings,
4633 } => {
4634 flush_batch!(); let px = size.0;
4637 let lh_ratio = rect.h / px;
4638 let fw = font_weight.0;
4639 let fs = if *font_style == FontStyle::Italic {
4640 1
4641 } else {
4642 0
4643 };
4644 let shaped = repose_text::shape_line(
4645 text.as_ref(),
4646 px,
4647 lh_ratio,
4648 *font_family,
4649 fw,
4650 fs,
4651 letter_spacing.0,
4652 font_variation_settings.as_deref(),
4653 );
4654 let baseline_y = shaped.first().map(|g| rect.y + g.y);
4655
4656 let cos_a = current_transform.rotate.cos();
4657 let sin_a = current_transform.rotate.sin();
4658 let has_rotation = current_transform.rotate != 0.0;
4659
4660 let pivot_x = rect.x + rect.w * 0.5;
4662 let pivot_y = rect.y + rect.h * 0.5;
4663
4664 let make_glyph_instance =
4666 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 2]) {
4667 if has_rotation {
4668 let corners =
4669 [(gx, gy), (gx + gw, gy), (gx + gw, gy + gh), (gx, gy + gh)];
4670 let mut min_x = f32::MAX;
4671 let mut max_x = f32::MIN;
4672 let mut min_y = f32::MAX;
4673 let mut max_y = f32::MIN;
4674 for &(x, y) in &corners {
4675 let dx = x - pivot_x;
4676 let dy = y - pivot_y;
4677 let rx = pivot_x + dx * cos_a - dy * sin_a;
4678 let ry = pivot_y + dx * sin_a + dy * cos_a;
4679 min_x = min_x.min(rx);
4680 max_x = max_x.max(rx);
4681 min_y = min_y.min(ry);
4682 max_y = max_y.max(ry);
4683 }
4684 let bb_w = max_x - min_x;
4685 let bb_h = max_y - min_y;
4686 let ndc_tl = to_ndc(
4687 min_x,
4688 min_y,
4689 bb_w,
4690 bb_h,
4691 current_target_size.0,
4692 current_target_size.1,
4693 );
4694 let ndc = [
4695 ndc_tl[0] + ndc_tl[2] * 0.5,
4696 ndc_tl[1] + ndc_tl[3] * 0.5,
4697 ndc_tl[2],
4698 ndc_tl[3],
4699 ];
4700 (ndc, [cos_a, sin_a])
4701 } else {
4702 let (sx, sy) = if current_transform.scale_x == 1.0
4704 && current_transform.scale_y == 1.0
4705 {
4706 (gx.round(), gy.round())
4707 } else {
4708 (gx, gy)
4709 };
4710 rect_to_instance_ndc(
4711 repose_core::Rect {
4712 x: sx,
4713 y: sy,
4714 w: gw,
4715 h: gh,
4716 },
4717 current_transform,
4718 current_target_size.0,
4719 current_target_size.1,
4720 )
4721 }
4722 };
4723
4724 let baseline_shift_y: f32 = px * extra_style.baseline_shift.0;
4725
4726 let (
4727 is_stroke,
4728 stroke_width,
4729 stroke_cap,
4730 stroke_join,
4731 stroke_miter,
4732 stroke_path_effect,
4733 ) = match &extra_style.draw_style {
4734 repose_core::DrawStyle::Stroke {
4735 width,
4736 cap,
4737 join,
4738 miter,
4739 path_effect,
4740 } => (true, *width, *cap, *join, *miter, path_effect.clone()),
4741 _ => (
4742 false,
4743 0.0,
4744 repose_core::StrokeCap::Butt,
4745 repose_core::StrokeJoin::Miter,
4746 4.0,
4747 None,
4748 ),
4749 };
4750 let stroke_tess_key = if is_stroke {
4751 Some(slug::StrokeTessKey::new(
4752 stroke_width,
4753 stroke_cap,
4754 stroke_join,
4755 stroke_miter,
4756 &stroke_path_effect,
4757 ))
4758 } else {
4759 None
4760 };
4761
4762 for sg in shaped {
4763 let gx = rect.x + sg.x + sg.bearing_x;
4764 let gy = rect.y + sg.y - sg.bearing_y + baseline_shift_y;
4765
4766 if self.slug_enabled {
4768 let ck = repose_text::lookup_cache_key(sg.key, sg.px);
4769 if let Some(ref ck) = ck {
4770 let need_tessellate = self.slug_cache.get(ck).is_none_or(|g| {
4772 if is_stroke {
4773 let key = stroke_tess_key.as_ref().unwrap();
4774 !g.stroke_variants.contains_key(key)
4775 } else {
4776 g.fill_vertices.is_none()
4777 }
4778 });
4779 if need_tessellate {
4780 if let Some((ck2, commands)) =
4781 repose_text::lookup_and_extract_outline(sg.key, sg.px)
4782 {
4783 let font_size_px = f32::from_bits(ck2.font_size_bits);
4784 if is_stroke {
4785 self.slug_cache.get_or_insert_stroke(
4786 ck2,
4787 font_size_px,
4788 &commands,
4789 stroke_width,
4790 stroke_cap,
4791 stroke_join,
4792 stroke_miter,
4793 &stroke_path_effect,
4794 );
4795 } else {
4796 self.slug_cache.get_or_insert(
4797 ck2,
4798 font_size_px,
4799 &commands,
4800 );
4801 }
4802 }
4803 } else {
4804 self.slug_cache.touch(ck);
4805 }
4806 }
4807 if let Some(entry) = ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
4808 {
4809 let ox = rect.x + sg.x;
4810 let oy = rect.y + sg.y + baseline_shift_y;
4811 let scx = current_transform.scale_x;
4812 let scy = current_transform.scale_y;
4813 let ttx = current_transform.translate_x;
4814 let tty = current_transform.translate_y;
4815
4816 let tf = |x: f32, y: f32| -> (f32, f32) {
4817 if has_rotation {
4818 let dx = x - pivot_x;
4819 let dy = y - pivot_y;
4820 let rx = pivot_x + dx * cos_a - dy * sin_a;
4821 let ry = pivot_y + dx * sin_a + dy * cos_a;
4822 (rx, ry)
4823 } else {
4824 (x * scx + ttx, y * scy + tty)
4825 }
4826 };
4827
4828 let tw = current_target_size.0;
4829 let th = current_target_size.1;
4830
4831 let verts = if is_stroke {
4832 let key = stroke_tess_key.as_ref().unwrap();
4833 entry
4834 .stroke_variants
4835 .get(key)
4836 .map(|v| v.as_slice())
4837 .unwrap_or(&[])
4838 } else {
4839 entry.fill_vertices.as_deref().unwrap_or(&[])
4840 };
4841
4842 for &v in verts {
4843 let (sx, sy) = tf(ox + v[0] * px, oy - v[1] * px);
4844 let ndc_x = sx / tw * 2.0 - 1.0;
4845 let ndc_y = -(sy / th) * 2.0 + 1.0;
4846 slug_verts_local.push(slug::TessVertex {
4847 ndc_pos: [ndc_x, ndc_y],
4848 color: color.to_linear(),
4849 });
4850 }
4851
4852 if is_stroke {
4853 continue;
4855 }
4856 continue;
4857 }
4858 }
4859
4860 if is_stroke {
4862 continue;
4863 }
4864
4865 if let Some(info) = self.upload_glyph_color(sg.key, sg.px) {
4867 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
4868 batch.colors.push(GlyphInstance {
4869 xywh: ndc,
4870 uv: [info.u0, info.v1, info.u1, info.v0],
4871 color: color.to_linear(),
4872 sin_cos,
4873 });
4874 } else if let Some(info) = self.upload_glyph_mask(sg.key, sg.px) {
4875 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
4876 batch.masks.push(GlyphInstance {
4877 xywh: ndc,
4878 uv: [info.u0, info.v1, info.u1, info.v0],
4879 color: color.to_linear(),
4880 sin_cos,
4881 });
4882 }
4883 }
4884
4885 if !slug_verts_local.is_empty() {
4887 let bytes = bytemuck::cast_slice(&slug_verts_local);
4888 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
4889 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
4890 current_pass.cmds.push(Cmd::GlyphsVector {
4891 off,
4892 cnt: slug_verts_local.len() as u32,
4893 });
4894 slug_verts_local.clear();
4895 }
4896
4897 if (text_decoration.underline || text_decoration.strikethrough)
4899 && let Some(baseline_y) = baseline_y
4900 {
4901 flush_batch!();
4902 current_prim = Some("rect");
4903 let deco_color = text_decoration.color.unwrap_or(*color);
4904 let thickness = (px * 0.07).max(1.0);
4905
4906 if text_decoration.underline {
4907 let dy = baseline_y + px * 0.1;
4908 let (ndc, sin_cos) = rect_to_instance_ndc(
4909 repose_core::Rect {
4910 x: rect.x,
4911 y: dy,
4912 w: rect.w,
4913 h: thickness,
4914 },
4915 current_transform,
4916 current_target_size.0,
4917 current_target_size.1,
4918 );
4919 batch.rects.push(RectInstance {
4920 xywh: ndc,
4921 radii: [0.0; 4],
4922 brush_type: 0,
4923 _pad: [0.0; 3],
4924 color0: deco_color.to_linear(),
4925 color1: [0.0; 4],
4926 grad_start: [0.0; 2],
4927 grad_end: [0.0; 2],
4928 sin_cos,
4929 });
4930 }
4931 if text_decoration.strikethrough {
4932 let sy = baseline_y - px * 0.3;
4933 let (ndc, sin_cos) = rect_to_instance_ndc(
4934 repose_core::Rect {
4935 x: rect.x,
4936 y: sy,
4937 w: rect.w,
4938 h: thickness,
4939 },
4940 current_transform,
4941 current_target_size.0,
4942 current_target_size.1,
4943 );
4944 batch.rects.push(RectInstance {
4945 xywh: ndc,
4946 radii: [0.0; 4],
4947 brush_type: 0,
4948 _pad: [0.0; 3],
4949 color0: deco_color.to_linear(),
4950 color1: [0.0; 4],
4951 grad_start: [0.0; 2],
4952 grad_end: [0.0; 2],
4953 sin_cos,
4954 });
4955 }
4956 }
4957 }
4958 SceneNode::Image {
4959 rect,
4960 handle,
4961 tint,
4962 fit,
4963 } => {
4964 flush_batch!();
4965
4966 let (img_w, img_h, is_nv12) = match self.resolve_image_for_draw(*handle) {
4969 Some(wh) => wh,
4970 None => {
4971 log::warn!("Image handle {} not found", handle);
4972 continue;
4973 }
4974 };
4975
4976 let src_w = img_w as f32;
4977 let src_h = img_h as f32;
4978
4979 let dst_w = rect.w.max(0.0);
4980 let dst_h = rect.h.max(0.0);
4981 if dst_w <= 0.0 || dst_h <= 0.0 {
4982 continue;
4983 }
4984
4985 let (draw_rect, uv_rect) = match fit {
4986 repose_core::view::ImageFit::Contain => {
4987 let scale = (dst_w / src_w).min(dst_h / src_h);
4988 let w = src_w * scale;
4989 let h = src_h * scale;
4990 (
4991 repose_core::Rect {
4992 x: rect.x + (dst_w - w) * 0.5,
4993 y: rect.y + (dst_h - h) * 0.5,
4994 w,
4995 h,
4996 },
4997 [0.0, 1.0, 1.0, 0.0],
4998 )
4999 }
5000 repose_core::view::ImageFit::Cover => {
5001 let scale = (dst_w / src_w).max(dst_h / src_h);
5002 let content_w = src_w * scale;
5003 let content_h = src_h * scale;
5004 let overflow_x = (content_w - dst_w) * 0.5;
5005 let overflow_y = (content_h - dst_h) * 0.5;
5006 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
5007 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
5008 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
5009 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
5010 (*rect, [u0, 1.0 - v0, u1, 1.0 - v1])
5011 }
5012 repose_core::view::ImageFit::FitWidth => {
5013 let scale = dst_w / src_w;
5014 (
5015 repose_core::Rect {
5016 x: rect.x,
5017 y: rect.y + (dst_h - src_h * scale) * 0.5,
5018 w: dst_w,
5019 h: src_h * scale,
5020 },
5021 [0.0, 1.0, 1.0, 0.0],
5022 )
5023 }
5024 repose_core::view::ImageFit::FitHeight => {
5025 let scale = dst_h / src_h;
5026 (
5027 repose_core::Rect {
5028 x: rect.x + (dst_w - src_w * scale) * 0.5,
5029 y: rect.y,
5030 w: src_w * scale,
5031 h: dst_h,
5032 },
5033 [0.0, 1.0, 1.0, 0.0],
5034 )
5035 }
5036 repose_core::view::ImageFit::FillBounds => (*rect, [0.0, 1.0, 1.0, 0.0]),
5037 repose_core::view::ImageFit::Inside => {
5038 let scale = (dst_w / src_w).min(dst_h / src_h).min(1.0);
5039 let w = src_w * scale;
5040 let h = src_h * scale;
5041 (
5042 repose_core::Rect {
5043 x: rect.x + (dst_w - w) * 0.5,
5044 y: rect.y + (dst_h - h) * 0.5,
5045 w,
5046 h,
5047 },
5048 [0.0, 1.0, 1.0, 0.0],
5049 )
5050 }
5051 repose_core::view::ImageFit::None => {
5052 (
5053 repose_core::Rect {
5054 x: rect.x,
5055 y: rect.y,
5056 w: src_w.min(dst_w),
5057 h: src_h.min(dst_h),
5058 },
5059 [
5061 0.0,
5062 1.0,
5063 (dst_w / src_w).min(1.0),
5064 1.0 - (dst_h / src_h).min(1.0),
5065 ],
5066 )
5067 }
5068 _ => continue,
5069 };
5070
5071 let (ndc_center, sin_cos) = rect_to_instance_ndc(
5072 draw_rect,
5073 current_transform,
5074 current_target_size.0,
5075 current_target_size.1,
5076 );
5077
5078 if is_nv12 {
5079 let uv_x_offset = if let Some(ImageTex::Nv12 { w, color_info, .. }) =
5080 self.images.get(handle)
5081 {
5082 match color_info.chroma_siting {
5083 ChromaSiting::Center | ChromaSiting::TopLeft => 0.0,
5084 ChromaSiting::Left => -1.0 / *w as f32,
5085 }
5086 } else {
5087 0.0
5088 };
5089
5090 let inst = Nv12Instance {
5091 xywh: ndc_center,
5092 uv: uv_rect,
5093 color: tint.to_linear(),
5094 uv_x_offset,
5095 sin_cos,
5096 _pad: [0.0],
5097 };
5098 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
5099 {
5100 current_pass.cmds.push(Cmd::ImageNv12 {
5101 off,
5102 cnt: 1,
5103 handle: *handle,
5104 });
5105 }
5106 } else {
5107 let inst = GlyphInstance {
5109 xywh: ndc_center,
5110 uv: uv_rect,
5111 color: tint.to_linear(),
5112 sin_cos,
5113 };
5114 if let Some((off, _)) =
5115 self.glyph_color.upload(&self.device, &self.queue, &[inst])
5116 {
5117 current_pass.cmds.push(Cmd::ImageRgba {
5118 off,
5119 cnt: 1,
5120 handle: *handle,
5121 });
5122 }
5123 }
5124 }
5125 SceneNode::PushClip { rect, radius, op } => {
5126 flush_batch!(); let is_diff = matches!(op, repose_core::ClipOp::Difference);
5129
5130 let t_identity = Transform::identity();
5131 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5132 let transformed = current_transform.apply_to_rect(*rect);
5133
5134 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5135 let next_scissor = if is_diff {
5136 top
5137 } else {
5138 intersect(top, transformed)
5139 };
5140 scissor_stack.push(next_scissor);
5141 let scissor = to_scissor(
5142 &next_scissor,
5143 current_target_size.0 as u32,
5144 current_target_size.1 as u32,
5145 );
5146
5147 let clip_ndc_tl = to_ndc(
5148 transformed.x,
5149 transformed.y,
5150 transformed.w,
5151 transformed.h,
5152 current_target_size.0,
5153 current_target_size.1,
5154 );
5155 let inst = ClipInstance {
5156 xywh: [
5157 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
5158 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
5159 clip_ndc_tl[2],
5160 clip_ndc_tl[3],
5161 ],
5162 radii: radius.map(|r| r.0),
5163 sin_cos: [1.0, 0.0],
5164 };
5165 let bytes = bytemuck::bytes_of(&inst);
5166 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
5167 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
5168
5169 let rounded = radius.iter().any(|&r| r.0 > 0.5);
5170
5171 current_pass.cmds.push(Cmd::ClipPush {
5172 off,
5173 cnt: 1,
5174 scissor,
5175 difference: is_diff,
5176 rounded,
5177 });
5178 clip_cmd_stack.push((off, 1, is_diff));
5179 }
5180 SceneNode::PopClip => {
5181 flush_batch!();
5182
5183 if !scissor_stack.is_empty() {
5184 scissor_stack.pop();
5185 } else {
5186 log::warn!("PopClip with empty stack");
5187 }
5188
5189 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5190 let scissor = to_scissor(
5191 &top,
5192 current_target_size.0 as u32,
5193 current_target_size.1 as u32,
5194 );
5195 let (off, cnt, difference) = clip_cmd_stack.pop().unwrap_or((0, 0, false));
5196 current_pass.cmds.push(Cmd::ClipPop {
5197 off,
5198 cnt,
5199 scissor,
5200 difference,
5201 });
5202 }
5203 SceneNode::Shadow {
5204 rect,
5205 radius,
5206 elevation: _,
5207 color,
5208 } => {
5209 flush_if_prim_changed!("rect", &self.rects);
5210 let (ndc, sin_cos) = rect_to_instance_ndc(
5211 *rect,
5212 current_transform,
5213 current_target_size.0,
5214 current_target_size.1,
5215 );
5216 let (brush_type, color0, _color1, _grad_start, _grad_end) =
5217 brush_to_instance_fields(&Brush::Solid(*color));
5218 batch.rects.push(RectInstance {
5219 xywh: ndc,
5220 radii: radius.map(|r| r.0),
5221 brush_type,
5222 _pad: [0.0; 3],
5223 color0,
5224 color1: [0.0; 4],
5225 grad_start: [0.0; 2],
5226 grad_end: [0.0; 2],
5227 sin_cos,
5228 });
5229 }
5230 SceneNode::PushTransform { transform } => {
5231 flush_batch!(); let combined = current_transform.combine(transform);
5233 transform_stack.push(combined);
5234 }
5235 SceneNode::PopTransform => {
5236 flush_batch!(); transform_stack.pop();
5238 }
5239 SceneNode::BeginLayer {
5240 rect,
5241 layer_id,
5242 alpha,
5243 blur_radius_x,
5244 blur_radius_y,
5245 rectangle_edge: _,
5246 } => {
5247 flush_batch!();
5248 let w = (rect.w.round().max(1.0)) as u32;
5251 let h = (rect.h.round().max(1.0)) as u32;
5252 saved_scissor_stack =
5253 std::mem::replace(&mut scissor_stack, Vec::with_capacity(8));
5254 saved_root_clip_rect = std::mem::replace(
5255 &mut root_clip_rect,
5256 repose_core::Rect {
5257 x: 0.0,
5258 y: 0.0,
5259 w: w as f32,
5260 h: h as f32,
5261 },
5262 );
5263 scissor_stack.push(root_clip_rect);
5264 let prev_target = current_pass.target;
5266 let prev_scissor = current_pass.initial_scissor;
5267 let saved = std::mem::replace(
5268 &mut current_pass,
5269 Pass {
5270 target: PassTarget::Layer(*layer_id),
5271 initial_scissor: (0, 0, w, h),
5272 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
5273 cmds: Vec::new(),
5274 },
5275 );
5276 passes.push(saved);
5277 target_stack.push(prev_target);
5278 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
5282 current_target_size = (w as f32, h as f32);
5283 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
5284 if blur_radius_x.0 > 0.0 || blur_radius_y.0 > 0.0 {
5286 layer_blurs.push((*layer_id, blur_radius_x.0, blur_radius_y.0));
5287 }
5288 }
5289 SceneNode::EndLayer { layer_id } => {
5290 flush_batch!();
5291 scissor_stack = std::mem::take(&mut saved_scissor_stack);
5292 root_clip_rect = saved_root_clip_rect;
5293 let saved = std::mem::replace(
5295 &mut current_pass,
5296 Pass {
5297 target: target_stack.pop().unwrap_or(PassTarget::Surface),
5298 initial_scissor: (0, 0, self.output_width, self.output_height),
5299 clear_color: None, cmds: Vec::new(),
5301 },
5302 );
5303 passes.push(saved);
5304 current_target_size = (fb_w, fb_h);
5305 if let Some((_, layer_alpha, _)) = layer_alphas
5307 .iter()
5308 .find(|(id, _, _)| id == layer_id)
5309 .copied()
5310 {
5311 let layer = self.layer_pool.get(layer_id).expect("layer target");
5312 let ndc_tl = to_ndc(
5313 layer.rect_px.0,
5314 layer.rect_px.1,
5315 layer.rect_px.2,
5316 layer.rect_px.3,
5317 fb_w,
5318 fb_h,
5319 );
5320 let uv_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
5321 let uv_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
5322 let blur_px_val = layer_blurs
5324 .iter()
5325 .find(|(id, _, _)| id == layer_id)
5326 .map(|(_, bx, by)| (*bx, *by));
5327 if let Some((blur_x, blur_y)) =
5328 blur_px_val.filter(|(bx, by)| *bx > 0.0 || *by > 0.0)
5329 {
5330 let bw_uv = (blur_x * 1.5) / layer.width.max(1) as f32;
5332 let bh_uv = (blur_y * 1.5) / layer.height.max(1) as f32;
5333 let inst = BlurInstance {
5334 xywh: [
5335 ndc_tl[0] + ndc_tl[2] * 0.5,
5336 ndc_tl[1] + ndc_tl[3] * 0.5,
5337 ndc_tl[2],
5338 ndc_tl[3],
5339 ],
5340 uv: [0.0, 0.0, uv_u1, uv_v1],
5341 color: [1.0, 1.0, 1.0, layer_alpha],
5342 blur_uv: [bw_uv, bh_uv],
5343 sin_cos: [1.0, 0.0],
5344 };
5345 self.blur_ring.grow_to_fit(
5346 &self.device,
5347 std::mem::size_of::<BlurInstance>() as u64,
5348 );
5349 let bytes = bytemuck::bytes_of(&inst);
5350 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
5351 current_pass.cmds.push(Cmd::CompositeBlur {
5352 off,
5353 cnt: 1,
5354 layer_id: *layer_id,
5355 });
5356 } else {
5357 let inst = GlyphInstance {
5359 xywh: [
5360 ndc_tl[0] + ndc_tl[2] * 0.5,
5361 ndc_tl[1] + ndc_tl[3] * 0.5,
5362 ndc_tl[2],
5363 ndc_tl[3],
5364 ],
5365 uv: [0.0, uv_v1, uv_u1, 0.0],
5366 color: [1.0, 1.0, 1.0, layer_alpha],
5367 sin_cos: [1.0, 0.0],
5368 };
5369 if let Some((off, cnt)) =
5370 self.glyph_color.upload(&self.device, &self.queue, &[inst])
5371 {
5372 current_pass.cmds.push(Cmd::CompositeLayer {
5373 off,
5374 cnt,
5375 layer_id: *layer_id,
5376 });
5377 }
5378 }
5379 }
5380 }
5381 SceneNode::CompositeShadow {
5382 layer_id,
5383 blur_px,
5384 offset_px,
5385 color,
5386 } => {
5387 flush_batch!();
5388 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
5389 let sx = layer.rect_px.0 + offset_px.0.0;
5391 let sy = layer.rect_px.1 + offset_px.1.0;
5392 let sw = layer.rect_px.2;
5393 let sh = layer.rect_px.3;
5394 let bw_uv = (blur_px.0 * 1.5) / layer.width.max(1) as f32;
5397 let bh_uv = (blur_px.0 * 1.5) / layer.height.max(1) as f32;
5398 let shadow_u1 = layer.rect_px.2 / layer.width.max(1) as f32;
5399 let shadow_v1 = layer.rect_px.3 / layer.height.max(1) as f32;
5400 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
5401 let inst = BlurInstance {
5402 xywh: [
5403 ndc_tl[0] + ndc_tl[2] * 0.5,
5404 ndc_tl[1] + ndc_tl[3] * 0.5,
5405 ndc_tl[2],
5406 ndc_tl[3],
5407 ],
5408 uv: [0.0, 0.0, shadow_u1, shadow_v1],
5409 color: [
5410 color.0 as f32 / 255.0,
5411 color.1 as f32 / 255.0,
5412 color.2 as f32 / 255.0,
5413 color.3 as f32 / 255.0,
5414 ],
5415 blur_uv: [bw_uv, bh_uv],
5416 sin_cos: [1.0, 0.0],
5417 };
5418 self.blur_ring
5419 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
5420 let bytes = bytemuck::bytes_of(&inst);
5421 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
5422 current_pass.cmds.push(Cmd::CompositeShadow {
5423 off,
5424 cnt: 1,
5425 layer_id: *layer_id,
5426 });
5427 }
5428 }
5429 SceneNode::VectorMesh {
5430 mesh,
5431 transform,
5432 paint,
5433 clip: _,
5434 blend: _,
5435 } => {
5436 flush_batch!();
5437 let t_identity = Transform::identity();
5438 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5439 self.emit_vector_mesh(
5440 current_transform,
5441 mesh,
5442 *transform,
5443 paint,
5444 &mut current_pass.cmds,
5445 );
5446 }
5447 SceneNode::VectorOverlay { meshes } => {
5448 flush_batch!();
5449 for m in meshes.iter() {
5450 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(m);
5451 let uoff = self.alloc_mesh_uniform(MeshUniform::identity());
5452 current_pass.cmds.push(Cmd::VectorOverlay {
5453 voff,
5454 vcnt,
5455 ioff,
5456 icnt,
5457 uoff,
5458 });
5459 }
5460 }
5461 SceneNode::PushVectorClip { mesh } => {
5462 flush_batch!();
5463 let t_identity = Transform::identity();
5464 let current_transform = transform_stack.last().unwrap_or(&t_identity);
5465 let affine =
5466 combine_mesh_affine(current_transform, [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
5467 let aabb = mesh_aabb(mesh, affine);
5468 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5469 let next = intersect(top, aabb);
5470 scissor_stack.push(next);
5471 let scissor = to_scissor(
5472 &next,
5473 current_target_size.0 as u32,
5474 current_target_size.1 as u32,
5475 );
5476 let (voff, vcnt, ioff, icnt) = self.upload_mesh_geometry(mesh);
5477 let uoff = self.alloc_mesh_uniform(mesh_uniform_from_paint(
5478 affine,
5479 &repose_core::PaintDesc::Solid,
5480 ));
5481 current_pass.cmds.push(Cmd::VectorClipPush {
5482 voff,
5483 vcnt,
5484 ioff,
5485 icnt,
5486 uoff,
5487 scissor,
5488 });
5489 self.mesh_clip_stack.push((voff, vcnt, ioff, icnt, uoff));
5490 }
5491 SceneNode::PopVectorClip => {
5492 flush_batch!();
5493 if !scissor_stack.is_empty() {
5494 scissor_stack.pop();
5495 } else {
5496 log::warn!("PopVectorClip with empty scissor stack");
5497 }
5498 if let Some((voff, vcnt, ioff, icnt, uoff)) = self.mesh_clip_stack.pop() {
5499 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
5500 let scissor = to_scissor(
5501 &top,
5502 current_target_size.0 as u32,
5503 current_target_size.1 as u32,
5504 );
5505 current_pass.cmds.push(Cmd::VectorClipPop {
5506 voff,
5507 vcnt,
5508 ioff,
5509 icnt,
5510 uoff,
5511 scissor,
5512 });
5513 } else {
5514 log::warn!("PopVectorClip with empty clip stack");
5515 }
5516 }
5517 SceneNode::Callback { rect, payload } => {
5518 flush_batch!();
5519 let t = transform_stack
5520 .last()
5521 .copied()
5522 .unwrap_or(Transform::identity());
5523 let transformed = t.apply_to_rect(*rect);
5524 current_pass.cmds.push(Cmd::Callback {
5525 rect: transformed,
5526 payload: payload.clone(),
5527 });
5528 }
5529 _ => {}
5530 }
5531 }
5532
5533 flush_batch!();
5534
5535 {
5536 let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
5537 let mut prepare_list: Vec<Arc<Callback>> = Vec::new();
5538 for node in &scene.nodes {
5539 if let SceneNode::Callback { payload, .. } = node
5540 && payload.downcast_ref::<Callback>().is_some()
5541 {
5542 let ptr = Arc::as_ptr(payload) as *const () as usize;
5543 if seen.insert(ptr)
5544 && let Ok(cb_arc) = payload.clone().downcast::<Callback>()
5545 {
5546 prepare_list.push(cb_arc);
5547 }
5548 }
5549 }
5550 if !prepare_list.is_empty() {
5551 let screen_desc = ScreenDescriptor {
5552 size_in_pixels: [self.output_width, self.output_height],
5553 pixels_per_point: self.pixels_per_point,
5554 target_format: self.output_format,
5555 sample_count: self.msaa_samples.max(1),
5556 };
5557 let mut user_cmd_bufs: Vec<wgpu::CommandBuffer> = Vec::new();
5558 for cb in &prepare_list {
5559 user_cmd_bufs.extend(cb.0.prepare(
5560 &self.device,
5561 &self.queue,
5562 encoder,
5563 &screen_desc,
5564 &mut self.callback_resources,
5565 ));
5566 }
5567 for cb in &prepare_list {
5568 user_cmd_bufs.extend(cb.0.finish_prepare(
5569 &self.device,
5570 &self.queue,
5571 encoder,
5572 &screen_desc,
5573 &mut self.callback_resources,
5574 ));
5575 }
5576 if !user_cmd_bufs.is_empty() {
5579 self.queue.submit(user_cmd_bufs);
5580 }
5581 }
5582 }
5583
5584 passes.push(current_pass);
5586
5587 let globals_bytes = std::mem::size_of::<Globals>() as u64;
5588 let globals_staging = self.device.create_buffer(&wgpu::BufferDescriptor {
5589 label: Some("globals staging"),
5590 size: (passes.len().max(1) as u64) * globals_bytes,
5591 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC,
5592 mapped_at_creation: false,
5593 });
5594 for (i, pass) in passes.iter().enumerate() {
5595 let (target_w, target_h) = match pass.target {
5596 PassTarget::Surface => (fb_w, fb_h),
5597 PassTarget::Layer(layer_id) => {
5598 let lt = self.layer_pool.get(&layer_id);
5599 (
5600 lt.map_or(fb_w, |l| l.width as f32),
5601 lt.map_or(fb_h, |l| l.height as f32),
5602 )
5603 }
5604 };
5605 self.queue.write_buffer(
5606 &globals_staging,
5607 (i as u64) * globals_bytes,
5608 bytemuck::bytes_of(&make_globals(target_w, target_h)),
5609 );
5610 }
5611
5612 let bind_mask = self.atlas_bind_group_mask();
5613 let bind_color = self.atlas_bind_group_color();
5614 let mut clip_depth: u32 = 0;
5615
5616 for (pass_index, pass) in std::mem::take(&mut passes).into_iter().enumerate() {
5617 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
5618 PassTarget::Surface => {
5619 let swap_view = target_view.clone();
5620 let use_ws = self.working_space && self.ws_view.is_some();
5621 let (color, resolve) = if use_ws {
5622 let ws_view = self.ws_view.as_ref().unwrap();
5623 if let Some(msaa_view) = &self.msaa_view {
5624 (msaa_view.clone(), Some(ws_view.clone()))
5626 } else {
5627 (ws_view.clone(), None)
5629 }
5630 } else if let Some(msaa_view) = &self.msaa_view {
5631 (msaa_view.clone(), Some(swap_view))
5632 } else {
5633 (swap_view, None)
5634 };
5635 (color, resolve, self.depth_stencil_view.clone(), false)
5636 }
5637 PassTarget::Layer(layer_id) => {
5638 if let Some(lt) = self.layer_pool.get(&layer_id) {
5639 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
5640 } else {
5641 log::warn!("missing layer target {layer_id}");
5642 continue;
5643 }
5644 }
5645 };
5646
5647 encoder.copy_buffer_to_buffer(
5648 &globals_staging,
5649 (pass_index as u64) * globals_bytes,
5650 &self.globals_buf,
5651 0,
5652 globals_bytes,
5653 );
5654
5655 if is_layer {
5656 clip_depth = 0;
5657 }
5658
5659 let (tw, th) = match pass.target {
5660 PassTarget::Surface => (self.output_width, self.output_height),
5661 PassTarget::Layer(layer_id) => self
5662 .layer_pool
5663 .get(&layer_id)
5664 .map(|l| (l.width, l.height))
5665 .unwrap_or((self.output_width, self.output_height)),
5666 };
5667 let initial_scissor = clamp_scissor(
5668 pass.initial_scissor.0,
5669 pass.initial_scissor.1,
5670 pass.initial_scissor.2,
5671 pass.initial_scissor.3,
5672 tw,
5673 th,
5674 );
5675
5676 let pipes: &Pipelines = if is_layer {
5677 &self.layer_pipes
5678 } else {
5679 &self.surface_pipes
5680 };
5681
5682 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
5683 label: Some("pass"),
5684 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
5685 view: &color_view,
5686 resolve_target: resolve_target.as_ref(),
5687 ops: wgpu::Operations {
5688 load: match pass.clear_color {
5689 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
5690 r: c[0] as f64,
5691 g: c[1] as f64,
5692 b: c[2] as f64,
5693 a: c[3] as f64,
5694 }),
5695 None => wgpu::LoadOp::Load,
5696 },
5697 store: wgpu::StoreOp::Store,
5698 },
5699 depth_slice: None,
5700 })],
5701 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
5702 view: &depth_stencil_view,
5703 depth_ops: None,
5704 stencil_ops: Some(wgpu::Operations {
5705 load: if is_layer || pass.clear_color.is_some() {
5706 wgpu::LoadOp::Clear(0)
5707 } else {
5708 wgpu::LoadOp::Load
5709 },
5710 store: wgpu::StoreOp::Store,
5711 }),
5712 }),
5713 timestamp_writes: None,
5714 occlusion_query_set: None,
5715 multiview_mask: None,
5716 });
5717
5718 rpass.set_bind_group(0, &self.globals_bind, &[]);
5719 rpass.set_stencil_reference(clip_depth);
5720 rpass.set_scissor_rect(
5721 initial_scissor.0,
5722 initial_scissor.1,
5723 initial_scissor.2,
5724 initial_scissor.3,
5725 );
5726
5727 macro_rules! draw_simple {
5728 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
5729 rpass.set_pipeline($pipeline);
5730 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
5731 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
5732 rpass.draw(0..6, 0..$n);
5733 }};
5734 }
5735
5736 macro_rules! draw_with_bind {
5737 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
5738 rpass.set_pipeline($pipeline);
5739 rpass.set_bind_group(1, $bind, &[]);
5740 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
5741 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
5742 rpass.draw(0..6, 0..$n);
5743 }};
5744 }
5745
5746 macro_rules! draw_indexed_mesh {
5747 ($pipeline:expr, $uoff:ident, $voff:ident, $vcnt:ident, $ioff:ident, $icnt:ident) => {{
5748 rpass.set_pipeline($pipeline);
5749 rpass.set_bind_group(1, &self.mesh_bind, &[$uoff as u32]);
5750 let vbytes = ($vcnt as u64) * std::mem::size_of::<MeshVertex>() as u64;
5751 rpass.set_vertex_buffer(0, self.mesh_verts.buf.slice($voff..$voff + vbytes));
5752 let ibytes = ($icnt as u64) * std::mem::size_of::<u32>() as u64;
5753 rpass.set_index_buffer(
5754 self.mesh_indices.buf.slice($ioff..$ioff + ibytes),
5755 wgpu::IndexFormat::Uint32,
5756 );
5757 rpass.draw_indexed(0..$icnt, 0, 0..1);
5758 }};
5759 }
5760
5761 for cmd in pass.cmds {
5762 match cmd {
5763 Cmd::ClipPush {
5764 off,
5765 cnt: n,
5766 scissor,
5767 difference,
5768 rounded,
5769 } => {
5770 let scissor =
5771 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
5772 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5773 rpass.set_stencil_reference(clip_depth);
5774
5775 if difference {
5776 rpass.set_pipeline(&pipes.clip_dec);
5777 } else if self.msaa_samples > 1 && !is_layer && rounded {
5778 rpass.set_pipeline(&pipes.clip_a2c);
5779 } else {
5780 rpass.set_pipeline(&pipes.clip_bin);
5781 }
5782
5783 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
5784 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
5785 rpass.draw(0..6, 0..n);
5786
5787 if !difference {
5788 clip_depth = (clip_depth + 1).min(255);
5789 rpass.set_stencil_reference(clip_depth);
5790 }
5791 }
5792
5793 Cmd::ClipPop {
5794 off,
5795 cnt: n,
5796 scissor,
5797 difference,
5798 } => {
5799 let scissor =
5800 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
5801 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5802
5803 if !difference && n > 0 {
5804 rpass.set_stencil_reference(clip_depth);
5805 rpass.set_pipeline(&pipes.clip_dec);
5806 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
5807 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
5808 rpass.draw(0..6, 0..n);
5809 clip_depth = clip_depth.saturating_sub(1);
5810 } else if !difference {
5811 clip_depth = clip_depth.saturating_sub(1);
5812 }
5813 rpass.set_stencil_reference(clip_depth);
5814 }
5815
5816 Cmd::Rect { off, cnt: n } => {
5817 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
5818 }
5819
5820 Cmd::Border { off, cnt: n } => {
5821 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
5822 }
5823
5824 Cmd::GlyphsMask { off, cnt: n } => {
5825 draw_with_bind!(
5826 &pipes.text_mask,
5827 self.glyph_mask.ring,
5828 GlyphInstance,
5829 &bind_mask,
5830 off,
5831 n
5832 );
5833 }
5834
5835 Cmd::GlyphsColor { off, cnt: n } => {
5836 draw_with_bind!(
5837 &pipes.text_color,
5838 self.glyph_color.ring,
5839 GlyphInstance,
5840 &bind_color,
5841 off,
5842 n
5843 );
5844 }
5845
5846 Cmd::GlyphsVector { off, cnt: n } => {
5847 if let Some(slug_pipe) = pipes.slug.as_ref() {
5848 rpass.set_pipeline(slug_pipe);
5849 let bytes = (n as u64) * std::mem::size_of::<slug::TessVertex>() as u64;
5850 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
5851 rpass.draw(0..n, 0..1);
5852 }
5853 }
5854
5855 Cmd::ImageRgba {
5856 off,
5857 cnt: n,
5858 handle,
5859 } => {
5860 let bind_opt = match self.images.get(&handle) {
5861 Some(ImageTex::Rgba { bind, .. }) => Some(bind),
5862 Some(ImageTex::User { bind, .. }) => Some(bind),
5863 _ => None,
5864 };
5865 if let Some(bind) = bind_opt {
5866 draw_with_bind!(
5867 &pipes.image_rgba,
5868 self.glyph_color.ring,
5869 GlyphInstance,
5870 bind,
5871 off,
5872 n
5873 );
5874 }
5875 }
5876
5877 Cmd::ImageNv12 {
5878 off,
5879 cnt: n,
5880 handle,
5881 } => {
5882 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
5883 draw_with_bind!(
5884 &pipes.image_nv12,
5885 self.nv12.ring,
5886 Nv12Instance,
5887 bind,
5888 off,
5889 n
5890 );
5891 }
5892 }
5893
5894 Cmd::Ellipse { off, cnt: n } => {
5895 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
5896 }
5897
5898 Cmd::EllipseBorder { off, cnt: n } => {
5899 draw_simple!(
5900 &pipes.ellipse_borders,
5901 self.ellipse_borders.ring,
5902 EllipseBorderInstance,
5903 off,
5904 n
5905 );
5906 }
5907
5908 Cmd::Arc { off, cnt: n } => {
5909 draw_simple!(&pipes.arcs, self.arcs.ring, ArcInstance, off, n);
5910 }
5911
5912 Cmd::CompositeLayer {
5913 off,
5914 cnt: n,
5915 layer_id,
5916 } => {
5917 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5918 draw_with_bind!(
5919 &pipes.image_rgba,
5920 self.glyph_color.ring,
5921 GlyphInstance,
5922 <.bind,
5923 off,
5924 n
5925 );
5926 }
5927 }
5928 Cmd::CompositeShadow {
5929 off,
5930 cnt: n,
5931 layer_id,
5932 } => {
5933 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5934 draw_with_bind!(
5935 &pipes.blur,
5936 self.blur_ring,
5937 BlurInstance,
5938 <.bind_linear,
5939 off,
5940 n
5941 );
5942 }
5943 }
5944 Cmd::CompositeBlur {
5945 off,
5946 cnt: n,
5947 layer_id,
5948 } => {
5949 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
5950 draw_with_bind!(
5951 &pipes.blur_content,
5952 self.blur_ring,
5953 BlurInstance,
5954 <.bind_linear,
5955 off,
5956 n
5957 );
5958 }
5959 }
5960
5961 Cmd::VectorMesh {
5962 voff,
5963 vcnt,
5964 ioff,
5965 icnt,
5966 uoff,
5967 } => {
5968 draw_indexed_mesh!(&pipes.mesh, uoff, voff, vcnt, ioff, icnt);
5969 }
5970
5971 Cmd::VectorOverlay {
5972 voff,
5973 vcnt,
5974 ioff,
5975 icnt,
5976 uoff,
5977 } => {
5978 draw_indexed_mesh!(&pipes.mesh_overlay, uoff, voff, vcnt, ioff, icnt);
5979 }
5980
5981 Cmd::VectorClipPush {
5982 voff,
5983 vcnt,
5984 ioff,
5985 icnt,
5986 uoff,
5987 scissor,
5988 } => {
5989 let scissor =
5990 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
5991 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
5992 rpass.set_stencil_reference(clip_depth);
5993 draw_indexed_mesh!(&pipes.mesh_clip_inc, uoff, voff, vcnt, ioff, icnt);
5994 clip_depth = (clip_depth + 1).min(255);
5995 rpass.set_stencil_reference(clip_depth);
5996 }
5997
5998 Cmd::VectorClipPop {
5999 voff,
6000 vcnt,
6001 ioff,
6002 icnt,
6003 uoff,
6004 scissor,
6005 } => {
6006 rpass.set_stencil_reference(clip_depth);
6010 let scissor =
6011 clamp_scissor(scissor.0, scissor.1, scissor.2, scissor.3, tw, th);
6012 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
6013 draw_indexed_mesh!(&pipes.mesh_clip_dec, uoff, voff, vcnt, ioff, icnt);
6014 clip_depth = clip_depth.saturating_sub(1);
6015 rpass.set_stencil_reference(clip_depth);
6016 }
6017
6018 Cmd::Callback { rect, payload } => {
6019 if let Some(cb) = payload.downcast_ref::<Callback>() {
6020 let vp_x = rect.x.floor().max(0.0);
6021 let vp_y = rect.y.floor().max(0.0);
6022 let vp_w = rect.w.ceil().max(1.0);
6023 let vp_h = rect.h.ceil().max(1.0);
6024 if vp_w > 0.0 && vp_h > 0.0 {
6025 rpass.set_viewport(vp_x, vp_y, vp_w, vp_h, 0.0, 1.0);
6026 let info = repose_core::PaintCallbackInfo {
6027 viewport: rect,
6028 clip_rect: rect,
6029 pixels_per_point: self.pixels_per_point,
6030 screen_size_px: [tw, th],
6031 };
6032 let rpass_static: &mut wgpu::RenderPass<'static> = unsafe {
6033 std::mem::transmute::<
6034 &mut wgpu::RenderPass<'_>,
6035 &mut wgpu::RenderPass<'static>,
6036 >(&mut rpass)
6037 };
6038 cb.0.paint(info, rpass_static, &self.callback_resources);
6039 rpass.set_viewport(0.0, 0.0, tw as f32, th as f32, 0.0, 1.0);
6040 rpass.set_bind_group(0, &self.globals_bind, &[]);
6041 rpass.set_stencil_reference(clip_depth);
6042 }
6043 } else {
6044 log::warn!("Unknown paint callback payload");
6045 }
6046 }
6047 }
6048 }
6049 }
6050
6051 if self.working_space
6053 && let (Some(_ws_view), Some(ws_bind), Some(display_pipeline)) =
6054 (&self.ws_view, &self.ws_bind, &self.display_pipeline)
6055 {
6056 let swap_view = target_view.clone();
6057 let mut display_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
6058 label: Some("display transform"),
6059 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
6060 view: &swap_view,
6061 resolve_target: None,
6062 ops: wgpu::Operations {
6063 load: wgpu::LoadOp::Load,
6064 store: wgpu::StoreOp::Store,
6065 },
6066 depth_slice: None,
6067 })],
6068 depth_stencil_attachment: None,
6069 timestamp_writes: None,
6070 occlusion_query_set: None,
6071 multiview_mask: None,
6072 });
6073 display_pass.set_pipeline(display_pipeline);
6074 display_pass.set_bind_group(1, ws_bind, &[]);
6075 display_pass.draw(0..3, 0..1);
6076 }
6077
6078 self.evict_unused_images();
6080 }
6081
6082 pub fn render_to_view(
6086 &mut self,
6087 scene: &Scene,
6088 encoder: &mut wgpu::CommandEncoder,
6089 target_view: &wgpu::TextureView,
6090 width: u32,
6091 height: u32,
6092 clear_color: Option<[f64; 4]>,
6093 ) {
6094 self.resize(width, height);
6095
6096 self.frame_index = self.frame_index.wrapping_add(1);
6097 self.slug_cache.next_frame();
6098
6099 if width == 0 || height == 0 {
6100 return;
6101 }
6102
6103 self.render_scene_to_encoder(scene, encoder, target_view, clear_color);
6104 }
6105}
6106
6107fn clamp_scissor(x: u32, y: u32, w: u32, h: u32, tw: u32, th: u32) -> (u32, u32, u32, u32) {
6108 let x = x.min(tw.saturating_sub(1));
6109 let y = y.min(th.saturating_sub(1));
6110 let w = w.min(tw.saturating_sub(x)).max(1);
6111 let h = h.min(th.saturating_sub(y)).max(1);
6112 (x, y, w, h)
6113}
6114
6115fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
6116 let x0 = a.x.max(b.x);
6117 let y0 = a.y.max(b.y);
6118 let x1 = (a.x + a.w).min(b.x + b.w);
6119 let y1 = (a.y + a.h).min(b.y + b.h);
6120 repose_core::Rect {
6121 x: x0,
6122 y: y0,
6123 w: (x1 - x0).max(0.0),
6124 h: (y1 - y0).max(0.0),
6125 }
6126}