1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use repose_core::request_frame;
6use repose_core::{Brush, GlyphRasterConfig, RenderBackend, Scene, SceneNode, Transform};
7use std::panic::{AssertUnwindSafe, catch_unwind};
8use wgpu::Instance;
9
10mod slug;
11
12#[derive(Clone)]
13struct UploadRing {
14 buf: wgpu::Buffer,
15 cap: u64,
16 head: u64,
17}
18
19impl UploadRing {
20 fn new(device: &wgpu::Device, label: &str, cap: u64) -> Self {
21 let buf = device.create_buffer(&wgpu::BufferDescriptor {
22 label: Some(label),
23 size: cap,
24 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
25 mapped_at_creation: false,
26 });
27 Self { buf, cap, head: 0 }
28 }
29
30 fn reset(&mut self) {
31 self.head = 0;
32 }
33
34 fn grow_to_fit(&mut self, device: &wgpu::Device, needed: u64) {
35 let start = (self.head + 3) & !3;
36 if start + needed <= self.cap {
37 return;
38 }
39 let new_cap = (start + needed).next_power_of_two();
40 self.buf = device.create_buffer(&wgpu::BufferDescriptor {
41 label: Some("upload ring (grown)"),
42 size: new_cap,
43 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
44 mapped_at_creation: false,
45 });
46 self.cap = new_cap;
47 }
48
49 fn alloc_write(&mut self, queue: &wgpu::Queue, bytes: &[u8]) -> (u64, u64) {
50 let len = bytes.len() as u64;
51 let start = (self.head + 3) & !3; let end = start + len;
53 assert!(end <= self.cap, "ring overflow - call grow_to_fit first");
54 queue.write_buffer(&self.buf, start, bytes);
55 self.head = end;
56 (start, len)
57 }
58}
59
60struct InstancedPipe<I: bytemuck::Pod> {
61 ring: UploadRing,
62 _marker: std::marker::PhantomData<I>,
63}
64
65impl<I: bytemuck::Pod> InstancedPipe<I> {
66 fn new(ring: UploadRing) -> Self {
67 Self {
68 ring,
69 _marker: std::marker::PhantomData,
70 }
71 }
72
73 fn upload(
74 &mut self,
75 device: &wgpu::Device,
76 queue: &wgpu::Queue,
77 data: &[I],
78 ) -> Option<(u64, u32)> {
79 if data.is_empty() {
80 return None;
81 }
82 let bytes = bytemuck::cast_slice(data);
83 self.ring.grow_to_fit(device, bytes.len() as u64);
84 let (off, wrote) = self.ring.alloc_write(queue, bytes);
85 debug_assert_eq!(wrote as usize, bytes.len());
86 Some((off, data.len() as u32))
87 }
88
89 fn reset(&mut self) {
90 self.ring.reset();
91 }
92}
93
94#[repr(C)]
95#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
96struct Globals {
97 ndc_to_px: [f32; 2],
98 _pad: [f32; 2],
99}
100
101pub struct WgpuBackend {
102 surface: wgpu::Surface<'static>,
103 device: wgpu::Device,
104 queue: wgpu::Queue,
105 config: wgpu::SurfaceConfiguration,
106
107 surface_pipes: Pipelines,
110 layer_pipes: Pipelines,
111
112 rects: InstancedPipe<RectInstance>,
114 borders: InstancedPipe<BorderInstance>,
115 ellipses: InstancedPipe<EllipseInstance>,
116 ellipse_borders: InstancedPipe<EllipseBorderInstance>,
117 glyph_mask: InstancedPipe<GlyphInstance>,
118 glyph_color: InstancedPipe<GlyphInstance>,
119
120 image_bind_layout_rgba: wgpu::BindGroupLayout,
122 image_bind_layout_nv12: wgpu::BindGroupLayout,
123 image_sampler: wgpu::Sampler,
124
125 blur_ring: UploadRing,
127
128 text_bind_layout: wgpu::BindGroupLayout,
129
130 clip_ring: UploadRing,
132
133 slug_pipeline: Option<slug::SlugPipeline>,
135 slug_ring: UploadRing,
136 slug_cache: slug::GlyphSlugCache,
137 slug_prev_generation: u64,
138 slug_instances: Vec<slug::SlugVertex>,
139
140 nv12: InstancedPipe<Nv12Instance>,
142
143 msaa_samples: u32,
144
145 depth_stencil_tex: wgpu::Texture,
147 depth_stencil_view: wgpu::TextureView,
148
149 msaa_tex: Option<wgpu::Texture>,
151 msaa_view: Option<wgpu::TextureView>,
152
153 globals_layout: wgpu::BindGroupLayout,
154 globals_buf: wgpu::Buffer,
155 globals_bind: wgpu::BindGroup,
156
157 atlas_mask: AtlasA8,
159 atlas_color: AtlasRGBA,
160
161 next_image_handle: u64,
163 images: HashMap<u64, ImageTex>,
164
165 frame_index: u64,
167 image_bytes_total: u64,
168 image_evict_after_frames: u64,
169 image_budget_bytes: u64,
170
171 layer_pool: HashMap<u32, LayerTarget>,
174}
175
176impl Drop for WgpuBackend {
177 fn drop(&mut self) {
178 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
179 }
180}
181
182#[derive(Clone)]
183struct LayerTarget {
184 texture: wgpu::Texture,
185 view: wgpu::TextureView,
186 bind: wgpu::BindGroup,
187 depth_stencil_tex: wgpu::Texture,
188 depth_stencil_view: wgpu::TextureView,
189 width: u32,
190 height: u32,
191 rect_px: (f32, f32, f32, f32),
192}
193
194#[derive(Clone, Copy)]
196enum PassTarget {
197 Surface,
198 Layer(u32),
199}
200
201struct Pipelines {
206 rects: wgpu::RenderPipeline,
207 borders: wgpu::RenderPipeline,
208 ellipses: wgpu::RenderPipeline,
209 ellipse_borders: wgpu::RenderPipeline,
210 text_mask: wgpu::RenderPipeline,
211 text_color: wgpu::RenderPipeline,
212 image_rgba: wgpu::RenderPipeline,
213 image_nv12: wgpu::RenderPipeline,
214 blur: wgpu::RenderPipeline,
215 clip_a2c: wgpu::RenderPipeline,
216 clip_bin: wgpu::RenderPipeline,
217 slug: Option<wgpu::RenderPipeline>,
218}
219
220impl Pipelines {
221 fn create(
222 device: &wgpu::Device,
223 format: wgpu::TextureFormat,
224 sample_count: u32,
225 globals_layout: &wgpu::BindGroupLayout,
226 text_bind_layout: &wgpu::BindGroupLayout,
227 image_bind_layout_nv12: &wgpu::BindGroupLayout,
228 clip_pipeline_layout: &wgpu::PipelineLayout,
229 stencil_for_content: &wgpu::DepthStencilState,
230 stencil_for_clip_inc: &wgpu::DepthStencilState,
231 clip_color_target: &wgpu::ColorTargetState,
232 clip_vertex_layout: &wgpu::VertexBufferLayout,
233 slug_storage_layout: Option<&wgpu::BindGroupLayout>,
234 ) -> Self {
235 let msaa_state = wgpu::MultisampleState {
236 count: sample_count,
237 mask: !0,
238 alpha_to_coverage_enabled: false,
239 };
240
241 macro_rules! make_content_pipeline {
242 ($name:ident, $shader:literal, $inst_type:ty, $attrs:expr) => {
243 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
244 label: Some(concat!($shader, ".wgsl")),
245 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(concat!(
246 "shaders/", $shader, ".wgsl"
247 )))),
248 });
249 let pipeline_layout =
250 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
251 label: Some(concat!($shader, " pipeline layout")),
252 bind_group_layouts: &[Some(globals_layout)],
253 immediate_size: 0,
254 });
255 let $name = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
256 label: Some(concat!($shader, " pipeline")),
257 layout: Some(&pipeline_layout),
258 vertex: wgpu::VertexState {
259 module: &shader_module,
260 entry_point: Some("vs_main"),
261 buffers: &[wgpu::VertexBufferLayout {
262 array_stride: std::mem::size_of::<$inst_type>() as u64,
263 step_mode: wgpu::VertexStepMode::Instance,
264 attributes: $attrs,
265 }],
266 compilation_options: wgpu::PipelineCompilationOptions::default(),
267 },
268 fragment: Some(wgpu::FragmentState {
269 module: &shader_module,
270 entry_point: Some("fs_main"),
271 targets: &[Some(wgpu::ColorTargetState {
272 format,
273 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
274 write_mask: wgpu::ColorWrites::ALL,
275 })],
276 compilation_options: wgpu::PipelineCompilationOptions::default(),
277 }),
278 primitive: wgpu::PrimitiveState::default(),
279 depth_stencil: Some(stencil_for_content.clone()),
280 multisample: msaa_state,
281 multiview_mask: None,
282 cache: None,
283 });
284 };
285 }
286
287 let rect_attrs: &[wgpu::VertexAttribute] = &[
288 wgpu::VertexAttribute {
289 shader_location: 0,
290 offset: 0,
291 format: wgpu::VertexFormat::Float32x4,
292 },
293 wgpu::VertexAttribute {
294 shader_location: 1,
295 offset: 16,
296 format: wgpu::VertexFormat::Float32,
297 },
298 wgpu::VertexAttribute {
299 shader_location: 2,
300 offset: 20,
301 format: wgpu::VertexFormat::Uint32,
302 },
303 wgpu::VertexAttribute {
304 shader_location: 3,
305 offset: 24,
306 format: wgpu::VertexFormat::Float32x4,
307 },
308 wgpu::VertexAttribute {
309 shader_location: 4,
310 offset: 40,
311 format: wgpu::VertexFormat::Float32x4,
312 },
313 wgpu::VertexAttribute {
314 shader_location: 5,
315 offset: 56,
316 format: wgpu::VertexFormat::Float32x2,
317 },
318 wgpu::VertexAttribute {
319 shader_location: 6,
320 offset: 64,
321 format: wgpu::VertexFormat::Float32x2,
322 },
323 wgpu::VertexAttribute {
324 shader_location: 7,
325 offset: 72,
326 format: wgpu::VertexFormat::Float32x2,
327 },
328 ];
329 let border_attrs: &[wgpu::VertexAttribute] = &[
330 wgpu::VertexAttribute {
331 shader_location: 0,
332 offset: 0,
333 format: wgpu::VertexFormat::Float32x4,
334 },
335 wgpu::VertexAttribute {
336 shader_location: 1,
337 offset: 16,
338 format: wgpu::VertexFormat::Float32,
339 },
340 wgpu::VertexAttribute {
341 shader_location: 2,
342 offset: 20,
343 format: wgpu::VertexFormat::Float32,
344 },
345 wgpu::VertexAttribute {
346 shader_location: 3,
347 offset: 24,
348 format: wgpu::VertexFormat::Float32x4,
349 },
350 wgpu::VertexAttribute {
351 shader_location: 4,
352 offset: 40,
353 format: wgpu::VertexFormat::Float32x2,
354 },
355 ];
356 let ellipse_attrs: &[wgpu::VertexAttribute] = &[
357 wgpu::VertexAttribute {
358 shader_location: 0,
359 offset: 0,
360 format: wgpu::VertexFormat::Float32x4,
361 },
362 wgpu::VertexAttribute {
363 shader_location: 1,
364 offset: 16,
365 format: wgpu::VertexFormat::Float32x4,
366 },
367 wgpu::VertexAttribute {
368 shader_location: 2,
369 offset: 32,
370 format: wgpu::VertexFormat::Float32x2,
371 },
372 ];
373 let ellipse_border_attrs: &[wgpu::VertexAttribute] = &[
374 wgpu::VertexAttribute {
375 shader_location: 0,
376 offset: 0,
377 format: wgpu::VertexFormat::Float32x4,
378 },
379 wgpu::VertexAttribute {
380 shader_location: 1,
381 offset: 16,
382 format: wgpu::VertexFormat::Float32,
383 },
384 wgpu::VertexAttribute {
385 shader_location: 2,
386 offset: 20,
387 format: wgpu::VertexFormat::Float32x4,
388 },
389 wgpu::VertexAttribute {
390 shader_location: 3,
391 offset: 36,
392 format: wgpu::VertexFormat::Float32x2,
393 },
394 ];
395
396 make_content_pipeline!(rects, "rect", RectInstance, rect_attrs);
397 make_content_pipeline!(borders, "border", BorderInstance, border_attrs);
398 make_content_pipeline!(ellipses, "ellipse", EllipseInstance, ellipse_attrs);
399 make_content_pipeline!(
400 ellipse_borders,
401 "ellipse_border",
402 EllipseBorderInstance,
403 ellipse_border_attrs
404 );
405
406 let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
408 label: Some("text.wgsl"),
409 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
410 });
411 let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
413 label: Some("text_color.wgsl"),
414 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
415 "shaders/text_color.wgsl"
416 ))),
417 });
418 let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
419 label: Some("text pipeline layout"),
420 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
421 immediate_size: 0,
422 });
423 let glyph_vertex = wgpu::VertexBufferLayout {
424 array_stride: std::mem::size_of::<GlyphInstance>() as u64,
425 step_mode: wgpu::VertexStepMode::Instance,
426 attributes: &[
427 wgpu::VertexAttribute {
428 shader_location: 0,
429 offset: 0,
430 format: wgpu::VertexFormat::Float32x4,
431 },
432 wgpu::VertexAttribute {
433 shader_location: 1,
434 offset: 16,
435 format: wgpu::VertexFormat::Float32x4,
436 },
437 wgpu::VertexAttribute {
438 shader_location: 2,
439 offset: 32,
440 format: wgpu::VertexFormat::Float32x4,
441 },
442 wgpu::VertexAttribute {
443 shader_location: 3,
444 offset: 48,
445 format: wgpu::VertexFormat::Float32x2,
446 },
447 ],
448 };
449 let text_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
450 label: Some("text pipeline (mask)"),
451 layout: Some(&text_pipeline_layout),
452 vertex: wgpu::VertexState {
453 module: &text_mask_shader,
454 entry_point: Some("vs_main"),
455 buffers: &[glyph_vertex.clone()],
456 compilation_options: wgpu::PipelineCompilationOptions::default(),
457 },
458 fragment: Some(wgpu::FragmentState {
459 module: &text_mask_shader,
460 entry_point: Some("fs_main"),
461 targets: &[Some(wgpu::ColorTargetState {
462 format,
463 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
464 write_mask: wgpu::ColorWrites::ALL,
465 })],
466 compilation_options: wgpu::PipelineCompilationOptions::default(),
467 }),
468 primitive: wgpu::PrimitiveState::default(),
469 depth_stencil: Some(stencil_for_content.clone()),
470 multisample: msaa_state,
471 multiview_mask: None,
472 cache: None,
473 });
474 let text_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
475 label: Some("text pipeline (color)"),
476 layout: Some(&text_pipeline_layout),
477 vertex: wgpu::VertexState {
478 module: &text_color_shader,
479 entry_point: Some("vs_main"),
480 buffers: &[glyph_vertex],
481 compilation_options: wgpu::PipelineCompilationOptions::default(),
482 },
483 fragment: Some(wgpu::FragmentState {
484 module: &text_color_shader,
485 entry_point: Some("fs_main"),
486 targets: &[Some(wgpu::ColorTargetState {
487 format,
488 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
489 write_mask: wgpu::ColorWrites::ALL,
490 })],
491 compilation_options: wgpu::PipelineCompilationOptions::default(),
492 }),
493 primitive: wgpu::PrimitiveState::default(),
494 depth_stencil: Some(stencil_for_content.clone()),
495 multisample: msaa_state,
496 multiview_mask: None,
497 cache: None,
498 });
499 let image_rgba = text_color.clone();
501
502 let blur_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
504 label: Some("blur_shadow.wgsl"),
505 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
506 "shaders/blur_shadow.wgsl"
507 ))),
508 });
509 let blur_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
510 label: Some("blur pipeline layout"),
511 bind_group_layouts: &[Some(globals_layout), Some(text_bind_layout)],
512 immediate_size: 0,
513 });
514 let blur = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
515 label: Some("blur pipeline"),
516 layout: Some(&blur_pipeline_layout),
517 vertex: wgpu::VertexState {
518 module: &blur_shader,
519 entry_point: Some("vs_main"),
520 buffers: &[wgpu::VertexBufferLayout {
521 array_stride: std::mem::size_of::<BlurInstance>() as u64,
522 step_mode: wgpu::VertexStepMode::Instance,
523 attributes: &[
524 wgpu::VertexAttribute {
525 shader_location: 0,
526 offset: 0,
527 format: wgpu::VertexFormat::Float32x4,
528 },
529 wgpu::VertexAttribute {
530 shader_location: 1,
531 offset: 16,
532 format: wgpu::VertexFormat::Float32x4,
533 },
534 wgpu::VertexAttribute {
535 shader_location: 2,
536 offset: 32,
537 format: wgpu::VertexFormat::Float32x4,
538 },
539 wgpu::VertexAttribute {
540 shader_location: 3,
541 offset: 48,
542 format: wgpu::VertexFormat::Float32x2,
543 },
544 wgpu::VertexAttribute {
545 shader_location: 4,
546 offset: 56,
547 format: wgpu::VertexFormat::Float32x2,
548 },
549 ],
550 }],
551 compilation_options: wgpu::PipelineCompilationOptions::default(),
552 },
553 fragment: Some(wgpu::FragmentState {
554 module: &blur_shader,
555 entry_point: Some("fs_main"),
556 targets: &[Some(wgpu::ColorTargetState {
557 format,
558 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
559 write_mask: wgpu::ColorWrites::ALL,
560 })],
561 compilation_options: wgpu::PipelineCompilationOptions::default(),
562 }),
563 primitive: wgpu::PrimitiveState::default(),
564 depth_stencil: Some(stencil_for_content.clone()),
565 multisample: msaa_state,
566 multiview_mask: None,
567 cache: None,
568 });
569
570 let image_nv12_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
572 label: Some("image_nv12.wgsl"),
573 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
574 "shaders/image_nv12.wgsl"
575 ))),
576 });
577 let image_nv12_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
578 label: Some("image nv12 pipeline layout"),
579 bind_group_layouts: &[Some(globals_layout), Some(image_bind_layout_nv12)],
580 immediate_size: 0,
581 });
582 let image_nv12 = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
583 label: Some("image nv12 pipeline"),
584 layout: Some(&image_nv12_layout),
585 vertex: wgpu::VertexState {
586 module: &image_nv12_shader,
587 entry_point: Some("vs_main"),
588 buffers: &[wgpu::VertexBufferLayout {
589 array_stride: std::mem::size_of::<Nv12Instance>() as u64,
590 step_mode: wgpu::VertexStepMode::Instance,
591 attributes: &[
592 wgpu::VertexAttribute {
593 shader_location: 0,
594 offset: 0,
595 format: wgpu::VertexFormat::Float32x4,
596 },
597 wgpu::VertexAttribute {
598 shader_location: 1,
599 offset: 16,
600 format: wgpu::VertexFormat::Float32x4,
601 },
602 wgpu::VertexAttribute {
603 shader_location: 2,
604 offset: 32,
605 format: wgpu::VertexFormat::Float32x4,
606 },
607 wgpu::VertexAttribute {
608 shader_location: 3,
609 offset: 48,
610 format: wgpu::VertexFormat::Float32,
611 },
612 wgpu::VertexAttribute {
613 shader_location: 4,
614 offset: 52,
615 format: wgpu::VertexFormat::Float32x2,
616 },
617 ],
618 }],
619 compilation_options: wgpu::PipelineCompilationOptions::default(),
620 },
621 fragment: Some(wgpu::FragmentState {
622 module: &image_nv12_shader,
623 entry_point: Some("fs_main"),
624 targets: &[Some(wgpu::ColorTargetState {
625 format,
626 blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
627 write_mask: wgpu::ColorWrites::ALL,
628 })],
629 compilation_options: wgpu::PipelineCompilationOptions::default(),
630 }),
631 primitive: wgpu::PrimitiveState::default(),
632 depth_stencil: Some(stencil_for_content.clone()),
633 multisample: msaa_state,
634 multiview_mask: None,
635 cache: None,
636 });
637
638 let clip_shader_a2c = device.create_shader_module(wgpu::ShaderModuleDescriptor {
640 label: Some("clip_round_rect_a2c.wgsl"),
641 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
642 "shaders/clip_round_rect_a2c.wgsl"
643 ))),
644 });
645 let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
646 label: Some("clip_round_rect_bin.wgsl"),
647 source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
648 "shaders/clip_round_rect_bin.wgsl"
649 ))),
650 });
651 let clip_a2c = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
652 label: Some("clip pipeline (a2c)"),
653 layout: Some(clip_pipeline_layout),
654 vertex: wgpu::VertexState {
655 module: &clip_shader_a2c,
656 entry_point: Some("vs_main"),
657 buffers: &[clip_vertex_layout.clone()],
658 compilation_options: wgpu::PipelineCompilationOptions::default(),
659 },
660 fragment: Some(wgpu::FragmentState {
661 module: &clip_shader_a2c,
662 entry_point: Some("fs_main"),
663 targets: &[Some(clip_color_target.clone())],
664 compilation_options: wgpu::PipelineCompilationOptions::default(),
665 }),
666 primitive: wgpu::PrimitiveState::default(),
667 depth_stencil: Some(stencil_for_clip_inc.clone()),
668 multisample: wgpu::MultisampleState {
669 count: sample_count,
670 mask: !0,
671 alpha_to_coverage_enabled: sample_count > 1,
672 },
673 multiview_mask: None,
674 cache: None,
675 });
676 let clip_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
677 label: Some("clip pipeline (bin)"),
678 layout: Some(clip_pipeline_layout),
679 vertex: wgpu::VertexState {
680 module: &clip_shader_bin,
681 entry_point: Some("vs_main"),
682 buffers: &[clip_vertex_layout.clone()],
683 compilation_options: wgpu::PipelineCompilationOptions::default(),
684 },
685 fragment: Some(wgpu::FragmentState {
686 module: &clip_shader_bin,
687 entry_point: Some("fs_main"),
688 targets: &[Some(clip_color_target.clone())],
689 compilation_options: wgpu::PipelineCompilationOptions::default(),
690 }),
691 primitive: wgpu::PrimitiveState::default(),
692 depth_stencil: Some(stencil_for_clip_inc.clone()),
693 multisample: wgpu::MultisampleState {
694 count: sample_count,
695 mask: !0,
696 alpha_to_coverage_enabled: false,
697 },
698 multiview_mask: None,
699 cache: None,
700 });
701
702 let slug = slug_storage_layout.map(|layout| {
703 slug::create_pipeline(
704 device,
705 format,
706 sample_count,
707 globals_layout,
708 layout,
709 stencil_for_content,
710 )
711 });
712
713 Self {
714 rects,
715 borders,
716 ellipses,
717 ellipse_borders,
718 text_mask,
719 text_color,
720 image_rgba,
721 image_nv12,
722 blur,
723 clip_a2c,
724 clip_bin,
725 slug,
726 }
727 }
728}
729
730struct Pass {
732 target: PassTarget,
733 initial_scissor: (u32, u32, u32, u32),
735 clear_color: Option<[f32; 4]>,
738 cmds: Vec<Cmd>,
739}
740
741#[allow(non_snake_case)]
742enum Cmd {
743 ClipPush {
744 off: u64,
745 cnt: u32,
746 scissor: (u32, u32, u32, u32),
747 },
748 ClipPop {
749 scissor: (u32, u32, u32, u32),
750 },
751 Rect {
752 off: u64,
753 cnt: u32,
754 },
755 Border {
756 off: u64,
757 cnt: u32,
758 },
759 Ellipse {
760 off: u64,
761 cnt: u32,
762 },
763 EllipseBorder {
764 off: u64,
765 cnt: u32,
766 },
767 GlyphsMask {
768 off: u64,
769 cnt: u32,
770 },
771 GlyphsColor {
772 off: u64,
773 cnt: u32,
774 },
775 GlyphsVector {
776 off: u64,
777 cnt: u32,
778 },
779 ImageRgba {
780 off: u64,
781 cnt: u32,
782 handle: u64,
783 },
784 ImageNv12 {
785 off: u64,
786 cnt: u32,
787 handle: u64,
788 },
789 PushTransform(Transform),
790 PopTransform,
791 CompositeLayer {
795 off: u64,
796 cnt: u32,
797 layer_id: u32,
798 alpha: f32,
799 },
800 CompositeShadow {
804 off: u64,
805 cnt: u32,
806 layer_id: u32,
807 },
808}
809
810enum ImageTex {
811 Rgba {
812 tex: wgpu::Texture,
813 view: wgpu::TextureView,
814 bind: wgpu::BindGroup,
815 w: u32,
816 h: u32,
817 format: wgpu::TextureFormat,
818 last_used_frame: u64,
819 bytes: u64,
820 },
821 Nv12 {
822 tex_y: wgpu::Texture,
823 view_y: wgpu::TextureView,
824 tex_uv: wgpu::Texture,
825 view_uv: wgpu::TextureView,
826 bind: wgpu::BindGroup,
827 w: u32,
828 h: u32,
829 full_range: bool,
830 last_used_frame: u64,
831 bytes: u64,
832 },
833}
834
835struct AtlasA8 {
836 tex: wgpu::Texture,
837 view: wgpu::TextureView,
838 sampler: wgpu::Sampler,
839 size: u32,
840 next_x: u32,
841 next_y: u32,
842 row_h: u32,
843 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
844}
845
846struct AtlasRGBA {
847 tex: wgpu::Texture,
848 view: wgpu::TextureView,
849 sampler: wgpu::Sampler,
850 size: u32,
851 next_x: u32,
852 next_y: u32,
853 row_h: u32,
854 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
855}
856
857#[derive(Clone, Copy)]
858struct GlyphInfo {
859 u0: f32,
860 v0: f32,
861 u1: f32,
862 v1: f32,
863 w: f32,
864 h: f32,
865 bearing_x: f32,
866 bearing_y: f32,
867 advance: f32,
868}
869
870#[repr(C)]
871#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
872struct RectInstance {
873 xywh: [f32; 4],
874 radius: f32,
875 brush_type: u32,
876 color0: [f32; 4],
877 color1: [f32; 4],
878 grad_start: [f32; 2],
879 grad_end: [f32; 2],
880 sin_cos: [f32; 2],
881}
882
883#[repr(C)]
884#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
885struct BorderInstance {
886 xywh: [f32; 4],
887 radius: f32,
888 stroke: f32,
889 color: [f32; 4],
890 sin_cos: [f32; 2],
891}
892
893#[repr(C)]
894#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
895struct EllipseInstance {
896 xywh: [f32; 4],
897 color: [f32; 4],
898 sin_cos: [f32; 2],
899}
900
901#[repr(C)]
902#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
903struct EllipseBorderInstance {
904 xywh: [f32; 4],
905 stroke: f32,
906 color: [f32; 4],
907 sin_cos: [f32; 2],
908}
909
910#[repr(C)]
911#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
912struct GlyphInstance {
913 xywh: [f32; 4],
914 uv: [f32; 4],
915 color: [f32; 4],
916 sin_cos: [f32; 2],
917}
918
919#[repr(C)]
920#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
921struct BlurInstance {
922 xywh: [f32; 4],
923 uv: [f32; 4],
924 color: [f32; 4],
925 blur_uv: [f32; 2],
926 sin_cos: [f32; 2],
927}
928
929#[repr(C)]
930#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
931struct Nv12Instance {
932 xywh: [f32; 4],
933 uv: [f32; 4],
934 color: [f32; 4], full_range: f32,
936 sin_cos: [f32; 2],
937 _pad: [f32; 1],
938}
939
940#[repr(C)]
941#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
942struct ClipInstance {
943 xywh: [f32; 4],
944 radius: f32,
945 sin_cos: [f32; 2],
946}
947
948fn swash_to_a8_coverage(content: cosmic_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
949 match content {
950 cosmic_text::SwashContent::Mask => Some(data.to_vec()),
951 cosmic_text::SwashContent::SubpixelMask => {
952 let mut out = Vec::with_capacity(data.len() / 4);
953 for px in data.chunks_exact(4) {
954 let r = px[0];
955 let g = px[1];
956 let b = px[2];
957 out.push(r.max(g).max(b));
958 }
959 Some(out)
960 }
961 cosmic_text::SwashContent::Color => None,
962 }
963}
964
965impl WgpuBackend {
966 pub async fn new_async(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
967 let instance: Instance;
968
969 if cfg!(target_arch = "wasm32") {
970 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
971 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
972 instance = wgpu::util::new_instance_with_webgpu_detection(desc).await;
973 } else {
974 instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
975 };
976
977 let surface = instance.create_surface(window.clone())?;
978
979 let adapter = instance
980 .request_adapter(&wgpu::RequestAdapterOptions {
981 power_preference: wgpu::PowerPreference::HighPerformance,
982 compatible_surface: Some(&surface),
983 force_fallback_adapter: false,
984 })
985 .await
986 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
987
988 let limits = adapter.limits();
989
990 let (device, queue) = adapter
991 .request_device(&wgpu::DeviceDescriptor {
992 label: Some("repose-rs device"),
993 required_features: wgpu::Features::empty(),
994 required_limits: limits,
995 experimental_features: wgpu::ExperimentalFeatures::disabled(),
996 memory_hints: wgpu::MemoryHints::default(),
997 trace: wgpu::Trace::Off,
998 })
999 .await
1000 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
1001
1002 let size = window.inner_size();
1003
1004 let caps = surface.get_capabilities(&adapter);
1005 let format = caps
1006 .formats
1007 .iter()
1008 .copied()
1009 .find(|f| f.is_srgb())
1010 .unwrap_or(caps.formats[0]);
1011 let present_mode = caps
1012 .present_modes
1013 .iter()
1014 .copied()
1015 .find(|m| *m == wgpu::PresentMode::Mailbox || *m == wgpu::PresentMode::Immediate)
1016 .unwrap_or(wgpu::PresentMode::Fifo);
1017 let alpha_mode = caps.alpha_modes[0];
1018
1019 let config = wgpu::SurfaceConfiguration {
1020 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1021 format,
1022 width: size.width.max(1),
1023 height: size.height.max(1),
1024 present_mode,
1025 alpha_mode,
1026 view_formats: vec![],
1027 desired_maximum_frame_latency: 2,
1028 };
1029 surface.configure(&device, &config);
1030
1031 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1032 label: Some("globals layout"),
1033 entries: &[wgpu::BindGroupLayoutEntry {
1034 binding: 0,
1035 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1036 ty: wgpu::BindingType::Buffer {
1037 ty: wgpu::BufferBindingType::Uniform,
1038 has_dynamic_offset: false,
1039 min_binding_size: None,
1040 },
1041 count: None,
1042 }],
1043 });
1044
1045 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
1046 label: Some("globals buf"),
1047 size: std::mem::size_of::<Globals>() as u64,
1048 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1049 mapped_at_creation: false,
1050 });
1051
1052 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1053 label: Some("globals bind"),
1054 layout: &globals_layout,
1055 entries: &[wgpu::BindGroupEntry {
1056 binding: 0,
1057 resource: globals_buf.as_entire_binding(),
1058 }],
1059 });
1060
1061 let fmt_features = adapter.get_texture_format_features(format);
1063 let msaa_samples = if fmt_features.flags.sample_count_supported(4)
1064 && fmt_features
1065 .flags
1066 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
1067 {
1068 4
1069 } else {
1070 1
1071 };
1072
1073 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
1074
1075 let stencil_for_content = wgpu::DepthStencilState {
1076 format: ds_format,
1077 depth_write_enabled: Some(false),
1078 depth_compare: Some(wgpu::CompareFunction::Always),
1079 stencil: wgpu::StencilState {
1080 front: wgpu::StencilFaceState {
1081 compare: wgpu::CompareFunction::LessEqual,
1082 fail_op: wgpu::StencilOperation::Keep,
1083 depth_fail_op: wgpu::StencilOperation::Keep,
1084 pass_op: wgpu::StencilOperation::Keep,
1085 },
1086 back: wgpu::StencilFaceState {
1087 compare: wgpu::CompareFunction::LessEqual,
1088 fail_op: wgpu::StencilOperation::Keep,
1089 depth_fail_op: wgpu::StencilOperation::Keep,
1090 pass_op: wgpu::StencilOperation::Keep,
1091 },
1092 read_mask: 0xFF,
1093 write_mask: 0x00,
1094 },
1095 bias: wgpu::DepthBiasState::default(),
1096 };
1097
1098 let stencil_for_clip_inc = wgpu::DepthStencilState {
1099 format: ds_format,
1100 depth_write_enabled: Some(false),
1101 depth_compare: Some(wgpu::CompareFunction::Always),
1102 stencil: wgpu::StencilState {
1103 front: wgpu::StencilFaceState {
1104 compare: wgpu::CompareFunction::Equal,
1105 fail_op: wgpu::StencilOperation::Keep,
1106 depth_fail_op: wgpu::StencilOperation::Keep,
1107 pass_op: wgpu::StencilOperation::IncrementClamp,
1108 },
1109 back: wgpu::StencilFaceState {
1110 compare: wgpu::CompareFunction::Equal,
1111 fail_op: wgpu::StencilOperation::Keep,
1112 depth_fail_op: wgpu::StencilOperation::Keep,
1113 pass_op: wgpu::StencilOperation::IncrementClamp,
1114 },
1115 read_mask: 0xFF,
1116 write_mask: 0xFF,
1117 },
1118 bias: wgpu::DepthBiasState::default(),
1119 };
1120
1121 let _multisample_state = wgpu::MultisampleState {
1122 count: msaa_samples,
1123 mask: !0,
1124 alpha_to_coverage_enabled: false,
1125 };
1126
1127 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1131 label: Some("image/text sampler"),
1132 address_mode_u: wgpu::AddressMode::ClampToEdge,
1133 address_mode_v: wgpu::AddressMode::ClampToEdge,
1134 mag_filter: wgpu::FilterMode::Linear,
1135 min_filter: wgpu::FilterMode::Linear,
1136 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1137 ..Default::default()
1138 });
1139
1140 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1142 label: Some("text/rgba bind layout"),
1143 entries: &[
1144 wgpu::BindGroupLayoutEntry {
1145 binding: 0,
1146 visibility: wgpu::ShaderStages::FRAGMENT,
1147 ty: wgpu::BindingType::Texture {
1148 multisampled: false,
1149 view_dimension: wgpu::TextureViewDimension::D2,
1150 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1151 },
1152 count: None,
1153 },
1154 wgpu::BindGroupLayoutEntry {
1155 binding: 1,
1156 visibility: wgpu::ShaderStages::FRAGMENT,
1157 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1158 count: None,
1159 },
1160 ],
1161 });
1162 let image_bind_layout_rgba = text_bind_layout.clone();
1164
1165 let image_bind_layout_nv12 =
1167 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1168 label: Some("image bind layout nv12"),
1169 entries: &[
1170 wgpu::BindGroupLayoutEntry {
1172 binding: 0,
1173 visibility: wgpu::ShaderStages::FRAGMENT,
1174 ty: wgpu::BindingType::Texture {
1175 multisampled: false,
1176 view_dimension: wgpu::TextureViewDimension::D2,
1177 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1178 },
1179 count: None,
1180 },
1181 wgpu::BindGroupLayoutEntry {
1183 binding: 1,
1184 visibility: wgpu::ShaderStages::FRAGMENT,
1185 ty: wgpu::BindingType::Texture {
1186 multisampled: false,
1187 view_dimension: wgpu::TextureViewDimension::D2,
1188 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1189 },
1190 count: None,
1191 },
1192 wgpu::BindGroupLayoutEntry {
1194 binding: 2,
1195 visibility: wgpu::ShaderStages::FRAGMENT,
1196 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1197 count: None,
1198 },
1199 ],
1200 });
1201
1202 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1204 label: Some("clip pipeline layout"),
1205 bind_group_layouts: &[Some(&globals_layout)],
1206 immediate_size: 0,
1207 });
1208 let clip_vertex_layout = wgpu::VertexBufferLayout {
1209 array_stride: std::mem::size_of::<ClipInstance>() as u64,
1210 step_mode: wgpu::VertexStepMode::Instance,
1211 attributes: &[
1212 wgpu::VertexAttribute {
1213 shader_location: 0,
1214 offset: 0,
1215 format: wgpu::VertexFormat::Float32x4,
1216 },
1217 wgpu::VertexAttribute {
1218 shader_location: 1,
1219 offset: 16,
1220 format: wgpu::VertexFormat::Float32,
1221 },
1222 wgpu::VertexAttribute {
1223 shader_location: 2,
1224 offset: 20,
1225 format: wgpu::VertexFormat::Float32x2,
1226 },
1227 ],
1228 };
1229 let clip_color_target = wgpu::ColorTargetState {
1230 format: config.format,
1231 blend: None,
1232 write_mask: wgpu::ColorWrites::empty(),
1233 };
1234
1235 let slug_storage_layout = if device.limits().max_storage_buffers_per_shader_stage > 0 {
1238 Some(slug::create_storage_layout(&device))
1239 } else {
1240 log::info!("storage buffers not supported per-shader-stage; vector glyphs disabled");
1241 None
1242 };
1243
1244 let surface_pipes = Pipelines::create(
1247 &device,
1248 config.format,
1249 msaa_samples,
1250 &globals_layout,
1251 &text_bind_layout,
1252 &image_bind_layout_nv12,
1253 &clip_pipeline_layout,
1254 &stencil_for_content,
1255 &stencil_for_clip_inc,
1256 &clip_color_target,
1257 &clip_vertex_layout,
1258 slug_storage_layout.as_ref(),
1259 );
1260 let layer_pipes = Pipelines::create(
1261 &device,
1262 config.format,
1263 1,
1264 &globals_layout,
1265 &text_bind_layout,
1266 &image_bind_layout_nv12,
1267 &clip_pipeline_layout,
1268 &stencil_for_content,
1269 &stencil_for_clip_inc,
1270 &clip_color_target,
1271 &clip_vertex_layout,
1272 slug_storage_layout.as_ref(),
1273 );
1274
1275 let slug_pipeline = slug_storage_layout
1277 .as_ref()
1278 .map(|layout| slug::SlugPipeline::new(&device, layout));
1279
1280 let blur_ring = UploadRing::new(&device, "blur ring", 1024 * 1024);
1282
1283 let atlas_mask = Self::init_atlas_mask(&device)?;
1285 let atlas_color = Self::init_atlas_color(&device)?;
1286
1287 let ring_rect = UploadRing::new(&device, "ring rect", 1 << 20);
1289 let ring_border = UploadRing::new(&device, "ring border", 1 << 20);
1290 let ring_ellipse = UploadRing::new(&device, "ring ellipse", 1 << 20);
1291 let ring_ellipse_border = UploadRing::new(&device, "ring ellipse border", 1 << 20);
1292 let ring_glyph_mask = UploadRing::new(&device, "ring glyph mask", 1 << 20);
1293 let ring_glyph_color = UploadRing::new(&device, "ring glyph color", 1 << 20);
1294 let ring_slug = UploadRing::new(&device, "ring slug", 1 << 22);
1295 let ring_clip = UploadRing::new(&device, "ring clip", 1 << 16);
1296 let ring_nv12 = UploadRing::new(&device, "ring nv12", 1 << 20);
1297
1298 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
1300 label: Some("temp ds"),
1301 size: wgpu::Extent3d {
1302 width: 1,
1303 height: 1,
1304 depth_or_array_layers: 1,
1305 },
1306 mip_level_count: 1,
1307 sample_count: 1,
1308 dimension: wgpu::TextureDimension::D2,
1309 format: wgpu::TextureFormat::Depth24PlusStencil8,
1310 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1311 view_formats: &[],
1312 });
1313 let depth_stencil_view =
1314 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1315
1316 let mut backend = Self {
1317 surface,
1318 device,
1319 queue,
1320 config,
1321
1322 surface_pipes,
1323 layer_pipes,
1324
1325 rects: InstancedPipe::new(ring_rect),
1326 borders: InstancedPipe::new(ring_border),
1327 ellipses: InstancedPipe::new(ring_ellipse),
1328 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
1329 glyph_mask: InstancedPipe::new(ring_glyph_mask),
1330 glyph_color: InstancedPipe::new(ring_glyph_color),
1331
1332 text_bind_layout,
1333
1334 image_bind_layout_rgba,
1335 image_bind_layout_nv12,
1336 image_sampler,
1337
1338 blur_ring,
1339
1340 slug_pipeline,
1341 slug_ring: ring_slug,
1342 slug_cache: slug::GlyphSlugCache::new(),
1343 slug_prev_generation: !0,
1344 slug_instances: Vec::new(),
1345
1346 clip_ring: ring_clip,
1347
1348 nv12: InstancedPipe::new(ring_nv12),
1349
1350 msaa_samples,
1351 depth_stencil_tex,
1352 depth_stencil_view,
1353 msaa_tex: None,
1354 msaa_view: None,
1355 globals_bind,
1356 globals_buf,
1357 globals_layout,
1358
1359 atlas_mask,
1360 atlas_color,
1361
1362 next_image_handle: 1,
1363 images: HashMap::new(),
1364
1365 frame_index: 0,
1366 image_bytes_total: 0,
1367 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
1370 };
1371
1372 backend.recreate_msaa_and_depth_stencil();
1373 Ok(backend)
1374 }
1375
1376 #[cfg(not(target_arch = "wasm32"))]
1377 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1378 pollster::block_on(Self::new_async(window))
1379 }
1380
1381 #[cfg(target_arch = "wasm32")]
1382 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1383 anyhow::bail!("Use WgpuBackend::new_async(window).await on wasm32")
1384 }
1385
1386 pub fn set_image_from_bytes(
1389 &mut self,
1390 handle: u64,
1391 data: &[u8],
1392 srgb: bool,
1393 ) -> anyhow::Result<()> {
1394 let img = image::load_from_memory(data)?;
1395 let rgba = img.to_rgba8();
1396 let (w, h) = rgba.dimensions();
1397 self.set_image_rgba8(handle, w, h, &rgba, srgb)
1398 }
1399
1400 pub fn set_image_rgba8(
1401 &mut self,
1402 handle: u64,
1403 w: u32,
1404 h: u32,
1405 rgba: &[u8],
1406 srgb: bool,
1407 ) -> anyhow::Result<()> {
1408 let expected = (w as usize) * (h as usize) * 4;
1409 if rgba.len() < expected {
1410 return Err(anyhow::anyhow!(
1411 "RGBA buffer too small: {} < {}",
1412 rgba.len(),
1413 expected
1414 ));
1415 }
1416
1417 let format = if srgb {
1418 wgpu::TextureFormat::Rgba8UnormSrgb
1419 } else {
1420 wgpu::TextureFormat::Rgba8Unorm
1421 };
1422
1423 let needs_recreate = match self.images.get(&handle) {
1424 Some(ImageTex::Rgba {
1425 w: cw,
1426 h: ch,
1427 format: cf,
1428 ..
1429 }) => *cw != w || *ch != h || *cf != format,
1430 _ => true,
1431 };
1432
1433 if needs_recreate {
1434 self.remove_image(handle);
1436
1437 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1438 label: Some("user image rgba"),
1439 size: wgpu::Extent3d {
1440 width: w,
1441 height: h,
1442 depth_or_array_layers: 1,
1443 },
1444 mip_level_count: 1,
1445 sample_count: 1,
1446 dimension: wgpu::TextureDimension::D2,
1447 format,
1448 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1449 view_formats: &[],
1450 });
1451 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1452
1453 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1454 label: Some("image bind rgba"),
1455 layout: &self.image_bind_layout_rgba,
1456 entries: &[
1457 wgpu::BindGroupEntry {
1458 binding: 0,
1459 resource: wgpu::BindingResource::TextureView(&view),
1460 },
1461 wgpu::BindGroupEntry {
1462 binding: 1,
1463 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1464 },
1465 ],
1466 });
1467
1468 let bytes = (w as u64) * (h as u64) * 4;
1469 self.image_bytes_total += bytes;
1470
1471 self.images.insert(
1472 handle,
1473 ImageTex::Rgba {
1474 tex,
1475 view,
1476 bind,
1477 w,
1478 h,
1479 format,
1480 last_used_frame: self.frame_index,
1481 bytes,
1482 },
1483 );
1484 }
1485
1486 let tex = match self.images.get(&handle) {
1487 Some(ImageTex::Rgba { tex, .. }) => tex,
1488 _ => unreachable!(),
1489 };
1490
1491 self.queue.write_texture(
1492 wgpu::TexelCopyTextureInfo {
1493 texture: tex,
1494 mip_level: 0,
1495 origin: wgpu::Origin3d::ZERO,
1496 aspect: wgpu::TextureAspect::All,
1497 },
1498 &rgba[..expected],
1499 wgpu::TexelCopyBufferLayout {
1500 offset: 0,
1501 bytes_per_row: Some(4 * w),
1502 rows_per_image: Some(h),
1503 },
1504 wgpu::Extent3d {
1505 width: w,
1506 height: h,
1507 depth_or_array_layers: 1,
1508 },
1509 );
1510
1511 self.evict_budget_excess();
1513
1514 Ok(())
1515 }
1516
1517 pub fn set_image_nv12(
1518 &mut self,
1519 handle: u64,
1520 w: u32,
1521 h: u32,
1522 y: &[u8],
1523 uv: &[u8],
1524 full_range: bool,
1525 ) -> anyhow::Result<()> {
1526 let y_expected = (w as usize) * (h as usize);
1527 let uv_w = (w / 2).max(1);
1528 let uv_h = (h / 2).max(1);
1529 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
1530
1531 if y.len() < y_expected {
1532 return Err(anyhow::anyhow!("Y plane too small"));
1533 }
1534 if uv.len() < uv_expected {
1535 return Err(anyhow::anyhow!("UV plane too small"));
1536 }
1537
1538 let needs_recreate = match self.images.get(&handle) {
1539 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
1540 _ => true,
1541 };
1542
1543 if needs_recreate {
1544 self.remove_image(handle);
1545
1546 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
1547 label: Some("nv12 Y"),
1548 size: wgpu::Extent3d {
1549 width: w,
1550 height: h,
1551 depth_or_array_layers: 1,
1552 },
1553 mip_level_count: 1,
1554 sample_count: 1,
1555 dimension: wgpu::TextureDimension::D2,
1556 format: wgpu::TextureFormat::R8Unorm,
1557 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1558 view_formats: &[],
1559 });
1560 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
1561
1562 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
1563 label: Some("nv12 UV"),
1564 size: wgpu::Extent3d {
1565 width: uv_w,
1566 height: uv_h,
1567 depth_or_array_layers: 1,
1568 },
1569 mip_level_count: 1,
1570 sample_count: 1,
1571 dimension: wgpu::TextureDimension::D2,
1572 format: wgpu::TextureFormat::Rg8Unorm,
1573 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1574 view_formats: &[],
1575 });
1576 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
1577
1578 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1579 label: Some("nv12 bind"),
1580 layout: &self.image_bind_layout_nv12,
1581 entries: &[
1582 wgpu::BindGroupEntry {
1583 binding: 0,
1584 resource: wgpu::BindingResource::TextureView(&view_y),
1585 },
1586 wgpu::BindGroupEntry {
1587 binding: 1,
1588 resource: wgpu::BindingResource::TextureView(&view_uv),
1589 },
1590 wgpu::BindGroupEntry {
1591 binding: 2,
1592 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1593 },
1594 ],
1595 });
1596
1597 let bytes = (w as u64) * (h as u64) + (uv_w as u64) * (uv_h as u64) * 2;
1598 self.image_bytes_total += bytes;
1599
1600 self.images.insert(
1601 handle,
1602 ImageTex::Nv12 {
1603 tex_y,
1604 view_y,
1605 tex_uv,
1606 view_uv,
1607 bind,
1608 w,
1609 h,
1610 full_range,
1611 last_used_frame: self.frame_index,
1612 bytes,
1613 },
1614 );
1615 }
1616
1617 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
1618 Some(ImageTex::Nv12 {
1619 tex_y,
1620 tex_uv,
1621 bind,
1622 ..
1623 }) => (tex_y, tex_uv, bind),
1624 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
1625 };
1626
1627 self.queue.write_texture(
1628 wgpu::TexelCopyTextureInfo {
1629 texture: tex_y,
1630 mip_level: 0,
1631 origin: wgpu::Origin3d::ZERO,
1632 aspect: wgpu::TextureAspect::All,
1633 },
1634 &y[..y_expected],
1635 wgpu::TexelCopyBufferLayout {
1636 offset: 0,
1637 bytes_per_row: Some(w),
1638 rows_per_image: Some(h),
1639 },
1640 wgpu::Extent3d {
1641 width: w,
1642 height: h,
1643 depth_or_array_layers: 1,
1644 },
1645 );
1646
1647 self.queue.write_texture(
1648 wgpu::TexelCopyTextureInfo {
1649 texture: tex_uv,
1650 mip_level: 0,
1651 origin: wgpu::Origin3d::ZERO,
1652 aspect: wgpu::TextureAspect::All,
1653 },
1654 &uv[..uv_expected],
1655 wgpu::TexelCopyBufferLayout {
1656 offset: 0,
1657 bytes_per_row: Some(2 * uv_w),
1658 rows_per_image: Some(uv_h),
1659 },
1660 wgpu::Extent3d {
1661 width: uv_w,
1662 height: uv_h,
1663 depth_or_array_layers: 1,
1664 },
1665 );
1666
1667 self.evict_budget_excess();
1668 Ok(())
1669 }
1670
1671 pub fn remove_image(&mut self, handle: u64) {
1672 if let Some(img) = self.images.remove(&handle) {
1673 let b = match img {
1674 ImageTex::Rgba { bytes, .. } => bytes,
1675 ImageTex::Nv12 { bytes, .. } => bytes,
1676 };
1677 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
1678 }
1679 }
1680
1681 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
1683 let handle = self.next_image_handle;
1684 self.next_image_handle += 1;
1685 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
1686 log::error!("Failed to register image: {e}");
1687 }
1688 handle
1689 }
1690
1691 fn evict_unused_images(&mut self) {
1692 let now = self.frame_index;
1693 let evict_after = self.image_evict_after_frames;
1694
1695 let mut to_remove = Vec::new();
1697 for (h, t) in self.images.iter() {
1698 let last = match t {
1699 ImageTex::Rgba {
1700 last_used_frame, ..
1701 } => *last_used_frame,
1702 ImageTex::Nv12 {
1703 last_used_frame, ..
1704 } => *last_used_frame,
1705 };
1706 if now.saturating_sub(last) > evict_after {
1707 to_remove.push(*h);
1708 }
1709 }
1710 for h in to_remove {
1711 self.remove_image(h);
1712 }
1713
1714 self.evict_budget_excess();
1715 }
1716
1717 fn evict_budget_excess(&mut self) {
1718 if self.image_bytes_total <= self.image_budget_bytes {
1719 return;
1720 }
1721 let mut candidates: Vec<(u64, u64, u64)> = self
1723 .images
1724 .iter()
1725 .map(|(h, t)| {
1726 let (last, bytes) = match t {
1727 ImageTex::Rgba {
1728 last_used_frame,
1729 bytes,
1730 ..
1731 } => (*last_used_frame, *bytes),
1732 ImageTex::Nv12 {
1733 last_used_frame,
1734 bytes,
1735 ..
1736 } => (*last_used_frame, *bytes),
1737 };
1738 (*h, last, bytes)
1739 })
1740 .collect();
1741
1742 candidates.sort_by_key(|k| k.1);
1744
1745 let now = self.frame_index;
1746 for (h, last, _bytes) in candidates {
1747 if self.image_bytes_total <= self.image_budget_bytes {
1748 break;
1749 }
1750 if last == now {
1752 continue;
1753 }
1754 self.remove_image(h);
1755 }
1756 }
1757
1758 fn recreate_msaa_and_depth_stencil(&mut self) {
1759 if self.msaa_samples > 1 {
1760 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1761 label: Some("msaa color"),
1762 size: wgpu::Extent3d {
1763 width: self.config.width.max(1),
1764 height: self.config.height.max(1),
1765 depth_or_array_layers: 1,
1766 },
1767 mip_level_count: 1,
1768 sample_count: self.msaa_samples,
1769 dimension: wgpu::TextureDimension::D2,
1770 format: self.config.format,
1771 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1772 view_formats: &[],
1773 });
1774 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1775 self.msaa_tex = Some(tex);
1776 self.msaa_view = Some(view);
1777 } else {
1778 self.msaa_tex = None;
1779 self.msaa_view = None;
1780 }
1781
1782 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1783 label: Some("depth-stencil (stencil clips)"),
1784 size: wgpu::Extent3d {
1785 width: self.config.width.max(1),
1786 height: self.config.height.max(1),
1787 depth_or_array_layers: 1,
1788 },
1789 mip_level_count: 1,
1790 sample_count: self.msaa_samples,
1791 dimension: wgpu::TextureDimension::D2,
1792 format: wgpu::TextureFormat::Depth24PlusStencil8,
1793 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1794 view_formats: &[],
1795 });
1796 self.depth_stencil_view = self
1797 .depth_stencil_tex
1798 .create_view(&wgpu::TextureViewDescriptor::default());
1799 }
1800
1801 fn init_atlas_mask(device: &wgpu::Device) -> anyhow::Result<AtlasA8> {
1802 let size = 1024u32;
1803 let tex = device.create_texture(&wgpu::TextureDescriptor {
1804 label: Some("glyph atlas A8"),
1805 size: wgpu::Extent3d {
1806 width: size,
1807 height: size,
1808 depth_or_array_layers: 1,
1809 },
1810 mip_level_count: 1,
1811 sample_count: 1,
1812 dimension: wgpu::TextureDimension::D2,
1813 format: wgpu::TextureFormat::R8Unorm,
1814 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1815 view_formats: &[],
1816 });
1817 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1818 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1819 label: Some("glyph atlas sampler A8"),
1820 address_mode_u: wgpu::AddressMode::ClampToEdge,
1821 address_mode_v: wgpu::AddressMode::ClampToEdge,
1822 address_mode_w: wgpu::AddressMode::ClampToEdge,
1823 mag_filter: wgpu::FilterMode::Linear,
1824 min_filter: wgpu::FilterMode::Linear,
1825 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1826 ..Default::default()
1827 });
1828
1829 Ok(AtlasA8 {
1830 tex,
1831 view,
1832 sampler,
1833 size,
1834 next_x: 1,
1835 next_y: 1,
1836 row_h: 0,
1837 map: HashMap::new(),
1838 })
1839 }
1840
1841 fn init_atlas_color(device: &wgpu::Device) -> anyhow::Result<AtlasRGBA> {
1842 let size = 1024u32;
1843 let tex = device.create_texture(&wgpu::TextureDescriptor {
1844 label: Some("glyph atlas RGBA"),
1845 size: wgpu::Extent3d {
1846 width: size,
1847 height: size,
1848 depth_or_array_layers: 1,
1849 },
1850 mip_level_count: 1,
1851 sample_count: 1,
1852 dimension: wgpu::TextureDimension::D2,
1853 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1854 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1855 view_formats: &[],
1856 });
1857 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1858 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1859 label: Some("glyph atlas sampler RGBA"),
1860 address_mode_u: wgpu::AddressMode::ClampToEdge,
1861 address_mode_v: wgpu::AddressMode::ClampToEdge,
1862 address_mode_w: wgpu::AddressMode::ClampToEdge,
1863 mag_filter: wgpu::FilterMode::Linear,
1864 min_filter: wgpu::FilterMode::Linear,
1865 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1866 ..Default::default()
1867 });
1868 Ok(AtlasRGBA {
1869 tex,
1870 view,
1871 sampler,
1872 size,
1873 next_x: 1,
1874 next_y: 1,
1875 row_h: 0,
1876 map: HashMap::new(),
1877 })
1878 }
1879
1880 fn get_or_create_layer(
1881 &mut self,
1882 layer_id: u32,
1883 width: u32,
1884 height: u32,
1885 rect: repose_core::Rect,
1886 ) {
1887 let needs_alloc = match self.layer_pool.get(&layer_id) {
1888 Some(lt) => lt.width != width || lt.height != height,
1889 None => true,
1890 };
1891 if !needs_alloc {
1892 return;
1893 }
1894 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1895 label: Some("graphics layer"),
1896 size: wgpu::Extent3d {
1897 width: width.max(1),
1898 height: height.max(1),
1899 depth_or_array_layers: 1,
1900 },
1901 mip_level_count: 1,
1902 sample_count: 1,
1903 dimension: wgpu::TextureDimension::D2,
1904 format: self.config.format,
1905 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1906 view_formats: &[],
1907 });
1908 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1909 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1910 label: Some("layer bind"),
1911 layout: &self.image_bind_layout_rgba,
1912 entries: &[
1913 wgpu::BindGroupEntry {
1914 binding: 0,
1915 resource: wgpu::BindingResource::TextureView(&view),
1916 },
1917 wgpu::BindGroupEntry {
1918 binding: 1,
1919 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1920 },
1921 ],
1922 });
1923 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1924 label: Some("graphics layer depth-stencil"),
1925 size: wgpu::Extent3d {
1926 width: width.max(1),
1927 height: height.max(1),
1928 depth_or_array_layers: 1,
1929 },
1930 mip_level_count: 1,
1931 sample_count: 1,
1932 dimension: wgpu::TextureDimension::D2,
1933 format: wgpu::TextureFormat::Depth24PlusStencil8,
1934 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1935 view_formats: &[],
1936 });
1937 let depth_stencil_view =
1938 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1939 self.layer_pool.insert(
1940 layer_id,
1941 LayerTarget {
1942 texture: tex,
1943 view,
1944 bind,
1945 depth_stencil_tex,
1946 depth_stencil_view,
1947 width,
1948 height,
1949 rect_px: (rect.x, rect.y, rect.w, rect.h),
1950 },
1951 );
1952 }
1953
1954 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
1955 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1956 label: Some("atlas bind"),
1957 layout: &self.text_bind_layout,
1958 entries: &[
1959 wgpu::BindGroupEntry {
1960 binding: 0,
1961 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
1962 },
1963 wgpu::BindGroupEntry {
1964 binding: 1,
1965 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
1966 },
1967 ],
1968 })
1969 }
1970
1971 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
1972 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1973 label: Some("atlas bind color"),
1974 layout: &self.text_bind_layout,
1975 entries: &[
1976 wgpu::BindGroupEntry {
1977 binding: 0,
1978 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
1979 },
1980 wgpu::BindGroupEntry {
1981 binding: 1,
1982 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
1983 },
1984 ],
1985 })
1986 }
1987
1988 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
1989 let keyp = (key, px);
1990 if let Some(info) = self.atlas_mask.map.get(&keyp) {
1991 return Some(*info);
1992 }
1993
1994 let gb = repose_text::rasterize(key, px as f32)?;
1995 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
1996 return None;
1997 }
1998
1999 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
2000
2001 let w = gb.w.max(1);
2002 let h = gb.h.max(1);
2003
2004 if !self.alloc_space_mask(w, h) {
2005 self.grow_mask_and_rebuild();
2006 }
2007 if !self.alloc_space_mask(w, h) {
2008 return None;
2009 }
2010 let x = self.atlas_mask.next_x;
2011 let y = self.atlas_mask.next_y;
2012 self.atlas_mask.next_x += w + 1;
2013 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
2014
2015 let layout = wgpu::TexelCopyBufferLayout {
2016 offset: 0,
2017 bytes_per_row: Some(w),
2018 rows_per_image: Some(h),
2019 };
2020 let size = wgpu::Extent3d {
2021 width: w,
2022 height: h,
2023 depth_or_array_layers: 1,
2024 };
2025 self.queue.write_texture(
2026 wgpu::TexelCopyTextureInfoBase {
2027 texture: &self.atlas_mask.tex,
2028 mip_level: 0,
2029 origin: wgpu::Origin3d { x, y, z: 0 },
2030 aspect: wgpu::TextureAspect::All,
2031 },
2032 &coverage,
2033 layout,
2034 size,
2035 );
2036
2037 let info = GlyphInfo {
2038 u0: x as f32 / self.atlas_mask.size as f32,
2039 v0: y as f32 / self.atlas_mask.size as f32,
2040 u1: (x + w) as f32 / self.atlas_mask.size as f32,
2041 v1: (y + h) as f32 / self.atlas_mask.size as f32,
2042 w: w as f32,
2043 h: h as f32,
2044 bearing_x: 0.0,
2045 bearing_y: 0.0,
2046 advance: 0.0,
2047 };
2048 self.atlas_mask.map.insert(keyp, info);
2049 Some(info)
2050 }
2051
2052 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
2053 let keyp = (key, px);
2054 if let Some(info) = self.atlas_color.map.get(&keyp) {
2055 return Some(*info);
2056 }
2057 let gb = repose_text::rasterize(key, px as f32)?;
2058 if !matches!(gb.content, cosmic_text::SwashContent::Color) {
2059 return None;
2060 }
2061 let w = gb.w.max(1);
2062 let h = gb.h.max(1);
2063 if !self.alloc_space_color(w, h) {
2064 self.grow_color_and_rebuild();
2065 }
2066 if !self.alloc_space_color(w, h) {
2067 return None;
2068 }
2069 let x = self.atlas_color.next_x;
2070 let y = self.atlas_color.next_y;
2071 self.atlas_color.next_x += w + 1;
2072 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
2073
2074 let layout = wgpu::TexelCopyBufferLayout {
2075 offset: 0,
2076 bytes_per_row: Some(w * 4),
2077 rows_per_image: Some(h),
2078 };
2079 let size = wgpu::Extent3d {
2080 width: w,
2081 height: h,
2082 depth_or_array_layers: 1,
2083 };
2084 self.queue.write_texture(
2085 wgpu::TexelCopyTextureInfoBase {
2086 texture: &self.atlas_color.tex,
2087 mip_level: 0,
2088 origin: wgpu::Origin3d { x, y, z: 0 },
2089 aspect: wgpu::TextureAspect::All,
2090 },
2091 &gb.data,
2092 layout,
2093 size,
2094 );
2095 let info = GlyphInfo {
2096 u0: x as f32 / self.atlas_color.size as f32,
2097 v0: y as f32 / self.atlas_color.size as f32,
2098 u1: (x + w) as f32 / self.atlas_color.size as f32,
2099 v1: (y + h) as f32 / self.atlas_color.size as f32,
2100 w: w as f32,
2101 h: h as f32,
2102 bearing_x: 0.0,
2103 bearing_y: 0.0,
2104 advance: 0.0,
2105 };
2106 self.atlas_color.map.insert(keyp, info);
2107 Some(info)
2108 }
2109
2110 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
2111 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
2112 self.atlas_mask.next_x = 1;
2113 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
2114 self.atlas_mask.row_h = 0;
2115 }
2116 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
2117 return false;
2118 }
2119 true
2120 }
2121
2122 fn grow_mask_and_rebuild(&mut self) {
2123 let new_size = (self.atlas_mask.size * 2).min(4096);
2124 if new_size == self.atlas_mask.size {
2125 return;
2126 }
2127 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2128 label: Some("glyph atlas A8 (grown)"),
2129 size: wgpu::Extent3d {
2130 width: new_size,
2131 height: new_size,
2132 depth_or_array_layers: 1,
2133 },
2134 mip_level_count: 1,
2135 sample_count: 1,
2136 dimension: wgpu::TextureDimension::D2,
2137 format: wgpu::TextureFormat::R8Unorm,
2138 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2139 view_formats: &[],
2140 });
2141 self.atlas_mask.tex = tex;
2142 self.atlas_mask.view = self
2143 .atlas_mask
2144 .tex
2145 .create_view(&wgpu::TextureViewDescriptor::default());
2146 self.atlas_mask.size = new_size;
2147 self.atlas_mask.next_x = 1;
2148 self.atlas_mask.next_y = 1;
2149 self.atlas_mask.row_h = 0;
2150 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
2151 self.atlas_mask.map.clear();
2152 for (k, px) in keys {
2153 let _ = self.upload_glyph_mask(k, px);
2154 }
2155 }
2156
2157 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
2158 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
2159 self.atlas_color.next_x = 1;
2160 self.atlas_color.next_y += self.atlas_color.row_h + 1;
2161 self.atlas_color.row_h = 0;
2162 }
2163 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
2164 return false;
2165 }
2166 true
2167 }
2168
2169 fn grow_color_and_rebuild(&mut self) {
2170 let new_size = (self.atlas_color.size * 2).min(4096);
2171 if new_size == self.atlas_color.size {
2172 return;
2173 }
2174 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2175 label: Some("glyph atlas RGBA (grown)"),
2176 size: wgpu::Extent3d {
2177 width: new_size,
2178 height: new_size,
2179 depth_or_array_layers: 1,
2180 },
2181 mip_level_count: 1,
2182 sample_count: 1,
2183 dimension: wgpu::TextureDimension::D2,
2184 format: wgpu::TextureFormat::Rgba8UnormSrgb,
2185 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2186 view_formats: &[],
2187 });
2188 self.atlas_color.tex = tex;
2189 self.atlas_color.view = self
2190 .atlas_color
2191 .tex
2192 .create_view(&wgpu::TextureViewDescriptor::default());
2193 self.atlas_color.size = new_size;
2194 self.atlas_color.next_x = 1;
2195 self.atlas_color.next_y = 1;
2196 self.atlas_color.row_h = 0;
2197 let keys: Vec<(repose_text::GlyphKey, u32)> =
2198 self.atlas_color.map.keys().copied().collect();
2199 self.atlas_color.map.clear();
2200 for (k, px) in keys {
2201 let _ = self.upload_glyph_color(k, px);
2202 }
2203 }
2204}
2205
2206fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
2207 match brush {
2208 Brush::Solid(c) => (
2209 0u32,
2210 c.to_linear(),
2211 [0.0, 0.0, 0.0, 0.0],
2212 [0.0, 0.0],
2213 [0.0, 1.0],
2214 ),
2215 Brush::Linear {
2216 start,
2217 end,
2218 start_color,
2219 end_color,
2220 } => (
2221 1u32,
2222 start_color.to_linear(),
2223 end_color.to_linear(),
2224 [start.x, start.y],
2225 [end.x, end.y],
2226 ),
2227 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
2228 }
2229}
2230
2231fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
2232 match brush {
2233 Brush::Solid(c) => c.to_linear(),
2234 Brush::Linear { start_color, .. } => start_color.to_linear(),
2235 _ => [0.0; 4],
2236 }
2237}
2238
2239impl RenderBackend for WgpuBackend {
2240 fn configure_surface(&mut self, width: u32, height: u32) {
2241 if width == 0 || height == 0 {
2242 return;
2243 }
2244 self.config.width = width;
2245 self.config.height = height;
2246 self.surface.configure(&self.device, &self.config);
2247 self.recreate_msaa_and_depth_stencil();
2248 }
2249
2250 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
2251 self.frame_index = self.frame_index.wrapping_add(1);
2253 self.slug_cache.next_frame();
2254
2255 if self.config.width == 0 || self.config.height == 0 {
2256 return;
2257 }
2258 let mut retries = 0u32;
2259 const MAX_RETRIES: u32 = 4;
2260 let frame = loop {
2261 match self.surface.get_current_texture() {
2262 wgpu::CurrentSurfaceTexture::Success(f) => break f,
2263 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
2264 log::warn!("suboptimal surface; reconfiguring");
2265 self.surface.configure(&self.device, &self.config);
2266 break f;
2267 }
2268 wgpu::CurrentSurfaceTexture::Outdated => {
2269 retries += 1;
2270 if retries >= MAX_RETRIES {
2271 log::warn!(
2272 "surface outdated persisted after {MAX_RETRIES} retries; skipping frame"
2273 );
2274 return;
2275 }
2276 log::warn!("surface outdated; reconfiguring");
2277 self.surface.configure(&self.device, &self.config);
2278 }
2279 wgpu::CurrentSurfaceTexture::Lost => {
2280 retries += 1;
2281 if retries >= MAX_RETRIES {
2282 log::warn!(
2283 "surface lost persisted after {MAX_RETRIES} retries; skipping frame"
2284 );
2285 return;
2286 }
2287 log::warn!("surface lost; reconfiguring");
2288 self.surface.configure(&self.device, &self.config);
2289 }
2290 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
2291 request_frame();
2292 return;
2293 }
2294 wgpu::CurrentSurfaceTexture::Validation => {
2295 retries += 1;
2296 if retries >= MAX_RETRIES {
2297 log::warn!(
2298 "surface validation persisted after {MAX_RETRIES} retries; skipping frame"
2299 );
2300 return;
2301 }
2302 self.surface.configure(&self.device, &self.config);
2303 }
2304 }
2305 };
2306
2307 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
2308 let x0 = (x / fb_w) * 2.0 - 1.0;
2309 let y0 = 1.0 - (y / fb_h) * 2.0;
2310 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
2311 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
2312 let min_x = x0.min(x1);
2313 let min_y = y0.min(y1);
2314 let w_ndc = (x1 - x0).abs();
2315 let h_ndc = (y1 - y0).abs();
2316 [min_x, min_y, w_ndc, h_ndc]
2317 }
2318
2319 fn rect_to_instance_ndc(
2321 rect: repose_core::Rect,
2322 transform: &Transform,
2323 fb_w: f32,
2324 fb_h: f32,
2325 ) -> ([f32; 4], [f32; 2]) {
2326 let cx = rect.x + rect.w * 0.5;
2327 let cy = rect.y + rect.h * 0.5;
2328
2329 let sx = cx * transform.scale_x;
2331 let sy = cy * transform.scale_y;
2332 let cos_a = transform.rotate.cos();
2333 let sin_a = transform.rotate.sin();
2334 let tx = sx * cos_a - sy * sin_a + transform.translate_x;
2335 let ty = sx * sin_a + sy * cos_a + transform.translate_y;
2336
2337 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
2339 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
2340 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
2342 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
2343
2344 ([ndc_cx, ndc_cy, ndc_w, ndc_h], [cos_a, sin_a])
2345 }
2346
2347 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
2348 let mut x = r.x.floor() as i64;
2349 let mut y = r.y.floor() as i64;
2350 let fb_wi = fb_w as i64;
2351 let fb_hi = fb_h as i64;
2352 x = x.clamp(0, fb_wi.saturating_sub(1));
2353 y = y.clamp(0, fb_hi.saturating_sub(1));
2354 let w_req = r.w.ceil().max(1.0) as i64;
2355 let h_req = r.h.ceil().max(1.0) as i64;
2356 let w = (w_req).min(fb_wi - x).max(1);
2357 let h = (h_req).min(fb_hi - y).max(1);
2358 (x as u32, y as u32, w as u32, h as u32)
2359 }
2360
2361 let fb_w = self.config.width as f32;
2362 let fb_h = self.config.height as f32;
2363
2364 let globals = Globals {
2365 ndc_to_px: [fb_w * 0.5, fb_h * 0.5],
2366 _pad: [0.0, 0.0],
2367 };
2368 self.queue
2369 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
2370
2371 let mut passes: Vec<Pass> = Vec::with_capacity(1);
2372 let mut current_pass: Pass = Pass {
2373 target: PassTarget::Surface,
2374 initial_scissor: (0, 0, self.config.width, self.config.height),
2375 clear_color: Some([
2376 scene.clear_color.0 as f32 / 255.0,
2377 scene.clear_color.1 as f32 / 255.0,
2378 scene.clear_color.2 as f32 / 255.0,
2379 scene.clear_color.3 as f32 / 255.0,
2380 ]),
2381 cmds: Vec::with_capacity(scene.nodes.len()),
2382 };
2383 let mut target_stack: Vec<PassTarget> = Vec::new();
2384 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
2385 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
2386
2387 struct Batch {
2388 rects: Vec<RectInstance>,
2389 borders: Vec<BorderInstance>,
2390 ellipses: Vec<EllipseInstance>,
2391 e_borders: Vec<EllipseBorderInstance>,
2392 masks: Vec<GlyphInstance>,
2393 colors: Vec<GlyphInstance>,
2394 nv12s: Vec<Nv12Instance>,
2395 }
2396
2397 impl Batch {
2398 fn new() -> Self {
2399 Self {
2400 rects: vec![],
2401 borders: vec![],
2402 ellipses: vec![],
2403 e_borders: vec![],
2404 masks: vec![],
2405 colors: vec![],
2406 nv12s: vec![],
2407 }
2408 }
2409
2410 fn is_empty(&self) -> bool {
2411 self.rects.is_empty()
2412 && self.borders.is_empty()
2413 && self.ellipses.is_empty()
2414 && self.e_borders.is_empty()
2415 && self.masks.is_empty()
2416 && self.colors.is_empty()
2417 && self.nv12s.is_empty()
2418 }
2419
2420 fn flush(
2421 &mut self,
2422 pipes: (
2423 &mut InstancedPipe<RectInstance>,
2424 &mut InstancedPipe<BorderInstance>,
2425 &mut InstancedPipe<EllipseInstance>,
2426 &mut InstancedPipe<EllipseBorderInstance>,
2427 ),
2428 glyph_pipes: (
2429 &mut InstancedPipe<GlyphInstance>,
2430 &mut InstancedPipe<GlyphInstance>,
2431 ),
2432 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
2433 device: &wgpu::Device,
2434 queue: &wgpu::Queue,
2435 cmds: &mut Vec<Cmd>,
2436 ) {
2437 let (rects, borders, ellipses, e_borders) = pipes;
2438 let (masks, colors) = glyph_pipes;
2439
2440 macro_rules! flush_one {
2441 ($buf:ident, $pipe:expr, $variant:ident) => {
2442 if !self.$buf.is_empty() {
2443 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
2444 cmds.push(Cmd::$variant { off, cnt });
2445 }
2446 self.$buf.clear();
2447 }
2448 };
2449 }
2450
2451 flush_one!(rects, rects, Rect);
2452 flush_one!(borders, borders, Border);
2453 flush_one!(ellipses, ellipses, Ellipse);
2454 flush_one!(e_borders, e_borders, EllipseBorder);
2455 flush_one!(masks, masks, GlyphsMask);
2456 flush_one!(colors, colors, GlyphsColor);
2457
2458 if !self.nv12s.is_empty() {
2459 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
2460 let _ = (off, cnt);
2461 }
2462 self.nv12s.clear();
2463 }
2464 }
2465 }
2466
2467 self.rects.reset();
2468 self.borders.reset();
2469 self.ellipses.reset();
2470 self.ellipse_borders.reset();
2471 self.glyph_mask.reset();
2472 self.glyph_color.reset();
2473 self.clip_ring.reset();
2474 self.blur_ring.reset();
2475 self.nv12.reset();
2476
2477 self.slug_ring.reset();
2478 self.slug_instances.clear();
2479 let mut batch = Batch::new();
2480 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
2481 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
2482 let root_clip_rect = repose_core::Rect {
2483 x: 0.0,
2484 y: 0.0,
2485 w: fb_w,
2486 h: fb_h,
2487 };
2488
2489 let mut current_prim: Option<&'static str> = None;
2490
2491 macro_rules! flush_if_prim_changed {
2492 ($prim:literal, $pipe:expr) => {
2493 if current_prim != Some($prim) {
2494 flush_batch!();
2495 current_prim = Some($prim);
2496 }
2497 };
2498 }
2499
2500 macro_rules! flush_batch {
2501 () => {
2502 if !batch.is_empty() {
2503 batch.flush(
2504 (
2505 &mut self.rects,
2506 &mut self.borders,
2507 &mut self.ellipses,
2508 &mut self.ellipse_borders,
2509 ),
2510 (&mut self.glyph_mask, &mut self.glyph_color),
2511 &mut self.nv12,
2512 &self.device,
2513 &self.queue,
2514 &mut current_pass.cmds,
2515 )
2516 }
2517 };
2518 }
2519
2520 for node in &scene.nodes {
2521 let t_identity = Transform::identity();
2522 let current_transform = transform_stack.last().unwrap_or(&t_identity);
2523
2524 match node {
2525 SceneNode::Rect {
2526 rect,
2527 brush,
2528 radius,
2529 } => {
2530 flush_if_prim_changed!("rect", &self.rects);
2531 let (ndc, sin_cos) = rect_to_instance_ndc(
2532 *rect,
2533 current_transform,
2534 current_target_size.0,
2535 current_target_size.1,
2536 );
2537 let (brush_type, color0, color1, grad_start, grad_end) =
2538 brush_to_instance_fields(brush);
2539 batch.rects.push(RectInstance {
2540 xywh: ndc,
2541 radius: *radius,
2542 brush_type,
2543 color0,
2544 color1,
2545 grad_start,
2546 grad_end,
2547 sin_cos,
2548 });
2549 }
2550 SceneNode::Border {
2551 rect,
2552 color,
2553 width,
2554 radius,
2555 } => {
2556 flush_if_prim_changed!("border", &self.borders);
2557 let (ndc, sin_cos) = rect_to_instance_ndc(
2558 *rect,
2559 current_transform,
2560 current_target_size.0,
2561 current_target_size.1,
2562 );
2563 batch.borders.push(BorderInstance {
2564 xywh: ndc,
2565 radius: *radius,
2566 stroke: *width,
2567 color: color.to_linear(),
2568 sin_cos,
2569 });
2570 }
2571 SceneNode::Ellipse { rect, brush } => {
2572 flush_if_prim_changed!("ellipse", &self.ellipses);
2573 let (ndc, sin_cos) = rect_to_instance_ndc(
2574 *rect,
2575 current_transform,
2576 current_target_size.0,
2577 current_target_size.1,
2578 );
2579 let color = brush_to_solid_color(brush);
2580 batch.ellipses.push(EllipseInstance {
2581 xywh: ndc,
2582 color,
2583 sin_cos,
2584 });
2585 }
2586 SceneNode::EllipseBorder { rect, color, width } => {
2587 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
2588 let (ndc, sin_cos) = rect_to_instance_ndc(
2589 *rect,
2590 current_transform,
2591 current_target_size.0,
2592 current_target_size.1,
2593 );
2594 batch.e_borders.push(EllipseBorderInstance {
2595 xywh: ndc,
2596 stroke: *width,
2597 color: color.to_linear(),
2598 sin_cos,
2599 });
2600 }
2601 SceneNode::Text {
2602 rect,
2603 text,
2604 color,
2605 size,
2606 font_family,
2607 } => {
2608 flush_batch!(); let px = (*size).clamp(8.0, 96.0);
2611 let shaped = repose_text::shape_line(text.as_ref(), px, *font_family);
2612
2613 let cos_a = current_transform.rotate.cos();
2614 let sin_a = current_transform.rotate.sin();
2615 let has_rotation = current_transform.rotate != 0.0;
2616
2617 let pivot_x = rect.x + rect.w * 0.5;
2619 let pivot_y = rect.y + rect.h * 0.5;
2620
2621 let make_glyph_instance =
2623 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 2]) {
2624 if has_rotation {
2625 let corners =
2626 [(gx, gy), (gx + gw, gy), (gx + gw, gy + gh), (gx, gy + gh)];
2627 let mut min_x = f32::MAX;
2628 let mut max_x = f32::MIN;
2629 let mut min_y = f32::MAX;
2630 let mut max_y = f32::MIN;
2631 for &(x, y) in &corners {
2632 let dx = x - pivot_x;
2633 let dy = y - pivot_y;
2634 let rx = pivot_x + dx * cos_a - dy * sin_a;
2635 let ry = pivot_y + dx * sin_a + dy * cos_a;
2636 min_x = min_x.min(rx);
2637 max_x = max_x.max(rx);
2638 min_y = min_y.min(ry);
2639 max_y = max_y.max(ry);
2640 }
2641 let bb_w = max_x - min_x;
2642 let bb_h = max_y - min_y;
2643 let ndc_tl = to_ndc(
2644 min_x,
2645 min_y,
2646 bb_w,
2647 bb_h,
2648 current_target_size.0,
2649 current_target_size.1,
2650 );
2651 let ndc = [
2652 ndc_tl[0] + ndc_tl[2] * 0.5,
2653 ndc_tl[1] + ndc_tl[3] * 0.5,
2654 ndc_tl[2],
2655 ndc_tl[3],
2656 ];
2657 (ndc, [cos_a, sin_a])
2658 } else {
2659 rect_to_instance_ndc(
2660 repose_core::Rect {
2661 x: gx,
2662 y: gy,
2663 w: gw,
2664 h: gh,
2665 },
2666 current_transform,
2667 current_target_size.0,
2668 current_target_size.1,
2669 )
2670 }
2671 };
2672
2673 for sg in shaped {
2674 let gx = rect.x + sg.x + sg.bearing_x;
2675 let gy = rect.y + sg.y - sg.bearing_y;
2676
2677 if self.slug_pipeline.is_some() {
2679 {
2680 let ck = repose_text::lookup_cache_key(sg.key);
2681 if let Some(ref ck) = ck {
2682 if !self.slug_cache.contains(ck) {
2683 if let Some((ck2, commands)) =
2685 repose_text::lookup_and_extract_outline(sg.key)
2686 {
2687 let font_size_px = f32::from_bits(ck2.font_size_bits);
2688 self.slug_cache.get_or_insert(
2689 ck2,
2690 font_size_px,
2691 &commands,
2692 );
2693 }
2694 }
2695 }
2696 if let Some(ref ck) = ck {
2697 self.slug_cache.touch(ck);
2698 }
2699 if let Some(entry) =
2700 ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
2701 {
2702 let ox = rect.x + sg.x;
2703 let oy = rect.y + sg.y;
2704 let scx = current_transform.scale_x;
2705 let scy = current_transform.scale_y;
2706 let ttx = current_transform.translate_x;
2707 let tty = current_transform.translate_y;
2708
2709 let tf = |x: f32, y: f32| -> (f32, f32) {
2710 let sx = x * scx;
2711 let sy = y * scy;
2712 (
2713 sx * cos_a - sy * sin_a + ttx,
2714 sx * sin_a + sy * cos_a + tty,
2715 )
2716 };
2717
2718 let (tox, toy) = tf(ox, oy);
2719 let (tpx, tpy) = tf(pivot_x, pivot_y);
2720
2721 let b = &entry.band_data.bounds;
2722 let corners = [
2724 tf(ox + b[0] * px, oy - b[3] * px),
2725 tf(ox + b[2] * px, oy - b[3] * px),
2726 tf(ox + b[2] * px, oy - b[1] * px),
2727 tf(ox + b[0] * px, oy - b[1] * px),
2728 ];
2729 let (mut min_x, mut max_x, mut min_y, mut max_y) =
2730 (f32::MAX, f32::MIN, f32::MAX, f32::MIN);
2731 for &(x, y) in &corners {
2732 min_x = min_x.min(x);
2733 max_x = max_x.max(x);
2734 min_y = min_y.min(y);
2735 max_y = max_y.max(y);
2736 }
2737 let bb_w = max_x - min_x;
2738 let bb_h = max_y - min_y;
2739 let ndc_tl = to_ndc(
2740 min_x,
2741 min_y,
2742 bb_w,
2743 bb_h,
2744 current_target_size.0,
2745 current_target_size.1,
2746 );
2747 self.slug_instances.push(slug::SlugVertex {
2748 center: [
2749 ndc_tl[0] + ndc_tl[2] * 0.5,
2750 ndc_tl[1] + ndc_tl[3] * 0.5,
2751 ],
2752 size: [ndc_tl[2], ndc_tl[3]],
2753 color: color.to_linear(),
2754 glyph_base: entry.gpu_offset,
2755 origin_px: [tox, toy],
2756 pivot_px: [tpx, tpy],
2757 cos_sin: [cos_a, sin_a],
2758 });
2759 continue;
2760 }
2761 }
2762 }
2763
2764 if let Some(info) = self.upload_glyph_color(sg.key, px as u32) {
2766 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
2767 batch.colors.push(GlyphInstance {
2768 xywh: ndc,
2769 uv: [info.u0, info.v1, info.u1, info.v0],
2770 color: color.to_linear(),
2771 sin_cos,
2772 });
2773 } else if let Some(info) = self.upload_glyph_mask(sg.key, px as u32) {
2774 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
2775 batch.masks.push(GlyphInstance {
2776 xywh: ndc,
2777 uv: [info.u0, info.v1, info.u1, info.v0],
2778 color: color.to_linear(),
2779 sin_cos,
2780 });
2781 }
2782 }
2783
2784 if !self.slug_instances.is_empty() {
2786 let bytes = bytemuck::cast_slice(&self.slug_instances);
2787 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
2788 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
2789 current_pass.cmds.push(Cmd::GlyphsVector {
2790 off,
2791 cnt: self.slug_instances.len() as u32,
2792 });
2793 self.slug_instances.clear();
2794 }
2795 }
2796 SceneNode::Image {
2797 rect,
2798 handle,
2799 tint,
2800 fit,
2801 } => {
2802 flush_batch!();
2803
2804 let (img_w, img_h, is_nv12) = if let Some(t) = self.images.get_mut(handle) {
2806 match t {
2807 ImageTex::Rgba {
2808 w,
2809 h,
2810 last_used_frame,
2811 ..
2812 } => {
2813 *last_used_frame = self.frame_index;
2814 (*w, *h, false)
2815 }
2816 ImageTex::Nv12 {
2817 w,
2818 h,
2819 last_used_frame,
2820 ..
2821 } => {
2822 *last_used_frame = self.frame_index;
2823 (*w, *h, true)
2824 }
2825 }
2826 } else {
2827 log::warn!("Image handle {} not found", handle);
2828 continue;
2829 };
2830
2831 let src_w = img_w as f32;
2832 let src_h = img_h as f32;
2833 let transformed = current_transform.apply_to_rect(*rect);
2834 let dst_w = transformed.w.max(0.0);
2835 let dst_h = transformed.h.max(0.0);
2836 if dst_w <= 0.0 || dst_h <= 0.0 {
2837 continue;
2838 }
2839
2840 let (xywh_ndc, uv_rect) = match fit {
2841 repose_core::view::ImageFit::Contain => {
2842 let scale = (dst_w / src_w).min(dst_h / src_h);
2843 let w = src_w * scale;
2844 let h = src_h * scale;
2845 let x = transformed.x + (dst_w - w) * 0.5;
2846 let y = transformed.y + (dst_h - h) * 0.5;
2847 (
2848 to_ndc(x, y, w, h, current_target_size.0, current_target_size.1),
2849 [0.0, 1.0, 1.0, 0.0],
2850 )
2851 }
2852 repose_core::view::ImageFit::Cover => {
2853 let scale = (dst_w / src_w).max(dst_h / src_h);
2854 let content_w = src_w * scale;
2855 let content_h = src_h * scale;
2856 let overflow_x = (content_w - dst_w) * 0.5;
2857 let overflow_y = (content_h - dst_h) * 0.5;
2858 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
2859 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
2860 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
2861 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
2862 (
2863 to_ndc(
2864 transformed.x,
2865 transformed.y,
2866 dst_w,
2867 dst_h,
2868 current_target_size.0,
2869 current_target_size.1,
2870 ),
2871 [u0, 1.0 - v1, u1, 1.0 - v0],
2872 )
2873 }
2874 repose_core::view::ImageFit::FitWidth => {
2875 let scale = dst_w / src_w;
2876 let w = dst_w;
2877 let h = src_h * scale;
2878 let y = transformed.y + (dst_h - h) * 0.5;
2879 (
2880 to_ndc(
2881 transformed.x,
2882 y,
2883 w,
2884 h,
2885 current_target_size.0,
2886 current_target_size.1,
2887 ),
2888 [0.0, 1.0, 1.0, 0.0],
2889 )
2890 }
2891 repose_core::view::ImageFit::FitHeight => {
2892 let scale = dst_h / src_h;
2893 let w = src_w * scale;
2894 let h = dst_h;
2895 let x = transformed.x + (dst_w - w) * 0.5;
2896 (
2897 to_ndc(
2898 x,
2899 transformed.y,
2900 w,
2901 h,
2902 current_target_size.0,
2903 current_target_size.1,
2904 ),
2905 [0.0, 1.0, 1.0, 0.0],
2906 )
2907 }
2908 _ => ([0.0; 4], [0.0; 4]),
2909 };
2910
2911 let ndc_center = [
2913 xywh_ndc[0] + xywh_ndc[2] * 0.5,
2914 xywh_ndc[1] + xywh_ndc[3] * 0.5,
2915 xywh_ndc[2],
2916 xywh_ndc[3],
2917 ];
2918
2919 if is_nv12 {
2920 let full_range = if let Some(ImageTex::Nv12 { full_range, .. }) =
2921 self.images.get(handle)
2922 {
2923 if *full_range { 1.0 } else { 0.0 }
2924 } else {
2925 0.0
2926 };
2927
2928 let inst = Nv12Instance {
2929 xywh: ndc_center,
2930 uv: uv_rect,
2931 color: tint.to_linear(),
2932 full_range,
2933 sin_cos: [1.0, 0.0],
2934 _pad: [0.0],
2935 };
2936 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
2937 {
2938 current_pass.cmds.push(Cmd::ImageNv12 {
2939 off,
2940 cnt: 1,
2941 handle: *handle,
2942 });
2943 }
2944 } else {
2945 let inst = GlyphInstance {
2947 xywh: ndc_center,
2948 uv: uv_rect,
2949 color: tint.to_linear(),
2950 sin_cos: [1.0, 0.0],
2951 };
2952 if let Some((off, _)) =
2953 self.glyph_color.upload(&self.device, &self.queue, &[inst])
2954 {
2955 current_pass.cmds.push(Cmd::ImageRgba {
2956 off,
2957 cnt: 1,
2958 handle: *handle,
2959 });
2960 }
2961 }
2962 }
2963 SceneNode::PushClip { rect, radius } => {
2964 flush_batch!(); let t_identity = Transform::identity();
2967 let current_transform = transform_stack.last().unwrap_or(&t_identity);
2968 let transformed = current_transform.apply_to_rect(*rect);
2969
2970 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
2971 let next_scissor = intersect(top, transformed);
2972 scissor_stack.push(next_scissor);
2973 let scissor = to_scissor(
2974 &next_scissor,
2975 current_target_size.0 as u32,
2976 current_target_size.1 as u32,
2977 );
2978
2979 let clip_ndc_tl = to_ndc(
2980 transformed.x,
2981 transformed.y,
2982 transformed.w,
2983 transformed.h,
2984 current_target_size.0,
2985 current_target_size.1,
2986 );
2987 let inst = ClipInstance {
2988 xywh: [
2989 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
2990 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
2991 clip_ndc_tl[2],
2992 clip_ndc_tl[3],
2993 ],
2994 radius: *radius,
2995 sin_cos: [1.0, 0.0],
2996 };
2997 let bytes = bytemuck::bytes_of(&inst);
2998 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
2999 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
3000
3001 current_pass.cmds.push(Cmd::ClipPush {
3002 off,
3003 cnt: 1,
3004 scissor,
3005 });
3006 }
3007 SceneNode::PopClip => {
3008 flush_batch!();
3009
3010 if !scissor_stack.is_empty() {
3011 scissor_stack.pop();
3012 } else {
3013 log::warn!("PopClip with empty stack");
3014 }
3015
3016 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
3017 let scissor = to_scissor(
3018 &top,
3019 current_target_size.0 as u32,
3020 current_target_size.1 as u32,
3021 );
3022 current_pass.cmds.push(Cmd::ClipPop { scissor });
3023 }
3024 SceneNode::Shadow {
3025 rect,
3026 radius,
3027 elevation: _,
3028 color,
3029 } => {
3030 flush_if_prim_changed!("rect", &self.rects);
3031 let (ndc, sin_cos) = rect_to_instance_ndc(
3032 *rect,
3033 current_transform,
3034 current_target_size.0,
3035 current_target_size.1,
3036 );
3037 let (brush_type, color0, _color1, _grad_start, _grad_end) =
3038 brush_to_instance_fields(&Brush::Solid(*color));
3039 batch.rects.push(RectInstance {
3040 xywh: ndc,
3041 radius: *radius,
3042 brush_type,
3043 color0,
3044 color1: [0.0; 4],
3045 grad_start: [0.0; 2],
3046 grad_end: [0.0; 2],
3047 sin_cos,
3048 });
3049 }
3050 SceneNode::PushTransform { transform } => {
3051 flush_batch!(); let combined = current_transform.combine(transform);
3053 transform_stack.push(combined);
3054 }
3055 SceneNode::PopTransform => {
3056 flush_batch!(); transform_stack.pop();
3058 }
3059 SceneNode::BeginLayer {
3060 rect,
3061 layer_id,
3062 alpha,
3063 } => {
3064 flush_batch!();
3065 let w = (rect.w.max(1.0)).ceil() as u32;
3066 let h = (rect.h.max(1.0)).ceil() as u32;
3067 let prev_target = current_pass.target;
3069 let prev_scissor = current_pass.initial_scissor;
3070 let saved = std::mem::replace(
3071 &mut current_pass,
3072 Pass {
3073 target: PassTarget::Layer(*layer_id),
3074 initial_scissor: (0, 0, w, h),
3075 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
3076 cmds: Vec::new(),
3077 },
3078 );
3079 passes.push(saved);
3080 target_stack.push(prev_target);
3081 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
3085 current_target_size = (w as f32, h as f32);
3086 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
3087 }
3088 SceneNode::EndLayer { layer_id } => {
3089 flush_batch!();
3090 let saved = std::mem::replace(
3092 &mut current_pass,
3093 Pass {
3094 target: target_stack.pop().unwrap_or(PassTarget::Surface),
3095 initial_scissor: (0, 0, self.config.width, self.config.height),
3096 clear_color: None, cmds: Vec::new(),
3098 },
3099 );
3100 passes.push(saved);
3101 current_target_size = (fb_w, fb_h);
3102 if let Some((_, layer_alpha, _)) = layer_alphas
3104 .iter()
3105 .find(|(id, _, _)| id == layer_id)
3106 .copied()
3107 {
3108 let layer = self.layer_pool.get(layer_id).expect("layer target");
3109 let ndc_tl = to_ndc(
3110 layer.rect_px.0,
3111 layer.rect_px.1,
3112 layer.rect_px.2,
3113 layer.rect_px.3,
3114 fb_w,
3115 fb_h,
3116 );
3117 let inst = GlyphInstance {
3118 xywh: [
3119 ndc_tl[0] + ndc_tl[2] * 0.5,
3120 ndc_tl[1] + ndc_tl[3] * 0.5,
3121 ndc_tl[2],
3122 ndc_tl[3],
3123 ],
3124 uv: [0.0, 1.0, 1.0, 0.0],
3125 color: [1.0, 1.0, 1.0, layer_alpha],
3126 sin_cos: [1.0, 0.0],
3127 };
3128 if let Some((off, cnt)) =
3129 self.glyph_color.upload(&self.device, &self.queue, &[inst])
3130 {
3131 current_pass.cmds.push(Cmd::CompositeLayer {
3132 off,
3133 cnt,
3134 layer_id: *layer_id,
3135 alpha: layer_alpha,
3136 });
3137 }
3138 }
3139 }
3140 SceneNode::CompositeShadow {
3141 layer_id,
3142 blur_px,
3143 offset_px,
3144 color,
3145 } => {
3146 flush_batch!();
3147 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
3148 let sx = layer.rect_px.0 + offset_px.0;
3150 let sy = layer.rect_px.1 + offset_px.1;
3151 let sw = layer.rect_px.2;
3152 let sh = layer.rect_px.3;
3153 let bw_uv = (blur_px * 1.5) / layer.width.max(1) as f32;
3156 let bh_uv = (blur_px * 1.5) / layer.height.max(1) as f32;
3157 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
3158 let inst = BlurInstance {
3159 xywh: [
3160 ndc_tl[0] + ndc_tl[2] * 0.5,
3161 ndc_tl[1] + ndc_tl[3] * 0.5,
3162 ndc_tl[2],
3163 ndc_tl[3],
3164 ],
3165 uv: [0.0, 0.0, 1.0, 1.0],
3166 color: [
3167 color.0 as f32 / 255.0,
3168 color.1 as f32 / 255.0,
3169 color.2 as f32 / 255.0,
3170 color.3 as f32 / 255.0,
3171 ],
3172 blur_uv: [bw_uv, bh_uv],
3173 sin_cos: [1.0, 0.0],
3174 };
3175 self.blur_ring
3176 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
3177 let bytes = bytemuck::bytes_of(&inst);
3178 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
3179 current_pass.cmds.push(Cmd::CompositeShadow {
3180 off,
3181 cnt: 1,
3182 layer_id: *layer_id,
3183 });
3184 }
3185 }
3186 _ => {}
3187 }
3188 }
3189
3190 flush_batch!();
3191
3192 passes.push(current_pass);
3194
3195 if let Some(ref mut slug) = self.slug_pipeline {
3196 slug.upload(
3197 &self.device,
3198 &self.queue,
3199 &mut self.slug_cache,
3200 &mut self.slug_prev_generation,
3201 );
3202 }
3203
3204 let mut encoder = self
3205 .device
3206 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3207 label: Some("frame encoder"),
3208 });
3209
3210 let bind_mask = self.atlas_bind_group_mask();
3211 let bind_color = self.atlas_bind_group_color();
3212 let mut clip_depth: u32 = 0;
3213
3214 for pass in std::mem::take(&mut passes) {
3215 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
3216 PassTarget::Surface => {
3217 let swap_view = frame
3218 .texture
3219 .create_view(&wgpu::TextureViewDescriptor::default());
3220 let (color, resolve) = if let Some(msaa_view) = &self.msaa_view {
3221 (msaa_view.clone(), Some(swap_view))
3222 } else {
3223 (swap_view, None)
3224 };
3225 (color, resolve, self.depth_stencil_view.clone(), false)
3226 }
3227 PassTarget::Layer(layer_id) => {
3228 if let Some(lt) = self.layer_pool.get(&layer_id) {
3229 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
3230 } else {
3231 log::warn!("missing layer target {layer_id}");
3232 continue;
3233 }
3234 }
3235 };
3236
3237 if is_layer {
3238 clip_depth = 0;
3239 }
3240
3241 let pipes: &Pipelines = if is_layer {
3242 &self.layer_pipes
3243 } else {
3244 &self.surface_pipes
3245 };
3246
3247 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3248 label: Some("pass"),
3249 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3250 view: &color_view,
3251 resolve_target: resolve_target.as_ref(),
3252 ops: wgpu::Operations {
3253 load: match pass.clear_color {
3254 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
3255 r: c[0] as f64,
3256 g: c[1] as f64,
3257 b: c[2] as f64,
3258 a: c[3] as f64,
3259 }),
3260 None => wgpu::LoadOp::Load,
3261 },
3262 store: wgpu::StoreOp::Store,
3263 },
3264 depth_slice: None,
3265 })],
3266 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
3267 view: &depth_stencil_view,
3268 depth_ops: None,
3269 stencil_ops: Some(wgpu::Operations {
3270 load: if is_layer || pass.clear_color.is_some() {
3271 wgpu::LoadOp::Clear(0)
3272 } else {
3273 wgpu::LoadOp::Load
3274 },
3275 store: wgpu::StoreOp::Store,
3276 }),
3277 }),
3278 timestamp_writes: None,
3279 occlusion_query_set: None,
3280 multiview_mask: None,
3281 });
3282
3283 rpass.set_bind_group(0, &self.globals_bind, &[]);
3284 rpass.set_stencil_reference(clip_depth);
3285 rpass.set_scissor_rect(
3286 pass.initial_scissor.0,
3287 pass.initial_scissor.1,
3288 pass.initial_scissor.2,
3289 pass.initial_scissor.3,
3290 );
3291
3292 macro_rules! draw_simple {
3293 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
3294 rpass.set_pipeline($pipeline);
3295 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
3296 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
3297 rpass.draw(0..6, 0..$n);
3298 }};
3299 }
3300
3301 macro_rules! draw_with_bind {
3302 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
3303 rpass.set_pipeline($pipeline);
3304 rpass.set_bind_group(1, $bind, &[]);
3305 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
3306 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
3307 rpass.draw(0..6, 0..$n);
3308 }};
3309 }
3310
3311 for cmd in pass.cmds {
3312 match cmd {
3313 Cmd::ClipPush {
3314 off,
3315 cnt: n,
3316 scissor,
3317 } => {
3318 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
3319 rpass.set_stencil_reference(clip_depth);
3320
3321 if self.msaa_samples > 1 && !is_layer {
3322 rpass.set_pipeline(&pipes.clip_a2c);
3323 } else {
3324 rpass.set_pipeline(&pipes.clip_bin);
3325 }
3326
3327 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
3328 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
3329 rpass.draw(0..6, 0..n);
3330
3331 clip_depth = (clip_depth + 1).min(255);
3332 rpass.set_stencil_reference(clip_depth);
3333 }
3334
3335 Cmd::ClipPop { scissor } => {
3336 clip_depth = clip_depth.saturating_sub(1);
3337 rpass.set_stencil_reference(clip_depth);
3338 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
3339 }
3340
3341 Cmd::Rect { off, cnt: n } => {
3342 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
3343 }
3344
3345 Cmd::Border { off, cnt: n } => {
3346 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
3347 }
3348
3349 Cmd::GlyphsMask { off, cnt: n } => {
3350 draw_with_bind!(
3351 &pipes.text_mask,
3352 self.glyph_mask.ring,
3353 GlyphInstance,
3354 &bind_mask,
3355 off,
3356 n
3357 );
3358 }
3359
3360 Cmd::GlyphsColor { off, cnt: n } => {
3361 draw_with_bind!(
3362 &pipes.text_color,
3363 self.glyph_color.ring,
3364 GlyphInstance,
3365 &bind_color,
3366 off,
3367 n
3368 );
3369 }
3370
3371 Cmd::GlyphsVector { off, cnt: n } => {
3372 if let (Some(ref slug_pipe), Some(ref slug_backend)) =
3373 (pipes.slug.as_ref(), self.slug_pipeline.as_ref())
3374 {
3375 rpass.set_pipeline(slug_pipe);
3376 rpass.set_bind_group(1, slug_backend.bind_group(), &[]);
3377 let bytes = (n as u64) * std::mem::size_of::<slug::SlugVertex>() as u64;
3378 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
3379 rpass.draw(0..6, 0..n);
3380 }
3381 }
3382
3383 Cmd::ImageRgba {
3384 off,
3385 cnt: n,
3386 handle,
3387 } => {
3388 if let Some(ImageTex::Rgba { bind, .. }) = self.images.get(&handle) {
3389 draw_with_bind!(
3390 &pipes.image_rgba,
3391 self.glyph_color.ring,
3392 GlyphInstance,
3393 bind,
3394 off,
3395 n
3396 );
3397 }
3398 }
3399
3400 Cmd::ImageNv12 {
3401 off,
3402 cnt: n,
3403 handle,
3404 } => {
3405 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
3406 draw_with_bind!(
3407 &pipes.image_nv12,
3408 self.nv12.ring,
3409 Nv12Instance,
3410 bind,
3411 off,
3412 n
3413 );
3414 }
3415 }
3416
3417 Cmd::Ellipse { off, cnt: n } => {
3418 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
3419 }
3420
3421 Cmd::EllipseBorder { off, cnt: n } => {
3422 draw_simple!(
3423 &pipes.ellipse_borders,
3424 self.ellipse_borders.ring,
3425 EllipseBorderInstance,
3426 off,
3427 n
3428 );
3429 }
3430
3431 Cmd::PushTransform(_) => {}
3432 Cmd::PopTransform => {}
3433 Cmd::CompositeLayer {
3434 off,
3435 cnt: n,
3436 layer_id,
3437 alpha: _,
3438 } => {
3439 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
3440 draw_with_bind!(
3441 &pipes.image_rgba,
3442 self.glyph_color.ring,
3443 GlyphInstance,
3444 <.bind,
3445 off,
3446 n
3447 );
3448 }
3449 }
3450 Cmd::CompositeShadow {
3451 off,
3452 cnt: n,
3453 layer_id,
3454 } => {
3455 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
3456 draw_with_bind!(
3457 &pipes.blur,
3458 self.blur_ring,
3459 BlurInstance,
3460 <.bind,
3461 off,
3462 n
3463 );
3464 }
3465 }
3466 }
3467 }
3468 }
3469
3470 self.queue.submit(std::iter::once(encoder.finish()));
3471 if let Err(e) = catch_unwind(AssertUnwindSafe(|| frame.present())) {
3472 log::warn!("frame.present panicked: {:?}", e);
3473 }
3474
3475 self.evict_unused_images();
3477 }
3478}
3479
3480fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
3481 let x0 = a.x.max(b.x);
3482 let y0 = a.y.max(b.y);
3483 let x1 = (a.x + a.w).min(b.x + b.w);
3484 let y1 = (a.y + a.h).min(b.y + b.h);
3485 repose_core::Rect {
3486 x: x0,
3487 y: y0,
3488 w: (x1 - x0).max(0.0),
3489 h: (y1 - y0).max(0.0),
3490 }
3491}