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