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: 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: 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: &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::create_pipeline(
703 device,
704 format,
705 sample_count,
706 globals_layout,
707 slug_storage_layout,
708 stencil_for_content,
709 );
710
711 Self {
712 rects,
713 borders,
714 ellipses,
715 ellipse_borders,
716 text_mask,
717 text_color,
718 image_rgba,
719 image_nv12,
720 blur,
721 clip_a2c,
722 clip_bin,
723 slug,
724 }
725 }
726}
727
728struct Pass {
730 target: PassTarget,
731 initial_scissor: (u32, u32, u32, u32),
733 clear_color: Option<[f32; 4]>,
736 cmds: Vec<Cmd>,
737}
738
739#[allow(non_snake_case)]
740enum Cmd {
741 ClipPush {
742 off: u64,
743 cnt: u32,
744 scissor: (u32, u32, u32, u32),
745 },
746 ClipPop {
747 scissor: (u32, u32, u32, u32),
748 },
749 Rect {
750 off: u64,
751 cnt: u32,
752 },
753 Border {
754 off: u64,
755 cnt: u32,
756 },
757 Ellipse {
758 off: u64,
759 cnt: u32,
760 },
761 EllipseBorder {
762 off: u64,
763 cnt: u32,
764 },
765 GlyphsMask {
766 off: u64,
767 cnt: u32,
768 },
769 GlyphsColor {
770 off: u64,
771 cnt: u32,
772 },
773 GlyphsVector {
774 off: u64,
775 cnt: u32,
776 },
777 ImageRgba {
778 off: u64,
779 cnt: u32,
780 handle: u64,
781 },
782 ImageNv12 {
783 off: u64,
784 cnt: u32,
785 handle: u64,
786 },
787 PushTransform(Transform),
788 PopTransform,
789 CompositeLayer {
793 off: u64,
794 cnt: u32,
795 layer_id: u32,
796 alpha: f32,
797 },
798 CompositeShadow {
802 off: u64,
803 cnt: u32,
804 layer_id: u32,
805 },
806}
807
808enum ImageTex {
809 Rgba {
810 tex: wgpu::Texture,
811 view: wgpu::TextureView,
812 bind: wgpu::BindGroup,
813 w: u32,
814 h: u32,
815 format: wgpu::TextureFormat,
816 last_used_frame: u64,
817 bytes: u64,
818 },
819 Nv12 {
820 tex_y: wgpu::Texture,
821 view_y: wgpu::TextureView,
822 tex_uv: wgpu::Texture,
823 view_uv: wgpu::TextureView,
824 bind: wgpu::BindGroup,
825 w: u32,
826 h: u32,
827 full_range: bool,
828 last_used_frame: u64,
829 bytes: u64,
830 },
831}
832
833struct AtlasA8 {
834 tex: wgpu::Texture,
835 view: wgpu::TextureView,
836 sampler: wgpu::Sampler,
837 size: u32,
838 next_x: u32,
839 next_y: u32,
840 row_h: u32,
841 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
842}
843
844struct AtlasRGBA {
845 tex: wgpu::Texture,
846 view: wgpu::TextureView,
847 sampler: wgpu::Sampler,
848 size: u32,
849 next_x: u32,
850 next_y: u32,
851 row_h: u32,
852 map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
853}
854
855#[derive(Clone, Copy)]
856struct GlyphInfo {
857 u0: f32,
858 v0: f32,
859 u1: f32,
860 v1: f32,
861 w: f32,
862 h: f32,
863 bearing_x: f32,
864 bearing_y: f32,
865 advance: f32,
866}
867
868#[repr(C)]
869#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
870struct RectInstance {
871 xywh: [f32; 4],
872 radius: f32,
873 brush_type: u32,
874 color0: [f32; 4],
875 color1: [f32; 4],
876 grad_start: [f32; 2],
877 grad_end: [f32; 2],
878 sin_cos: [f32; 2],
879}
880
881#[repr(C)]
882#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
883struct BorderInstance {
884 xywh: [f32; 4],
885 radius: f32,
886 stroke: f32,
887 color: [f32; 4],
888 sin_cos: [f32; 2],
889}
890
891#[repr(C)]
892#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
893struct EllipseInstance {
894 xywh: [f32; 4],
895 color: [f32; 4],
896 sin_cos: [f32; 2],
897}
898
899#[repr(C)]
900#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
901struct EllipseBorderInstance {
902 xywh: [f32; 4],
903 stroke: f32,
904 color: [f32; 4],
905 sin_cos: [f32; 2],
906}
907
908#[repr(C)]
909#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
910struct GlyphInstance {
911 xywh: [f32; 4],
912 uv: [f32; 4],
913 color: [f32; 4],
914 sin_cos: [f32; 2],
915}
916
917#[repr(C)]
918#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
919struct BlurInstance {
920 xywh: [f32; 4],
921 uv: [f32; 4],
922 color: [f32; 4],
923 blur_uv: [f32; 2],
924 sin_cos: [f32; 2],
925}
926
927#[repr(C)]
928#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
929struct Nv12Instance {
930 xywh: [f32; 4],
931 uv: [f32; 4],
932 color: [f32; 4], full_range: f32,
934 sin_cos: [f32; 2],
935 _pad: [f32; 1],
936}
937
938#[repr(C)]
939#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
940struct ClipInstance {
941 xywh: [f32; 4],
942 radius: f32,
943 sin_cos: [f32; 2],
944}
945
946fn swash_to_a8_coverage(content: cosmic_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
947 match content {
948 cosmic_text::SwashContent::Mask => Some(data.to_vec()),
949 cosmic_text::SwashContent::SubpixelMask => {
950 let mut out = Vec::with_capacity(data.len() / 4);
951 for px in data.chunks_exact(4) {
952 let r = px[0];
953 let g = px[1];
954 let b = px[2];
955 out.push(r.max(g).max(b));
956 }
957 Some(out)
958 }
959 cosmic_text::SwashContent::Color => None,
960 }
961}
962
963impl WgpuBackend {
964 pub async fn new_async(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
965 let instance: Instance;
966
967 if cfg!(target_arch = "wasm32") {
968 let mut desc = wgpu::InstanceDescriptor::new_without_display_handle();
969 desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
970 instance = wgpu::util::new_instance_with_webgpu_detection(desc).await;
971 } else {
972 instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
973 };
974
975 let surface = instance.create_surface(window.clone())?;
976
977 let adapter = instance
978 .request_adapter(&wgpu::RequestAdapterOptions {
979 power_preference: wgpu::PowerPreference::HighPerformance,
980 compatible_surface: Some(&surface),
981 force_fallback_adapter: false,
982 })
983 .await
984 .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
985
986 let limits = if cfg!(target_arch = "wasm32") {
987 wgpu::Limits::downlevel_webgl2_defaults()
988 } else {
989 wgpu::Limits::default()
990 };
991
992 let (device, queue) = adapter
993 .request_device(&wgpu::DeviceDescriptor {
994 label: Some("repose-rs device"),
995 required_features: wgpu::Features::empty(),
996 required_limits: limits,
997 experimental_features: wgpu::ExperimentalFeatures::disabled(),
998 memory_hints: wgpu::MemoryHints::default(),
999 trace: wgpu::Trace::Off,
1000 })
1001 .await
1002 .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
1003
1004 let size = window.inner_size();
1005
1006 let caps = surface.get_capabilities(&adapter);
1007 let format = caps
1008 .formats
1009 .iter()
1010 .copied()
1011 .find(|f| f.is_srgb())
1012 .unwrap_or(caps.formats[0]);
1013 let present_mode = caps
1014 .present_modes
1015 .iter()
1016 .copied()
1017 .find(|m| *m == wgpu::PresentMode::Mailbox || *m == wgpu::PresentMode::Immediate)
1018 .unwrap_or(wgpu::PresentMode::Fifo);
1019 let alpha_mode = caps.alpha_modes[0];
1020
1021 let config = wgpu::SurfaceConfiguration {
1022 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1023 format,
1024 width: size.width.max(1),
1025 height: size.height.max(1),
1026 present_mode,
1027 alpha_mode,
1028 view_formats: vec![],
1029 desired_maximum_frame_latency: 2,
1030 };
1031 surface.configure(&device, &config);
1032
1033 let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1034 label: Some("globals layout"),
1035 entries: &[wgpu::BindGroupLayoutEntry {
1036 binding: 0,
1037 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1038 ty: wgpu::BindingType::Buffer {
1039 ty: wgpu::BufferBindingType::Uniform,
1040 has_dynamic_offset: false,
1041 min_binding_size: None,
1042 },
1043 count: None,
1044 }],
1045 });
1046
1047 let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
1048 label: Some("globals buf"),
1049 size: std::mem::size_of::<Globals>() as u64,
1050 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1051 mapped_at_creation: false,
1052 });
1053
1054 let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
1055 label: Some("globals bind"),
1056 layout: &globals_layout,
1057 entries: &[wgpu::BindGroupEntry {
1058 binding: 0,
1059 resource: globals_buf.as_entire_binding(),
1060 }],
1061 });
1062
1063 let fmt_features = adapter.get_texture_format_features(format);
1065 let msaa_samples = if fmt_features.flags.sample_count_supported(4)
1066 && fmt_features
1067 .flags
1068 .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
1069 {
1070 4
1071 } else {
1072 1
1073 };
1074
1075 let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
1076
1077 let stencil_for_content = wgpu::DepthStencilState {
1078 format: ds_format,
1079 depth_write_enabled: Some(false),
1080 depth_compare: Some(wgpu::CompareFunction::Always),
1081 stencil: wgpu::StencilState {
1082 front: wgpu::StencilFaceState {
1083 compare: wgpu::CompareFunction::LessEqual,
1084 fail_op: wgpu::StencilOperation::Keep,
1085 depth_fail_op: wgpu::StencilOperation::Keep,
1086 pass_op: wgpu::StencilOperation::Keep,
1087 },
1088 back: wgpu::StencilFaceState {
1089 compare: wgpu::CompareFunction::LessEqual,
1090 fail_op: wgpu::StencilOperation::Keep,
1091 depth_fail_op: wgpu::StencilOperation::Keep,
1092 pass_op: wgpu::StencilOperation::Keep,
1093 },
1094 read_mask: 0xFF,
1095 write_mask: 0x00,
1096 },
1097 bias: wgpu::DepthBiasState::default(),
1098 };
1099
1100 let stencil_for_clip_inc = wgpu::DepthStencilState {
1101 format: ds_format,
1102 depth_write_enabled: Some(false),
1103 depth_compare: Some(wgpu::CompareFunction::Always),
1104 stencil: wgpu::StencilState {
1105 front: wgpu::StencilFaceState {
1106 compare: wgpu::CompareFunction::Equal,
1107 fail_op: wgpu::StencilOperation::Keep,
1108 depth_fail_op: wgpu::StencilOperation::Keep,
1109 pass_op: wgpu::StencilOperation::IncrementClamp,
1110 },
1111 back: wgpu::StencilFaceState {
1112 compare: wgpu::CompareFunction::Equal,
1113 fail_op: wgpu::StencilOperation::Keep,
1114 depth_fail_op: wgpu::StencilOperation::Keep,
1115 pass_op: wgpu::StencilOperation::IncrementClamp,
1116 },
1117 read_mask: 0xFF,
1118 write_mask: 0xFF,
1119 },
1120 bias: wgpu::DepthBiasState::default(),
1121 };
1122
1123 let _multisample_state = wgpu::MultisampleState {
1124 count: msaa_samples,
1125 mask: !0,
1126 alpha_to_coverage_enabled: false,
1127 };
1128
1129 let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1133 label: Some("image/text sampler"),
1134 address_mode_u: wgpu::AddressMode::ClampToEdge,
1135 address_mode_v: wgpu::AddressMode::ClampToEdge,
1136 mag_filter: wgpu::FilterMode::Linear,
1137 min_filter: wgpu::FilterMode::Linear,
1138 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1139 ..Default::default()
1140 });
1141
1142 let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1144 label: Some("text/rgba bind layout"),
1145 entries: &[
1146 wgpu::BindGroupLayoutEntry {
1147 binding: 0,
1148 visibility: wgpu::ShaderStages::FRAGMENT,
1149 ty: wgpu::BindingType::Texture {
1150 multisampled: false,
1151 view_dimension: wgpu::TextureViewDimension::D2,
1152 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1153 },
1154 count: None,
1155 },
1156 wgpu::BindGroupLayoutEntry {
1157 binding: 1,
1158 visibility: wgpu::ShaderStages::FRAGMENT,
1159 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1160 count: None,
1161 },
1162 ],
1163 });
1164 let image_bind_layout_rgba = text_bind_layout.clone();
1166
1167 let image_bind_layout_nv12 =
1169 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1170 label: Some("image bind layout nv12"),
1171 entries: &[
1172 wgpu::BindGroupLayoutEntry {
1174 binding: 0,
1175 visibility: wgpu::ShaderStages::FRAGMENT,
1176 ty: wgpu::BindingType::Texture {
1177 multisampled: false,
1178 view_dimension: wgpu::TextureViewDimension::D2,
1179 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1180 },
1181 count: None,
1182 },
1183 wgpu::BindGroupLayoutEntry {
1185 binding: 1,
1186 visibility: wgpu::ShaderStages::FRAGMENT,
1187 ty: wgpu::BindingType::Texture {
1188 multisampled: false,
1189 view_dimension: wgpu::TextureViewDimension::D2,
1190 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1191 },
1192 count: None,
1193 },
1194 wgpu::BindGroupLayoutEntry {
1196 binding: 2,
1197 visibility: wgpu::ShaderStages::FRAGMENT,
1198 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1199 count: None,
1200 },
1201 ],
1202 });
1203
1204 let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1206 label: Some("clip pipeline layout"),
1207 bind_group_layouts: &[Some(&globals_layout)],
1208 immediate_size: 0,
1209 });
1210 let clip_vertex_layout = wgpu::VertexBufferLayout {
1211 array_stride: std::mem::size_of::<ClipInstance>() as u64,
1212 step_mode: wgpu::VertexStepMode::Instance,
1213 attributes: &[
1214 wgpu::VertexAttribute {
1215 shader_location: 0,
1216 offset: 0,
1217 format: wgpu::VertexFormat::Float32x4,
1218 },
1219 wgpu::VertexAttribute {
1220 shader_location: 1,
1221 offset: 16,
1222 format: wgpu::VertexFormat::Float32,
1223 },
1224 wgpu::VertexAttribute {
1225 shader_location: 2,
1226 offset: 20,
1227 format: wgpu::VertexFormat::Float32x2,
1228 },
1229 ],
1230 };
1231 let clip_color_target = wgpu::ColorTargetState {
1232 format: config.format,
1233 blend: None,
1234 write_mask: wgpu::ColorWrites::empty(),
1235 };
1236
1237 let slug_storage_layout = slug::create_storage_layout(&device);
1239
1240 let surface_pipes = Pipelines::create(
1243 &device,
1244 config.format,
1245 msaa_samples,
1246 &globals_layout,
1247 &text_bind_layout,
1248 &image_bind_layout_nv12,
1249 &clip_pipeline_layout,
1250 &stencil_for_content,
1251 &stencil_for_clip_inc,
1252 &clip_color_target,
1253 &clip_vertex_layout,
1254 &slug_storage_layout,
1255 );
1256 let layer_pipes = Pipelines::create(
1257 &device,
1258 config.format,
1259 1,
1260 &globals_layout,
1261 &text_bind_layout,
1262 &image_bind_layout_nv12,
1263 &clip_pipeline_layout,
1264 &stencil_for_content,
1265 &stencil_for_clip_inc,
1266 &clip_color_target,
1267 &clip_vertex_layout,
1268 &slug_storage_layout,
1269 );
1270
1271 let slug_pipeline = slug::SlugPipeline::new(&device, &slug_storage_layout);
1273
1274 let blur_ring = UploadRing::new(&device, "blur ring", 1024 * 1024);
1276
1277 let atlas_mask = Self::init_atlas_mask(&device)?;
1279 let atlas_color = Self::init_atlas_color(&device)?;
1280
1281 let ring_rect = UploadRing::new(&device, "ring rect", 1 << 20);
1283 let ring_border = UploadRing::new(&device, "ring border", 1 << 20);
1284 let ring_ellipse = UploadRing::new(&device, "ring ellipse", 1 << 20);
1285 let ring_ellipse_border = UploadRing::new(&device, "ring ellipse border", 1 << 20);
1286 let ring_glyph_mask = UploadRing::new(&device, "ring glyph mask", 1 << 20);
1287 let ring_glyph_color = UploadRing::new(&device, "ring glyph color", 1 << 20);
1288 let ring_slug = UploadRing::new(&device, "ring slug", 1 << 18);
1289 let ring_clip = UploadRing::new(&device, "ring clip", 1 << 16);
1290 let ring_nv12 = UploadRing::new(&device, "ring nv12", 1 << 20);
1291
1292 let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
1294 label: Some("temp ds"),
1295 size: wgpu::Extent3d {
1296 width: 1,
1297 height: 1,
1298 depth_or_array_layers: 1,
1299 },
1300 mip_level_count: 1,
1301 sample_count: 1,
1302 dimension: wgpu::TextureDimension::D2,
1303 format: wgpu::TextureFormat::Depth24PlusStencil8,
1304 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1305 view_formats: &[],
1306 });
1307 let depth_stencil_view =
1308 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1309
1310 let mut backend = Self {
1311 surface,
1312 device,
1313 queue,
1314 config,
1315
1316 surface_pipes,
1317 layer_pipes,
1318
1319 rects: InstancedPipe::new(ring_rect),
1320 borders: InstancedPipe::new(ring_border),
1321 ellipses: InstancedPipe::new(ring_ellipse),
1322 ellipse_borders: InstancedPipe::new(ring_ellipse_border),
1323 glyph_mask: InstancedPipe::new(ring_glyph_mask),
1324 glyph_color: InstancedPipe::new(ring_glyph_color),
1325
1326 text_bind_layout,
1327
1328 image_bind_layout_rgba,
1329 image_bind_layout_nv12,
1330 image_sampler,
1331
1332 blur_ring,
1333
1334 slug_pipeline,
1335 slug_ring: ring_slug,
1336 slug_cache: slug::GlyphSlugCache::new(),
1337 slug_prev_generation: !0,
1338 slug_instances: Vec::new(),
1339
1340 clip_ring: ring_clip,
1341
1342 nv12: InstancedPipe::new(ring_nv12),
1343
1344 msaa_samples,
1345 depth_stencil_tex,
1346 depth_stencil_view,
1347 msaa_tex: None,
1348 msaa_view: None,
1349 globals_bind,
1350 globals_buf,
1351 globals_layout,
1352
1353 atlas_mask,
1354 atlas_color,
1355
1356 next_image_handle: 1,
1357 images: HashMap::new(),
1358
1359 frame_index: 0,
1360 image_bytes_total: 0,
1361 image_evict_after_frames: 600, image_budget_bytes: 512 * 1024 * 1024, layer_pool: HashMap::new(),
1364 };
1365
1366 backend.recreate_msaa_and_depth_stencil();
1367 Ok(backend)
1368 }
1369
1370 #[cfg(not(target_arch = "wasm32"))]
1371 pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1372 pollster::block_on(Self::new_async(window))
1373 }
1374
1375 #[cfg(target_arch = "wasm32")]
1376 pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
1377 anyhow::bail!("Use WgpuBackend::new_async(window).await on wasm32")
1378 }
1379
1380 pub fn set_image_from_bytes(
1383 &mut self,
1384 handle: u64,
1385 data: &[u8],
1386 srgb: bool,
1387 ) -> anyhow::Result<()> {
1388 let img = image::load_from_memory(data)?;
1389 let rgba = img.to_rgba8();
1390 let (w, h) = rgba.dimensions();
1391 self.set_image_rgba8(handle, w, h, &rgba, srgb)
1392 }
1393
1394 pub fn set_image_rgba8(
1395 &mut self,
1396 handle: u64,
1397 w: u32,
1398 h: u32,
1399 rgba: &[u8],
1400 srgb: bool,
1401 ) -> anyhow::Result<()> {
1402 let expected = (w as usize) * (h as usize) * 4;
1403 if rgba.len() < expected {
1404 return Err(anyhow::anyhow!(
1405 "RGBA buffer too small: {} < {}",
1406 rgba.len(),
1407 expected
1408 ));
1409 }
1410
1411 let format = if srgb {
1412 wgpu::TextureFormat::Rgba8UnormSrgb
1413 } else {
1414 wgpu::TextureFormat::Rgba8Unorm
1415 };
1416
1417 let needs_recreate = match self.images.get(&handle) {
1418 Some(ImageTex::Rgba {
1419 w: cw,
1420 h: ch,
1421 format: cf,
1422 ..
1423 }) => *cw != w || *ch != h || *cf != format,
1424 _ => true,
1425 };
1426
1427 if needs_recreate {
1428 self.remove_image(handle);
1430
1431 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1432 label: Some("user image rgba"),
1433 size: wgpu::Extent3d {
1434 width: w,
1435 height: h,
1436 depth_or_array_layers: 1,
1437 },
1438 mip_level_count: 1,
1439 sample_count: 1,
1440 dimension: wgpu::TextureDimension::D2,
1441 format,
1442 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1443 view_formats: &[],
1444 });
1445 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1446
1447 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1448 label: Some("image bind rgba"),
1449 layout: &self.image_bind_layout_rgba,
1450 entries: &[
1451 wgpu::BindGroupEntry {
1452 binding: 0,
1453 resource: wgpu::BindingResource::TextureView(&view),
1454 },
1455 wgpu::BindGroupEntry {
1456 binding: 1,
1457 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1458 },
1459 ],
1460 });
1461
1462 let bytes = (w as u64) * (h as u64) * 4;
1463 self.image_bytes_total += bytes;
1464
1465 self.images.insert(
1466 handle,
1467 ImageTex::Rgba {
1468 tex,
1469 view,
1470 bind,
1471 w,
1472 h,
1473 format,
1474 last_used_frame: self.frame_index,
1475 bytes,
1476 },
1477 );
1478 }
1479
1480 let tex = match self.images.get(&handle) {
1481 Some(ImageTex::Rgba { tex, .. }) => tex,
1482 _ => unreachable!(),
1483 };
1484
1485 self.queue.write_texture(
1486 wgpu::TexelCopyTextureInfo {
1487 texture: tex,
1488 mip_level: 0,
1489 origin: wgpu::Origin3d::ZERO,
1490 aspect: wgpu::TextureAspect::All,
1491 },
1492 &rgba[..expected],
1493 wgpu::TexelCopyBufferLayout {
1494 offset: 0,
1495 bytes_per_row: Some(4 * w),
1496 rows_per_image: Some(h),
1497 },
1498 wgpu::Extent3d {
1499 width: w,
1500 height: h,
1501 depth_or_array_layers: 1,
1502 },
1503 );
1504
1505 self.evict_budget_excess();
1507
1508 Ok(())
1509 }
1510
1511 pub fn set_image_nv12(
1512 &mut self,
1513 handle: u64,
1514 w: u32,
1515 h: u32,
1516 y: &[u8],
1517 uv: &[u8],
1518 full_range: bool,
1519 ) -> anyhow::Result<()> {
1520 let y_expected = (w as usize) * (h as usize);
1521 let uv_w = (w / 2).max(1);
1522 let uv_h = (h / 2).max(1);
1523 let uv_expected = (uv_w as usize) * (uv_h as usize) * 2;
1524
1525 if y.len() < y_expected {
1526 return Err(anyhow::anyhow!("Y plane too small"));
1527 }
1528 if uv.len() < uv_expected {
1529 return Err(anyhow::anyhow!("UV plane too small"));
1530 }
1531
1532 let needs_recreate = match self.images.get(&handle) {
1533 Some(ImageTex::Nv12 { w: ww, h: hh, .. }) => *ww != w || *hh != h,
1534 _ => true,
1535 };
1536
1537 if needs_recreate {
1538 self.remove_image(handle);
1539
1540 let tex_y = self.device.create_texture(&wgpu::TextureDescriptor {
1541 label: Some("nv12 Y"),
1542 size: wgpu::Extent3d {
1543 width: w,
1544 height: h,
1545 depth_or_array_layers: 1,
1546 },
1547 mip_level_count: 1,
1548 sample_count: 1,
1549 dimension: wgpu::TextureDimension::D2,
1550 format: wgpu::TextureFormat::R8Unorm,
1551 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1552 view_formats: &[],
1553 });
1554 let view_y = tex_y.create_view(&wgpu::TextureViewDescriptor::default());
1555
1556 let tex_uv = self.device.create_texture(&wgpu::TextureDescriptor {
1557 label: Some("nv12 UV"),
1558 size: wgpu::Extent3d {
1559 width: uv_w,
1560 height: uv_h,
1561 depth_or_array_layers: 1,
1562 },
1563 mip_level_count: 1,
1564 sample_count: 1,
1565 dimension: wgpu::TextureDimension::D2,
1566 format: wgpu::TextureFormat::Rg8Unorm,
1567 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1568 view_formats: &[],
1569 });
1570 let view_uv = tex_uv.create_view(&wgpu::TextureViewDescriptor::default());
1571
1572 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1573 label: Some("nv12 bind"),
1574 layout: &self.image_bind_layout_nv12,
1575 entries: &[
1576 wgpu::BindGroupEntry {
1577 binding: 0,
1578 resource: wgpu::BindingResource::TextureView(&view_y),
1579 },
1580 wgpu::BindGroupEntry {
1581 binding: 1,
1582 resource: wgpu::BindingResource::TextureView(&view_uv),
1583 },
1584 wgpu::BindGroupEntry {
1585 binding: 2,
1586 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1587 },
1588 ],
1589 });
1590
1591 let bytes = (w as u64) * (h as u64) + (uv_w as u64) * (uv_h as u64) * 2;
1592 self.image_bytes_total += bytes;
1593
1594 self.images.insert(
1595 handle,
1596 ImageTex::Nv12 {
1597 tex_y,
1598 view_y,
1599 tex_uv,
1600 view_uv,
1601 bind,
1602 w,
1603 h,
1604 full_range,
1605 last_used_frame: self.frame_index,
1606 bytes,
1607 },
1608 );
1609 }
1610
1611 let (tex_y, tex_uv, _bind) = match self.images.get(&handle) {
1612 Some(ImageTex::Nv12 {
1613 tex_y,
1614 tex_uv,
1615 bind,
1616 ..
1617 }) => (tex_y, tex_uv, bind),
1618 _ => return Err(anyhow::anyhow!("Handle is not NV12")),
1619 };
1620
1621 self.queue.write_texture(
1622 wgpu::TexelCopyTextureInfo {
1623 texture: tex_y,
1624 mip_level: 0,
1625 origin: wgpu::Origin3d::ZERO,
1626 aspect: wgpu::TextureAspect::All,
1627 },
1628 &y[..y_expected],
1629 wgpu::TexelCopyBufferLayout {
1630 offset: 0,
1631 bytes_per_row: Some(w),
1632 rows_per_image: Some(h),
1633 },
1634 wgpu::Extent3d {
1635 width: w,
1636 height: h,
1637 depth_or_array_layers: 1,
1638 },
1639 );
1640
1641 self.queue.write_texture(
1642 wgpu::TexelCopyTextureInfo {
1643 texture: tex_uv,
1644 mip_level: 0,
1645 origin: wgpu::Origin3d::ZERO,
1646 aspect: wgpu::TextureAspect::All,
1647 },
1648 &uv[..uv_expected],
1649 wgpu::TexelCopyBufferLayout {
1650 offset: 0,
1651 bytes_per_row: Some(2 * uv_w),
1652 rows_per_image: Some(uv_h),
1653 },
1654 wgpu::Extent3d {
1655 width: uv_w,
1656 height: uv_h,
1657 depth_or_array_layers: 1,
1658 },
1659 );
1660
1661 self.evict_budget_excess();
1662 Ok(())
1663 }
1664
1665 pub fn remove_image(&mut self, handle: u64) {
1666 if let Some(img) = self.images.remove(&handle) {
1667 let b = match img {
1668 ImageTex::Rgba { bytes, .. } => bytes,
1669 ImageTex::Nv12 { bytes, .. } => bytes,
1670 };
1671 self.image_bytes_total = self.image_bytes_total.saturating_sub(b);
1672 }
1673 }
1674
1675 pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
1677 let handle = self.next_image_handle;
1678 self.next_image_handle += 1;
1679 if let Err(e) = self.set_image_from_bytes(handle, data, srgb) {
1680 log::error!("Failed to register image: {e}");
1681 }
1682 handle
1683 }
1684
1685 fn evict_unused_images(&mut self) {
1686 let now = self.frame_index;
1687 let evict_after = self.image_evict_after_frames;
1688
1689 let mut to_remove = Vec::new();
1691 for (h, t) in self.images.iter() {
1692 let last = match t {
1693 ImageTex::Rgba {
1694 last_used_frame, ..
1695 } => *last_used_frame,
1696 ImageTex::Nv12 {
1697 last_used_frame, ..
1698 } => *last_used_frame,
1699 };
1700 if now.saturating_sub(last) > evict_after {
1701 to_remove.push(*h);
1702 }
1703 }
1704 for h in to_remove {
1705 self.remove_image(h);
1706 }
1707
1708 self.evict_budget_excess();
1709 }
1710
1711 fn evict_budget_excess(&mut self) {
1712 if self.image_bytes_total <= self.image_budget_bytes {
1713 return;
1714 }
1715 let mut candidates: Vec<(u64, u64, u64)> = self
1717 .images
1718 .iter()
1719 .map(|(h, t)| {
1720 let (last, bytes) = match t {
1721 ImageTex::Rgba {
1722 last_used_frame,
1723 bytes,
1724 ..
1725 } => (*last_used_frame, *bytes),
1726 ImageTex::Nv12 {
1727 last_used_frame,
1728 bytes,
1729 ..
1730 } => (*last_used_frame, *bytes),
1731 };
1732 (*h, last, bytes)
1733 })
1734 .collect();
1735
1736 candidates.sort_by_key(|k| k.1);
1738
1739 let now = self.frame_index;
1740 for (h, last, _bytes) in candidates {
1741 if self.image_bytes_total <= self.image_budget_bytes {
1742 break;
1743 }
1744 if last == now {
1746 continue;
1747 }
1748 self.remove_image(h);
1749 }
1750 }
1751
1752 fn recreate_msaa_and_depth_stencil(&mut self) {
1753 if self.msaa_samples > 1 {
1754 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1755 label: Some("msaa color"),
1756 size: wgpu::Extent3d {
1757 width: self.config.width.max(1),
1758 height: self.config.height.max(1),
1759 depth_or_array_layers: 1,
1760 },
1761 mip_level_count: 1,
1762 sample_count: self.msaa_samples,
1763 dimension: wgpu::TextureDimension::D2,
1764 format: self.config.format,
1765 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1766 view_formats: &[],
1767 });
1768 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1769 self.msaa_tex = Some(tex);
1770 self.msaa_view = Some(view);
1771 } else {
1772 self.msaa_tex = None;
1773 self.msaa_view = None;
1774 }
1775
1776 self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1777 label: Some("depth-stencil (stencil clips)"),
1778 size: wgpu::Extent3d {
1779 width: self.config.width.max(1),
1780 height: self.config.height.max(1),
1781 depth_or_array_layers: 1,
1782 },
1783 mip_level_count: 1,
1784 sample_count: self.msaa_samples,
1785 dimension: wgpu::TextureDimension::D2,
1786 format: wgpu::TextureFormat::Depth24PlusStencil8,
1787 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1788 view_formats: &[],
1789 });
1790 self.depth_stencil_view = self
1791 .depth_stencil_tex
1792 .create_view(&wgpu::TextureViewDescriptor::default());
1793 }
1794
1795 fn init_atlas_mask(device: &wgpu::Device) -> anyhow::Result<AtlasA8> {
1796 let size = 1024u32;
1797 let tex = device.create_texture(&wgpu::TextureDescriptor {
1798 label: Some("glyph atlas A8"),
1799 size: wgpu::Extent3d {
1800 width: size,
1801 height: size,
1802 depth_or_array_layers: 1,
1803 },
1804 mip_level_count: 1,
1805 sample_count: 1,
1806 dimension: wgpu::TextureDimension::D2,
1807 format: wgpu::TextureFormat::R8Unorm,
1808 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1809 view_formats: &[],
1810 });
1811 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1812 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1813 label: Some("glyph atlas sampler A8"),
1814 address_mode_u: wgpu::AddressMode::ClampToEdge,
1815 address_mode_v: wgpu::AddressMode::ClampToEdge,
1816 address_mode_w: wgpu::AddressMode::ClampToEdge,
1817 mag_filter: wgpu::FilterMode::Linear,
1818 min_filter: wgpu::FilterMode::Linear,
1819 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1820 ..Default::default()
1821 });
1822
1823 Ok(AtlasA8 {
1824 tex,
1825 view,
1826 sampler,
1827 size,
1828 next_x: 1,
1829 next_y: 1,
1830 row_h: 0,
1831 map: HashMap::new(),
1832 })
1833 }
1834
1835 fn init_atlas_color(device: &wgpu::Device) -> anyhow::Result<AtlasRGBA> {
1836 let size = 1024u32;
1837 let tex = device.create_texture(&wgpu::TextureDescriptor {
1838 label: Some("glyph atlas RGBA"),
1839 size: wgpu::Extent3d {
1840 width: size,
1841 height: size,
1842 depth_or_array_layers: 1,
1843 },
1844 mip_level_count: 1,
1845 sample_count: 1,
1846 dimension: wgpu::TextureDimension::D2,
1847 format: wgpu::TextureFormat::Rgba8UnormSrgb,
1848 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1849 view_formats: &[],
1850 });
1851 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1852 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1853 label: Some("glyph atlas sampler RGBA"),
1854 address_mode_u: wgpu::AddressMode::ClampToEdge,
1855 address_mode_v: wgpu::AddressMode::ClampToEdge,
1856 address_mode_w: wgpu::AddressMode::ClampToEdge,
1857 mag_filter: wgpu::FilterMode::Linear,
1858 min_filter: wgpu::FilterMode::Linear,
1859 mipmap_filter: wgpu::MipmapFilterMode::Linear,
1860 ..Default::default()
1861 });
1862 Ok(AtlasRGBA {
1863 tex,
1864 view,
1865 sampler,
1866 size,
1867 next_x: 1,
1868 next_y: 1,
1869 row_h: 0,
1870 map: HashMap::new(),
1871 })
1872 }
1873
1874 fn get_or_create_layer(
1875 &mut self,
1876 layer_id: u32,
1877 width: u32,
1878 height: u32,
1879 rect: repose_core::Rect,
1880 ) {
1881 let needs_alloc = match self.layer_pool.get(&layer_id) {
1882 Some(lt) => lt.width != width || lt.height != height,
1883 None => true,
1884 };
1885 if !needs_alloc {
1886 return;
1887 }
1888 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1889 label: Some("graphics layer"),
1890 size: wgpu::Extent3d {
1891 width: width.max(1),
1892 height: height.max(1),
1893 depth_or_array_layers: 1,
1894 },
1895 mip_level_count: 1,
1896 sample_count: 1,
1897 dimension: wgpu::TextureDimension::D2,
1898 format: self.config.format,
1899 usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
1900 view_formats: &[],
1901 });
1902 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1903 let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1904 label: Some("layer bind"),
1905 layout: &self.image_bind_layout_rgba,
1906 entries: &[
1907 wgpu::BindGroupEntry {
1908 binding: 0,
1909 resource: wgpu::BindingResource::TextureView(&view),
1910 },
1911 wgpu::BindGroupEntry {
1912 binding: 1,
1913 resource: wgpu::BindingResource::Sampler(&self.image_sampler),
1914 },
1915 ],
1916 });
1917 let depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
1918 label: Some("graphics layer depth-stencil"),
1919 size: wgpu::Extent3d {
1920 width: width.max(1),
1921 height: height.max(1),
1922 depth_or_array_layers: 1,
1923 },
1924 mip_level_count: 1,
1925 sample_count: 1,
1926 dimension: wgpu::TextureDimension::D2,
1927 format: wgpu::TextureFormat::Depth24PlusStencil8,
1928 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1929 view_formats: &[],
1930 });
1931 let depth_stencil_view =
1932 depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
1933 self.layer_pool.insert(
1934 layer_id,
1935 LayerTarget {
1936 texture: tex,
1937 view,
1938 bind,
1939 depth_stencil_tex,
1940 depth_stencil_view,
1941 width,
1942 height,
1943 rect_px: (rect.x, rect.y, rect.w, rect.h),
1944 },
1945 );
1946 }
1947
1948 fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
1949 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1950 label: Some("atlas bind"),
1951 layout: &self.text_bind_layout,
1952 entries: &[
1953 wgpu::BindGroupEntry {
1954 binding: 0,
1955 resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
1956 },
1957 wgpu::BindGroupEntry {
1958 binding: 1,
1959 resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
1960 },
1961 ],
1962 })
1963 }
1964
1965 fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
1966 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1967 label: Some("atlas bind color"),
1968 layout: &self.text_bind_layout,
1969 entries: &[
1970 wgpu::BindGroupEntry {
1971 binding: 0,
1972 resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
1973 },
1974 wgpu::BindGroupEntry {
1975 binding: 1,
1976 resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
1977 },
1978 ],
1979 })
1980 }
1981
1982 fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
1983 let keyp = (key, px);
1984 if let Some(info) = self.atlas_mask.map.get(&keyp) {
1985 return Some(*info);
1986 }
1987
1988 let gb = repose_text::rasterize(key, px as f32)?;
1989 if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
1990 return None;
1991 }
1992
1993 let coverage = swash_to_a8_coverage(gb.content, &gb.data)?;
1994
1995 let w = gb.w.max(1);
1996 let h = gb.h.max(1);
1997
1998 if !self.alloc_space_mask(w, h) {
1999 self.grow_mask_and_rebuild();
2000 }
2001 if !self.alloc_space_mask(w, h) {
2002 return None;
2003 }
2004 let x = self.atlas_mask.next_x;
2005 let y = self.atlas_mask.next_y;
2006 self.atlas_mask.next_x += w + 1;
2007 self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
2008
2009 let layout = wgpu::TexelCopyBufferLayout {
2010 offset: 0,
2011 bytes_per_row: Some(w),
2012 rows_per_image: Some(h),
2013 };
2014 let size = wgpu::Extent3d {
2015 width: w,
2016 height: h,
2017 depth_or_array_layers: 1,
2018 };
2019 self.queue.write_texture(
2020 wgpu::TexelCopyTextureInfoBase {
2021 texture: &self.atlas_mask.tex,
2022 mip_level: 0,
2023 origin: wgpu::Origin3d { x, y, z: 0 },
2024 aspect: wgpu::TextureAspect::All,
2025 },
2026 &coverage,
2027 layout,
2028 size,
2029 );
2030
2031 let info = GlyphInfo {
2032 u0: x as f32 / self.atlas_mask.size as f32,
2033 v0: y as f32 / self.atlas_mask.size as f32,
2034 u1: (x + w) as f32 / self.atlas_mask.size as f32,
2035 v1: (y + h) as f32 / self.atlas_mask.size as f32,
2036 w: w as f32,
2037 h: h as f32,
2038 bearing_x: 0.0,
2039 bearing_y: 0.0,
2040 advance: 0.0,
2041 };
2042 self.atlas_mask.map.insert(keyp, info);
2043 Some(info)
2044 }
2045
2046 fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
2047 let keyp = (key, px);
2048 if let Some(info) = self.atlas_color.map.get(&keyp) {
2049 return Some(*info);
2050 }
2051 let gb = repose_text::rasterize(key, px as f32)?;
2052 if !matches!(gb.content, cosmic_text::SwashContent::Color) {
2053 return None;
2054 }
2055 let w = gb.w.max(1);
2056 let h = gb.h.max(1);
2057 if !self.alloc_space_color(w, h) {
2058 self.grow_color_and_rebuild();
2059 }
2060 if !self.alloc_space_color(w, h) {
2061 return None;
2062 }
2063 let x = self.atlas_color.next_x;
2064 let y = self.atlas_color.next_y;
2065 self.atlas_color.next_x += w + 1;
2066 self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
2067
2068 let layout = wgpu::TexelCopyBufferLayout {
2069 offset: 0,
2070 bytes_per_row: Some(w * 4),
2071 rows_per_image: Some(h),
2072 };
2073 let size = wgpu::Extent3d {
2074 width: w,
2075 height: h,
2076 depth_or_array_layers: 1,
2077 };
2078 self.queue.write_texture(
2079 wgpu::TexelCopyTextureInfoBase {
2080 texture: &self.atlas_color.tex,
2081 mip_level: 0,
2082 origin: wgpu::Origin3d { x, y, z: 0 },
2083 aspect: wgpu::TextureAspect::All,
2084 },
2085 &gb.data,
2086 layout,
2087 size,
2088 );
2089 let info = GlyphInfo {
2090 u0: x as f32 / self.atlas_color.size as f32,
2091 v0: y as f32 / self.atlas_color.size as f32,
2092 u1: (x + w) as f32 / self.atlas_color.size as f32,
2093 v1: (y + h) as f32 / self.atlas_color.size as f32,
2094 w: w as f32,
2095 h: h as f32,
2096 bearing_x: 0.0,
2097 bearing_y: 0.0,
2098 advance: 0.0,
2099 };
2100 self.atlas_color.map.insert(keyp, info);
2101 Some(info)
2102 }
2103
2104 fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
2105 if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
2106 self.atlas_mask.next_x = 1;
2107 self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
2108 self.atlas_mask.row_h = 0;
2109 }
2110 if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
2111 return false;
2112 }
2113 true
2114 }
2115
2116 fn grow_mask_and_rebuild(&mut self) {
2117 let new_size = (self.atlas_mask.size * 2).min(4096);
2118 if new_size == self.atlas_mask.size {
2119 return;
2120 }
2121 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2122 label: Some("glyph atlas A8 (grown)"),
2123 size: wgpu::Extent3d {
2124 width: new_size,
2125 height: new_size,
2126 depth_or_array_layers: 1,
2127 },
2128 mip_level_count: 1,
2129 sample_count: 1,
2130 dimension: wgpu::TextureDimension::D2,
2131 format: wgpu::TextureFormat::R8Unorm,
2132 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2133 view_formats: &[],
2134 });
2135 self.atlas_mask.tex = tex;
2136 self.atlas_mask.view = self
2137 .atlas_mask
2138 .tex
2139 .create_view(&wgpu::TextureViewDescriptor::default());
2140 self.atlas_mask.size = new_size;
2141 self.atlas_mask.next_x = 1;
2142 self.atlas_mask.next_y = 1;
2143 self.atlas_mask.row_h = 0;
2144 let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
2145 self.atlas_mask.map.clear();
2146 for (k, px) in keys {
2147 let _ = self.upload_glyph_mask(k, px);
2148 }
2149 }
2150
2151 fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
2152 if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
2153 self.atlas_color.next_x = 1;
2154 self.atlas_color.next_y += self.atlas_color.row_h + 1;
2155 self.atlas_color.row_h = 0;
2156 }
2157 if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
2158 return false;
2159 }
2160 true
2161 }
2162
2163 fn grow_color_and_rebuild(&mut self) {
2164 let new_size = (self.atlas_color.size * 2).min(4096);
2165 if new_size == self.atlas_color.size {
2166 return;
2167 }
2168 let tex = self.device.create_texture(&wgpu::TextureDescriptor {
2169 label: Some("glyph atlas RGBA (grown)"),
2170 size: wgpu::Extent3d {
2171 width: new_size,
2172 height: new_size,
2173 depth_or_array_layers: 1,
2174 },
2175 mip_level_count: 1,
2176 sample_count: 1,
2177 dimension: wgpu::TextureDimension::D2,
2178 format: wgpu::TextureFormat::Rgba8UnormSrgb,
2179 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
2180 view_formats: &[],
2181 });
2182 self.atlas_color.tex = tex;
2183 self.atlas_color.view = self
2184 .atlas_color
2185 .tex
2186 .create_view(&wgpu::TextureViewDescriptor::default());
2187 self.atlas_color.size = new_size;
2188 self.atlas_color.next_x = 1;
2189 self.atlas_color.next_y = 1;
2190 self.atlas_color.row_h = 0;
2191 let keys: Vec<(repose_text::GlyphKey, u32)> =
2192 self.atlas_color.map.keys().copied().collect();
2193 self.atlas_color.map.clear();
2194 for (k, px) in keys {
2195 let _ = self.upload_glyph_color(k, px);
2196 }
2197 }
2198}
2199
2200fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
2201 match brush {
2202 Brush::Solid(c) => (
2203 0u32,
2204 c.to_linear(),
2205 [0.0, 0.0, 0.0, 0.0],
2206 [0.0, 0.0],
2207 [0.0, 1.0],
2208 ),
2209 Brush::Linear {
2210 start,
2211 end,
2212 start_color,
2213 end_color,
2214 } => (
2215 1u32,
2216 start_color.to_linear(),
2217 end_color.to_linear(),
2218 [start.x, start.y],
2219 [end.x, end.y],
2220 ),
2221 _ => (0u32, [0.0; 4], [0.0; 4], [0.0; 2], [0.0; 2]),
2222 }
2223}
2224
2225fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
2226 match brush {
2227 Brush::Solid(c) => c.to_linear(),
2228 Brush::Linear { start_color, .. } => start_color.to_linear(),
2229 _ => [0.0; 4],
2230 }
2231}
2232
2233impl RenderBackend for WgpuBackend {
2234 fn configure_surface(&mut self, width: u32, height: u32) {
2235 if width == 0 || height == 0 {
2236 return;
2237 }
2238 self.config.width = width;
2239 self.config.height = height;
2240 self.surface.configure(&self.device, &self.config);
2241 self.recreate_msaa_and_depth_stencil();
2242 }
2243
2244 fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
2245 self.frame_index = self.frame_index.wrapping_add(1);
2247 self.slug_cache.next_frame();
2248
2249 if self.config.width == 0 || self.config.height == 0 {
2250 return;
2251 }
2252 let mut retries = 0u32;
2253 const MAX_RETRIES: u32 = 4;
2254 let frame = loop {
2255 match self.surface.get_current_texture() {
2256 wgpu::CurrentSurfaceTexture::Success(f) => break f,
2257 wgpu::CurrentSurfaceTexture::Suboptimal(f) => {
2258 log::warn!("suboptimal surface; reconfiguring");
2259 self.surface.configure(&self.device, &self.config);
2260 break f;
2261 }
2262 wgpu::CurrentSurfaceTexture::Outdated => {
2263 retries += 1;
2264 if retries >= MAX_RETRIES {
2265 log::warn!(
2266 "surface outdated persisted after {MAX_RETRIES} retries; skipping frame"
2267 );
2268 return;
2269 }
2270 log::warn!("surface outdated; reconfiguring");
2271 self.surface.configure(&self.device, &self.config);
2272 }
2273 wgpu::CurrentSurfaceTexture::Lost => {
2274 retries += 1;
2275 if retries >= MAX_RETRIES {
2276 log::warn!(
2277 "surface lost persisted after {MAX_RETRIES} retries; skipping frame"
2278 );
2279 return;
2280 }
2281 log::warn!("surface lost; reconfiguring");
2282 self.surface.configure(&self.device, &self.config);
2283 }
2284 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
2285 request_frame();
2286 return;
2287 }
2288 wgpu::CurrentSurfaceTexture::Validation => {
2289 retries += 1;
2290 if retries >= MAX_RETRIES {
2291 log::warn!(
2292 "surface validation persisted after {MAX_RETRIES} retries; skipping frame"
2293 );
2294 return;
2295 }
2296 self.surface.configure(&self.device, &self.config);
2297 }
2298 }
2299 };
2300
2301 fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
2302 let x0 = (x / fb_w) * 2.0 - 1.0;
2303 let y0 = 1.0 - (y / fb_h) * 2.0;
2304 let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
2305 let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
2306 let min_x = x0.min(x1);
2307 let min_y = y0.min(y1);
2308 let w_ndc = (x1 - x0).abs();
2309 let h_ndc = (y1 - y0).abs();
2310 [min_x, min_y, w_ndc, h_ndc]
2311 }
2312
2313 fn rect_to_instance_ndc(
2315 rect: repose_core::Rect,
2316 transform: &Transform,
2317 fb_w: f32,
2318 fb_h: f32,
2319 ) -> ([f32; 4], [f32; 2]) {
2320 let cx = rect.x + rect.w * 0.5;
2321 let cy = rect.y + rect.h * 0.5;
2322
2323 let sx = cx * transform.scale_x;
2325 let sy = cy * transform.scale_y;
2326 let cos_a = transform.rotate.cos();
2327 let sin_a = transform.rotate.sin();
2328 let tx = sx * cos_a - sy * sin_a + transform.translate_x;
2329 let ty = sx * sin_a + sy * cos_a + transform.translate_y;
2330
2331 let ndc_cx = (tx / fb_w) * 2.0 - 1.0;
2333 let ndc_cy = 1.0 - (ty / fb_h) * 2.0;
2334 let ndc_w = (rect.w * transform.scale_x / fb_w) * 2.0;
2336 let ndc_h = (rect.h * transform.scale_y / fb_h) * 2.0;
2337
2338 ([ndc_cx, ndc_cy, ndc_w, ndc_h], [cos_a, sin_a])
2339 }
2340
2341 fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
2342 let mut x = r.x.floor() as i64;
2343 let mut y = r.y.floor() as i64;
2344 let fb_wi = fb_w as i64;
2345 let fb_hi = fb_h as i64;
2346 x = x.clamp(0, fb_wi.saturating_sub(1));
2347 y = y.clamp(0, fb_hi.saturating_sub(1));
2348 let w_req = r.w.ceil().max(1.0) as i64;
2349 let h_req = r.h.ceil().max(1.0) as i64;
2350 let w = (w_req).min(fb_wi - x).max(1);
2351 let h = (h_req).min(fb_hi - y).max(1);
2352 (x as u32, y as u32, w as u32, h as u32)
2353 }
2354
2355 let fb_w = self.config.width as f32;
2356 let fb_h = self.config.height as f32;
2357
2358 let globals = Globals {
2359 ndc_to_px: [fb_w * 0.5, fb_h * 0.5],
2360 _pad: [0.0, 0.0],
2361 };
2362 self.queue
2363 .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
2364
2365 let mut passes: Vec<Pass> = Vec::with_capacity(1);
2366 let mut current_pass: Pass = Pass {
2367 target: PassTarget::Surface,
2368 initial_scissor: (0, 0, self.config.width, self.config.height),
2369 clear_color: Some([
2370 scene.clear_color.0 as f32 / 255.0,
2371 scene.clear_color.1 as f32 / 255.0,
2372 scene.clear_color.2 as f32 / 255.0,
2373 scene.clear_color.3 as f32 / 255.0,
2374 ]),
2375 cmds: Vec::with_capacity(scene.nodes.len()),
2376 };
2377 let mut target_stack: Vec<PassTarget> = Vec::new();
2378 let mut layer_alphas: Vec<(u32, f32, (u32, u32, u32, u32))> = Vec::new();
2379 let mut current_target_size: (f32, f32) = (fb_w, fb_h);
2380
2381 struct Batch {
2382 rects: Vec<RectInstance>,
2383 borders: Vec<BorderInstance>,
2384 ellipses: Vec<EllipseInstance>,
2385 e_borders: Vec<EllipseBorderInstance>,
2386 masks: Vec<GlyphInstance>,
2387 colors: Vec<GlyphInstance>,
2388 nv12s: Vec<Nv12Instance>,
2389 }
2390
2391 impl Batch {
2392 fn new() -> Self {
2393 Self {
2394 rects: vec![],
2395 borders: vec![],
2396 ellipses: vec![],
2397 e_borders: vec![],
2398 masks: vec![],
2399 colors: vec![],
2400 nv12s: vec![],
2401 }
2402 }
2403
2404 fn is_empty(&self) -> bool {
2405 self.rects.is_empty()
2406 && self.borders.is_empty()
2407 && self.ellipses.is_empty()
2408 && self.e_borders.is_empty()
2409 && self.masks.is_empty()
2410 && self.colors.is_empty()
2411 && self.nv12s.is_empty()
2412 }
2413
2414 fn flush(
2415 &mut self,
2416 pipes: (
2417 &mut InstancedPipe<RectInstance>,
2418 &mut InstancedPipe<BorderInstance>,
2419 &mut InstancedPipe<EllipseInstance>,
2420 &mut InstancedPipe<EllipseBorderInstance>,
2421 ),
2422 glyph_pipes: (
2423 &mut InstancedPipe<GlyphInstance>,
2424 &mut InstancedPipe<GlyphInstance>,
2425 ),
2426 nv12_pipe: &mut InstancedPipe<Nv12Instance>,
2427 device: &wgpu::Device,
2428 queue: &wgpu::Queue,
2429 cmds: &mut Vec<Cmd>,
2430 ) {
2431 let (rects, borders, ellipses, e_borders) = pipes;
2432 let (masks, colors) = glyph_pipes;
2433
2434 macro_rules! flush_one {
2435 ($buf:ident, $pipe:expr, $variant:ident) => {
2436 if !self.$buf.is_empty() {
2437 if let Some((off, cnt)) = $pipe.upload(device, queue, &self.$buf) {
2438 cmds.push(Cmd::$variant { off, cnt });
2439 }
2440 self.$buf.clear();
2441 }
2442 };
2443 }
2444
2445 flush_one!(rects, rects, Rect);
2446 flush_one!(borders, borders, Border);
2447 flush_one!(ellipses, ellipses, Ellipse);
2448 flush_one!(e_borders, e_borders, EllipseBorder);
2449 flush_one!(masks, masks, GlyphsMask);
2450 flush_one!(colors, colors, GlyphsColor);
2451
2452 if !self.nv12s.is_empty() {
2453 if let Some((off, cnt)) = nv12_pipe.upload(device, queue, &self.nv12s) {
2454 let _ = (off, cnt);
2455 }
2456 self.nv12s.clear();
2457 }
2458 }
2459 }
2460
2461 self.rects.reset();
2462 self.borders.reset();
2463 self.ellipses.reset();
2464 self.ellipse_borders.reset();
2465 self.glyph_mask.reset();
2466 self.glyph_color.reset();
2467 self.clip_ring.reset();
2468 self.blur_ring.reset();
2469 self.nv12.reset();
2470
2471 self.slug_ring.reset();
2472 self.slug_instances.clear();
2473 let mut batch = Batch::new();
2474 let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
2475 let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
2476 let root_clip_rect = repose_core::Rect {
2477 x: 0.0,
2478 y: 0.0,
2479 w: fb_w,
2480 h: fb_h,
2481 };
2482
2483 let mut current_prim: Option<&'static str> = None;
2484
2485 macro_rules! flush_if_prim_changed {
2486 ($prim:literal, $pipe:expr) => {
2487 if current_prim != Some($prim) {
2488 flush_batch!();
2489 current_prim = Some($prim);
2490 }
2491 };
2492 }
2493
2494 macro_rules! flush_batch {
2495 () => {
2496 if !batch.is_empty() {
2497 batch.flush(
2498 (
2499 &mut self.rects,
2500 &mut self.borders,
2501 &mut self.ellipses,
2502 &mut self.ellipse_borders,
2503 ),
2504 (&mut self.glyph_mask, &mut self.glyph_color),
2505 &mut self.nv12,
2506 &self.device,
2507 &self.queue,
2508 &mut current_pass.cmds,
2509 )
2510 }
2511 };
2512 }
2513
2514 for node in &scene.nodes {
2515 let t_identity = Transform::identity();
2516 let current_transform = transform_stack.last().unwrap_or(&t_identity);
2517
2518 match node {
2519 SceneNode::Rect {
2520 rect,
2521 brush,
2522 radius,
2523 } => {
2524 flush_if_prim_changed!("rect", &self.rects);
2525 let (ndc, sin_cos) = rect_to_instance_ndc(
2526 *rect,
2527 current_transform,
2528 current_target_size.0,
2529 current_target_size.1,
2530 );
2531 let (brush_type, color0, color1, grad_start, grad_end) =
2532 brush_to_instance_fields(brush);
2533 batch.rects.push(RectInstance {
2534 xywh: ndc,
2535 radius: *radius,
2536 brush_type,
2537 color0,
2538 color1,
2539 grad_start,
2540 grad_end,
2541 sin_cos,
2542 });
2543 }
2544 SceneNode::Border {
2545 rect,
2546 color,
2547 width,
2548 radius,
2549 } => {
2550 flush_if_prim_changed!("border", &self.borders);
2551 let (ndc, sin_cos) = rect_to_instance_ndc(
2552 *rect,
2553 current_transform,
2554 current_target_size.0,
2555 current_target_size.1,
2556 );
2557 batch.borders.push(BorderInstance {
2558 xywh: ndc,
2559 radius: *radius,
2560 stroke: *width,
2561 color: color.to_linear(),
2562 sin_cos,
2563 });
2564 }
2565 SceneNode::Ellipse { rect, brush } => {
2566 flush_if_prim_changed!("ellipse", &self.ellipses);
2567 let (ndc, sin_cos) = rect_to_instance_ndc(
2568 *rect,
2569 current_transform,
2570 current_target_size.0,
2571 current_target_size.1,
2572 );
2573 let color = brush_to_solid_color(brush);
2574 batch.ellipses.push(EllipseInstance {
2575 xywh: ndc,
2576 color,
2577 sin_cos,
2578 });
2579 }
2580 SceneNode::EllipseBorder { rect, color, width } => {
2581 flush_if_prim_changed!("ellipse_border", &self.ellipse_borders);
2582 let (ndc, sin_cos) = rect_to_instance_ndc(
2583 *rect,
2584 current_transform,
2585 current_target_size.0,
2586 current_target_size.1,
2587 );
2588 batch.e_borders.push(EllipseBorderInstance {
2589 xywh: ndc,
2590 stroke: *width,
2591 color: color.to_linear(),
2592 sin_cos,
2593 });
2594 }
2595 SceneNode::Text {
2596 rect,
2597 text,
2598 color,
2599 size,
2600 font_family,
2601 } => {
2602 flush_batch!(); let px = (*size).clamp(8.0, 96.0);
2605 let shaped = repose_text::shape_line(text.as_ref(), px, *font_family);
2606
2607 let cos_a = current_transform.rotate.cos();
2608 let sin_a = current_transform.rotate.sin();
2609 let has_rotation = current_transform.rotate != 0.0;
2610
2611 let pivot_x = rect.x + rect.w * 0.5;
2613 let pivot_y = rect.y + rect.h * 0.5;
2614
2615 let make_glyph_instance =
2617 |gx: f32, gy: f32, gw: f32, gh: f32| -> ([f32; 4], [f32; 2]) {
2618 if has_rotation {
2619 let corners =
2620 [(gx, gy), (gx + gw, gy), (gx + gw, gy + gh), (gx, gy + gh)];
2621 let mut min_x = f32::MAX;
2622 let mut max_x = f32::MIN;
2623 let mut min_y = f32::MAX;
2624 let mut max_y = f32::MIN;
2625 for &(x, y) in &corners {
2626 let dx = x - pivot_x;
2627 let dy = y - pivot_y;
2628 let rx = pivot_x + dx * cos_a - dy * sin_a;
2629 let ry = pivot_y + dx * sin_a + dy * cos_a;
2630 min_x = min_x.min(rx);
2631 max_x = max_x.max(rx);
2632 min_y = min_y.min(ry);
2633 max_y = max_y.max(ry);
2634 }
2635 let bb_w = max_x - min_x;
2636 let bb_h = max_y - min_y;
2637 let ndc_tl = to_ndc(
2638 min_x,
2639 min_y,
2640 bb_w,
2641 bb_h,
2642 current_target_size.0,
2643 current_target_size.1,
2644 );
2645 let ndc = [
2646 ndc_tl[0] + ndc_tl[2] * 0.5,
2647 ndc_tl[1] + ndc_tl[3] * 0.5,
2648 ndc_tl[2],
2649 ndc_tl[3],
2650 ];
2651 (ndc, [cos_a, sin_a])
2652 } else {
2653 rect_to_instance_ndc(
2654 repose_core::Rect {
2655 x: gx,
2656 y: gy,
2657 w: gw,
2658 h: gh,
2659 },
2660 current_transform,
2661 current_target_size.0,
2662 current_target_size.1,
2663 )
2664 }
2665 };
2666
2667 for sg in shaped {
2668 let gx = rect.x + sg.x + sg.bearing_x;
2669 let gy = rect.y + sg.y - sg.bearing_y;
2670
2671 {
2673 let ck = repose_text::lookup_cache_key(sg.key);
2674 if let Some(ref ck) = ck {
2675 if !self.slug_cache.contains(ck) {
2676 if let Some((ck2, commands)) =
2678 repose_text::lookup_and_extract_outline(sg.key)
2679 {
2680 let font_size_px = f32::from_bits(ck2.font_size_bits);
2681 self.slug_cache.get_or_insert(ck2, font_size_px, &commands);
2682 }
2683 }
2684 }
2685 if let Some(entry) = ck.as_ref().and_then(|ck| self.slug_cache.get(ck))
2686 {
2687 let ox = rect.x + sg.x;
2688 let oy = rect.y + sg.y;
2689 let scx = current_transform.scale_x;
2690 let scy = current_transform.scale_y;
2691 let ttx = current_transform.translate_x;
2692 let tty = current_transform.translate_y;
2693
2694 let tf = |x: f32, y: f32| -> (f32, f32) {
2695 let sx = x * scx;
2696 let sy = y * scy;
2697 (sx * cos_a - sy * sin_a + ttx, sx * sin_a + sy * cos_a + tty)
2698 };
2699
2700 let (tox, toy) = tf(ox, oy);
2701 let (tpx, tpy) = tf(pivot_x, pivot_y);
2702
2703 let b = &entry.band_data.bounds;
2704 let corners = [
2706 tf(ox + b[0] * px, oy - b[3] * px),
2707 tf(ox + b[2] * px, oy - b[3] * px),
2708 tf(ox + b[2] * px, oy - b[1] * px),
2709 tf(ox + b[0] * px, oy - b[1] * px),
2710 ];
2711 let (mut min_x, mut max_x, mut min_y, mut max_y) =
2712 (f32::MAX, f32::MIN, f32::MAX, f32::MIN);
2713 for &(x, y) in &corners {
2714 min_x = min_x.min(x);
2715 max_x = max_x.max(x);
2716 min_y = min_y.min(y);
2717 max_y = max_y.max(y);
2718 }
2719 let bb_w = max_x - min_x;
2720 let bb_h = max_y - min_y;
2721 let ndc_tl = to_ndc(
2722 min_x,
2723 min_y,
2724 bb_w,
2725 bb_h,
2726 current_target_size.0,
2727 current_target_size.1,
2728 );
2729 self.slug_instances.push(slug::SlugVertex {
2730 center: [
2731 ndc_tl[0] + ndc_tl[2] * 0.5,
2732 ndc_tl[1] + ndc_tl[3] * 0.5,
2733 ],
2734 size: [ndc_tl[2], ndc_tl[3]],
2735 color: color.to_linear(),
2736 glyph_base: entry.gpu_offset,
2737 origin_px: [tox, toy],
2738 pivot_px: [tpx, tpy],
2739 cos_sin: [cos_a, sin_a],
2740 });
2741 continue;
2742 }
2743 }
2744
2745 if let Some(info) = self.upload_glyph_color(sg.key, px as u32) {
2747 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
2748 batch.colors.push(GlyphInstance {
2749 xywh: ndc,
2750 uv: [info.u0, info.v1, info.u1, info.v0],
2751 color: color.to_linear(),
2752 sin_cos,
2753 });
2754 } else if let Some(info) = self.upload_glyph_mask(sg.key, px as u32) {
2755 let (ndc, sin_cos) = make_glyph_instance(gx, gy, info.w, info.h);
2756 batch.masks.push(GlyphInstance {
2757 xywh: ndc,
2758 uv: [info.u0, info.v1, info.u1, info.v0],
2759 color: color.to_linear(),
2760 sin_cos,
2761 });
2762 }
2763 }
2764
2765 if !self.slug_instances.is_empty() {
2767 let bytes = bytemuck::cast_slice(&self.slug_instances);
2768 self.slug_ring.grow_to_fit(&self.device, bytes.len() as u64);
2769 let (off, _) = self.slug_ring.alloc_write(&self.queue, bytes);
2770 current_pass.cmds.push(Cmd::GlyphsVector {
2771 off,
2772 cnt: self.slug_instances.len() as u32,
2773 });
2774 self.slug_instances.clear();
2775 }
2776 }
2777 SceneNode::Image {
2778 rect,
2779 handle,
2780 tint,
2781 fit,
2782 } => {
2783 flush_batch!();
2784
2785 let (img_w, img_h, is_nv12) = if let Some(t) = self.images.get_mut(handle) {
2787 match t {
2788 ImageTex::Rgba {
2789 w,
2790 h,
2791 last_used_frame,
2792 ..
2793 } => {
2794 *last_used_frame = self.frame_index;
2795 (*w, *h, false)
2796 }
2797 ImageTex::Nv12 {
2798 w,
2799 h,
2800 last_used_frame,
2801 ..
2802 } => {
2803 *last_used_frame = self.frame_index;
2804 (*w, *h, true)
2805 }
2806 }
2807 } else {
2808 log::warn!("Image handle {} not found", handle);
2809 continue;
2810 };
2811
2812 let src_w = img_w as f32;
2813 let src_h = img_h as f32;
2814 let transformed = current_transform.apply_to_rect(*rect);
2815 let dst_w = transformed.w.max(0.0);
2816 let dst_h = transformed.h.max(0.0);
2817 if dst_w <= 0.0 || dst_h <= 0.0 {
2818 continue;
2819 }
2820
2821 let (xywh_ndc, uv_rect) = match fit {
2822 repose_core::view::ImageFit::Contain => {
2823 let scale = (dst_w / src_w).min(dst_h / src_h);
2824 let w = src_w * scale;
2825 let h = src_h * scale;
2826 let x = transformed.x + (dst_w - w) * 0.5;
2827 let y = transformed.y + (dst_h - h) * 0.5;
2828 (
2829 to_ndc(x, y, w, h, current_target_size.0, current_target_size.1),
2830 [0.0, 1.0, 1.0, 0.0],
2831 )
2832 }
2833 repose_core::view::ImageFit::Cover => {
2834 let scale = (dst_w / src_w).max(dst_h / src_h);
2835 let content_w = src_w * scale;
2836 let content_h = src_h * scale;
2837 let overflow_x = (content_w - dst_w) * 0.5;
2838 let overflow_y = (content_h - dst_h) * 0.5;
2839 let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
2840 let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
2841 let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
2842 let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
2843 (
2844 to_ndc(
2845 transformed.x,
2846 transformed.y,
2847 dst_w,
2848 dst_h,
2849 current_target_size.0,
2850 current_target_size.1,
2851 ),
2852 [u0, 1.0 - v1, u1, 1.0 - v0],
2853 )
2854 }
2855 repose_core::view::ImageFit::FitWidth => {
2856 let scale = dst_w / src_w;
2857 let w = dst_w;
2858 let h = src_h * scale;
2859 let y = transformed.y + (dst_h - h) * 0.5;
2860 (
2861 to_ndc(
2862 transformed.x,
2863 y,
2864 w,
2865 h,
2866 current_target_size.0,
2867 current_target_size.1,
2868 ),
2869 [0.0, 1.0, 1.0, 0.0],
2870 )
2871 }
2872 repose_core::view::ImageFit::FitHeight => {
2873 let scale = dst_h / src_h;
2874 let w = src_w * scale;
2875 let h = dst_h;
2876 let x = transformed.x + (dst_w - w) * 0.5;
2877 (
2878 to_ndc(
2879 x,
2880 transformed.y,
2881 w,
2882 h,
2883 current_target_size.0,
2884 current_target_size.1,
2885 ),
2886 [0.0, 1.0, 1.0, 0.0],
2887 )
2888 }
2889 _ => ([0.0; 4], [0.0; 4]),
2890 };
2891
2892 let ndc_center = [
2894 xywh_ndc[0] + xywh_ndc[2] * 0.5,
2895 xywh_ndc[1] + xywh_ndc[3] * 0.5,
2896 xywh_ndc[2],
2897 xywh_ndc[3],
2898 ];
2899
2900 if is_nv12 {
2901 let full_range = if let Some(ImageTex::Nv12 { full_range, .. }) =
2902 self.images.get(handle)
2903 {
2904 if *full_range { 1.0 } else { 0.0 }
2905 } else {
2906 0.0
2907 };
2908
2909 let inst = Nv12Instance {
2910 xywh: ndc_center,
2911 uv: uv_rect,
2912 color: tint.to_linear(),
2913 full_range,
2914 sin_cos: [1.0, 0.0],
2915 _pad: [0.0],
2916 };
2917 if let Some((off, _)) = self.nv12.upload(&self.device, &self.queue, &[inst])
2918 {
2919 current_pass.cmds.push(Cmd::ImageNv12 {
2920 off,
2921 cnt: 1,
2922 handle: *handle,
2923 });
2924 }
2925 } else {
2926 let inst = GlyphInstance {
2928 xywh: ndc_center,
2929 uv: uv_rect,
2930 color: tint.to_linear(),
2931 sin_cos: [1.0, 0.0],
2932 };
2933 if let Some((off, _)) =
2934 self.glyph_color.upload(&self.device, &self.queue, &[inst])
2935 {
2936 current_pass.cmds.push(Cmd::ImageRgba {
2937 off,
2938 cnt: 1,
2939 handle: *handle,
2940 });
2941 }
2942 }
2943 }
2944 SceneNode::PushClip { rect, radius } => {
2945 flush_batch!(); let t_identity = Transform::identity();
2948 let current_transform = transform_stack.last().unwrap_or(&t_identity);
2949 let transformed = current_transform.apply_to_rect(*rect);
2950
2951 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
2952 let next_scissor = intersect(top, transformed);
2953 scissor_stack.push(next_scissor);
2954 let scissor = to_scissor(
2955 &next_scissor,
2956 current_target_size.0 as u32,
2957 current_target_size.1 as u32,
2958 );
2959
2960 let clip_ndc_tl = to_ndc(
2961 transformed.x,
2962 transformed.y,
2963 transformed.w,
2964 transformed.h,
2965 current_target_size.0,
2966 current_target_size.1,
2967 );
2968 let inst = ClipInstance {
2969 xywh: [
2970 clip_ndc_tl[0] + clip_ndc_tl[2] * 0.5,
2971 clip_ndc_tl[1] + clip_ndc_tl[3] * 0.5,
2972 clip_ndc_tl[2],
2973 clip_ndc_tl[3],
2974 ],
2975 radius: *radius,
2976 sin_cos: [1.0, 0.0],
2977 };
2978 let bytes = bytemuck::bytes_of(&inst);
2979 self.clip_ring.grow_to_fit(&self.device, bytes.len() as u64);
2980 let (off, _) = self.clip_ring.alloc_write(&self.queue, bytes);
2981
2982 current_pass.cmds.push(Cmd::ClipPush {
2983 off,
2984 cnt: 1,
2985 scissor,
2986 });
2987 }
2988 SceneNode::PopClip => {
2989 flush_batch!();
2990
2991 if !scissor_stack.is_empty() {
2992 scissor_stack.pop();
2993 } else {
2994 log::warn!("PopClip with empty stack");
2995 }
2996
2997 let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
2998 let scissor = to_scissor(
2999 &top,
3000 current_target_size.0 as u32,
3001 current_target_size.1 as u32,
3002 );
3003 current_pass.cmds.push(Cmd::ClipPop { scissor });
3004 }
3005 SceneNode::Shadow {
3006 rect,
3007 radius,
3008 elevation: _,
3009 color,
3010 } => {
3011 flush_if_prim_changed!("rect", &self.rects);
3012 let (ndc, sin_cos) = rect_to_instance_ndc(
3013 *rect,
3014 current_transform,
3015 current_target_size.0,
3016 current_target_size.1,
3017 );
3018 let (brush_type, color0, _color1, _grad_start, _grad_end) =
3019 brush_to_instance_fields(&Brush::Solid(*color));
3020 batch.rects.push(RectInstance {
3021 xywh: ndc,
3022 radius: *radius,
3023 brush_type,
3024 color0,
3025 color1: [0.0; 4],
3026 grad_start: [0.0; 2],
3027 grad_end: [0.0; 2],
3028 sin_cos,
3029 });
3030 }
3031 SceneNode::PushTransform { transform } => {
3032 flush_batch!(); let combined = current_transform.combine(transform);
3034 transform_stack.push(combined);
3035 }
3036 SceneNode::PopTransform => {
3037 flush_batch!(); transform_stack.pop();
3039 }
3040 SceneNode::BeginLayer {
3041 rect,
3042 layer_id,
3043 alpha,
3044 } => {
3045 flush_batch!();
3046 let w = (rect.w.max(1.0)).ceil() as u32;
3047 let h = (rect.h.max(1.0)).ceil() as u32;
3048 let prev_target = current_pass.target;
3050 let prev_scissor = current_pass.initial_scissor;
3051 let saved = std::mem::replace(
3052 &mut current_pass,
3053 Pass {
3054 target: PassTarget::Layer(*layer_id),
3055 initial_scissor: (0, 0, w, h),
3056 clear_color: Some([0.0, 0.0, 0.0, 0.0]),
3057 cmds: Vec::new(),
3058 },
3059 );
3060 passes.push(saved);
3061 target_stack.push(prev_target);
3062 let _ = prev_scissor; self.get_or_create_layer(*layer_id, w, h, *rect);
3066 current_target_size = (w as f32, h as f32);
3067 layer_alphas.push((*layer_id, *alpha, current_pass.initial_scissor));
3068 }
3069 SceneNode::EndLayer { layer_id } => {
3070 flush_batch!();
3071 let saved = std::mem::replace(
3073 &mut current_pass,
3074 Pass {
3075 target: target_stack.pop().unwrap_or(PassTarget::Surface),
3076 initial_scissor: (0, 0, self.config.width, self.config.height),
3077 clear_color: None, cmds: Vec::new(),
3079 },
3080 );
3081 passes.push(saved);
3082 current_target_size = (fb_w, fb_h);
3083 if let Some((_, layer_alpha, _)) = layer_alphas
3085 .iter()
3086 .find(|(id, _, _)| id == layer_id)
3087 .copied()
3088 {
3089 let layer = self.layer_pool.get(layer_id).expect("layer target");
3090 let ndc_tl = to_ndc(
3091 layer.rect_px.0,
3092 layer.rect_px.1,
3093 layer.rect_px.2,
3094 layer.rect_px.3,
3095 fb_w,
3096 fb_h,
3097 );
3098 let inst = GlyphInstance {
3099 xywh: [
3100 ndc_tl[0] + ndc_tl[2] * 0.5,
3101 ndc_tl[1] + ndc_tl[3] * 0.5,
3102 ndc_tl[2],
3103 ndc_tl[3],
3104 ],
3105 uv: [0.0, 1.0, 1.0, 0.0],
3106 color: [1.0, 1.0, 1.0, layer_alpha],
3107 sin_cos: [1.0, 0.0],
3108 };
3109 if let Some((off, cnt)) =
3110 self.glyph_color.upload(&self.device, &self.queue, &[inst])
3111 {
3112 current_pass.cmds.push(Cmd::CompositeLayer {
3113 off,
3114 cnt,
3115 layer_id: *layer_id,
3116 alpha: layer_alpha,
3117 });
3118 }
3119 }
3120 }
3121 SceneNode::CompositeShadow {
3122 layer_id,
3123 blur_px,
3124 offset_px,
3125 color,
3126 } => {
3127 flush_batch!();
3128 if let Some(layer) = self.layer_pool.get(layer_id).cloned() {
3129 let sx = layer.rect_px.0 + offset_px.0;
3131 let sy = layer.rect_px.1 + offset_px.1;
3132 let sw = layer.rect_px.2;
3133 let sh = layer.rect_px.3;
3134 let bw_uv = (blur_px * 1.5) / layer.width.max(1) as f32;
3137 let bh_uv = (blur_px * 1.5) / layer.height.max(1) as f32;
3138 let ndc_tl = to_ndc(sx, sy, sw, sh, fb_w, fb_h);
3139 let inst = BlurInstance {
3140 xywh: [
3141 ndc_tl[0] + ndc_tl[2] * 0.5,
3142 ndc_tl[1] + ndc_tl[3] * 0.5,
3143 ndc_tl[2],
3144 ndc_tl[3],
3145 ],
3146 uv: [0.0, 0.0, 1.0, 1.0],
3147 color: [
3148 color.0 as f32 / 255.0,
3149 color.1 as f32 / 255.0,
3150 color.2 as f32 / 255.0,
3151 color.3 as f32 / 255.0,
3152 ],
3153 blur_uv: [bw_uv, bh_uv],
3154 sin_cos: [1.0, 0.0],
3155 };
3156 self.blur_ring
3157 .grow_to_fit(&self.device, std::mem::size_of::<BlurInstance>() as u64);
3158 let bytes = bytemuck::bytes_of(&inst);
3159 let (off, _) = self.blur_ring.alloc_write(&self.queue, bytes);
3160 current_pass.cmds.push(Cmd::CompositeShadow {
3161 off,
3162 cnt: 1,
3163 layer_id: *layer_id,
3164 });
3165 }
3166 }
3167 _ => {}
3168 }
3169 }
3170
3171 flush_batch!();
3172
3173 passes.push(current_pass);
3175
3176 self.slug_pipeline.upload(
3177 &self.device,
3178 &self.queue,
3179 &mut self.slug_cache,
3180 &mut self.slug_prev_generation,
3181 );
3182
3183 let mut encoder = self
3184 .device
3185 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3186 label: Some("frame encoder"),
3187 });
3188
3189 let bind_mask = self.atlas_bind_group_mask();
3190 let bind_color = self.atlas_bind_group_color();
3191 let mut clip_depth: u32 = 0;
3192
3193 for pass in std::mem::take(&mut passes) {
3194 let (color_view, resolve_target, depth_stencil_view, is_layer) = match pass.target {
3195 PassTarget::Surface => {
3196 let swap_view = frame
3197 .texture
3198 .create_view(&wgpu::TextureViewDescriptor::default());
3199 let (color, resolve) = if let Some(msaa_view) = &self.msaa_view {
3200 (msaa_view.clone(), Some(swap_view))
3201 } else {
3202 (swap_view, None)
3203 };
3204 (color, resolve, self.depth_stencil_view.clone(), false)
3205 }
3206 PassTarget::Layer(layer_id) => {
3207 if let Some(lt) = self.layer_pool.get(&layer_id) {
3208 (lt.view.clone(), None, lt.depth_stencil_view.clone(), true)
3209 } else {
3210 log::warn!("missing layer target {layer_id}");
3211 continue;
3212 }
3213 }
3214 };
3215
3216 if is_layer {
3217 clip_depth = 0;
3218 }
3219
3220 let pipes: &Pipelines = if is_layer {
3221 &self.layer_pipes
3222 } else {
3223 &self.surface_pipes
3224 };
3225
3226 let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3227 label: Some("pass"),
3228 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3229 view: &color_view,
3230 resolve_target: resolve_target.as_ref(),
3231 ops: wgpu::Operations {
3232 load: match pass.clear_color {
3233 Some(c) => wgpu::LoadOp::Clear(wgpu::Color {
3234 r: c[0] as f64,
3235 g: c[1] as f64,
3236 b: c[2] as f64,
3237 a: c[3] as f64,
3238 }),
3239 None => wgpu::LoadOp::Load,
3240 },
3241 store: wgpu::StoreOp::Store,
3242 },
3243 depth_slice: None,
3244 })],
3245 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
3246 view: &depth_stencil_view,
3247 depth_ops: None,
3248 stencil_ops: Some(wgpu::Operations {
3249 load: if is_layer || pass.clear_color.is_some() {
3250 wgpu::LoadOp::Clear(0)
3251 } else {
3252 wgpu::LoadOp::Load
3253 },
3254 store: wgpu::StoreOp::Store,
3255 }),
3256 }),
3257 timestamp_writes: None,
3258 occlusion_query_set: None,
3259 multiview_mask: None,
3260 });
3261
3262 rpass.set_bind_group(0, &self.globals_bind, &[]);
3263 rpass.set_stencil_reference(clip_depth);
3264 rpass.set_scissor_rect(
3265 pass.initial_scissor.0,
3266 pass.initial_scissor.1,
3267 pass.initial_scissor.2,
3268 pass.initial_scissor.3,
3269 );
3270
3271 macro_rules! draw_simple {
3272 ($pipeline:expr, $ring:expr, $inst:ty, $off:ident, $n:ident) => {{
3273 rpass.set_pipeline($pipeline);
3274 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
3275 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
3276 rpass.draw(0..6, 0..$n);
3277 }};
3278 }
3279
3280 macro_rules! draw_with_bind {
3281 ($pipeline:expr, $ring:expr, $inst:ty, $bind:expr, $off:ident, $n:ident) => {{
3282 rpass.set_pipeline($pipeline);
3283 rpass.set_bind_group(1, $bind, &[]);
3284 let bytes = ($n as u64) * std::mem::size_of::<$inst>() as u64;
3285 rpass.set_vertex_buffer(0, $ring.buf.slice($off..$off + bytes));
3286 rpass.draw(0..6, 0..$n);
3287 }};
3288 }
3289
3290 for cmd in pass.cmds {
3291 match cmd {
3292 Cmd::ClipPush {
3293 off,
3294 cnt: n,
3295 scissor,
3296 } => {
3297 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
3298 rpass.set_stencil_reference(clip_depth);
3299
3300 if self.msaa_samples > 1 && !is_layer {
3301 rpass.set_pipeline(&pipes.clip_a2c);
3302 } else {
3303 rpass.set_pipeline(&pipes.clip_bin);
3304 }
3305
3306 let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
3307 rpass.set_vertex_buffer(0, self.clip_ring.buf.slice(off..off + bytes));
3308 rpass.draw(0..6, 0..n);
3309
3310 clip_depth = (clip_depth + 1).min(255);
3311 rpass.set_stencil_reference(clip_depth);
3312 }
3313
3314 Cmd::ClipPop { scissor } => {
3315 clip_depth = clip_depth.saturating_sub(1);
3316 rpass.set_stencil_reference(clip_depth);
3317 rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
3318 }
3319
3320 Cmd::Rect { off, cnt: n } => {
3321 draw_simple!(&pipes.rects, self.rects.ring, RectInstance, off, n);
3322 }
3323
3324 Cmd::Border { off, cnt: n } => {
3325 draw_simple!(&pipes.borders, self.borders.ring, BorderInstance, off, n);
3326 }
3327
3328 Cmd::GlyphsMask { off, cnt: n } => {
3329 draw_with_bind!(
3330 &pipes.text_mask,
3331 self.glyph_mask.ring,
3332 GlyphInstance,
3333 &bind_mask,
3334 off,
3335 n
3336 );
3337 }
3338
3339 Cmd::GlyphsColor { off, cnt: n } => {
3340 draw_with_bind!(
3341 &pipes.text_color,
3342 self.glyph_color.ring,
3343 GlyphInstance,
3344 &bind_color,
3345 off,
3346 n
3347 );
3348 }
3349
3350 Cmd::GlyphsVector { off, cnt: n } => {
3351 rpass.set_pipeline(&pipes.slug);
3352 rpass.set_bind_group(1, self.slug_pipeline.bind_group(), &[]);
3353 let bytes = (n as u64) * std::mem::size_of::<slug::SlugVertex>() as u64;
3354 rpass.set_vertex_buffer(0, self.slug_ring.buf.slice(off..off + bytes));
3355 rpass.draw(0..6, 0..n);
3356 }
3357
3358 Cmd::ImageRgba {
3359 off,
3360 cnt: n,
3361 handle,
3362 } => {
3363 if let Some(ImageTex::Rgba { bind, .. }) = self.images.get(&handle) {
3364 draw_with_bind!(
3365 &pipes.image_rgba,
3366 self.glyph_color.ring,
3367 GlyphInstance,
3368 bind,
3369 off,
3370 n
3371 );
3372 }
3373 }
3374
3375 Cmd::ImageNv12 {
3376 off,
3377 cnt: n,
3378 handle,
3379 } => {
3380 if let Some(ImageTex::Nv12 { bind, .. }) = self.images.get(&handle) {
3381 draw_with_bind!(
3382 &pipes.image_nv12,
3383 self.nv12.ring,
3384 Nv12Instance,
3385 bind,
3386 off,
3387 n
3388 );
3389 }
3390 }
3391
3392 Cmd::Ellipse { off, cnt: n } => {
3393 draw_simple!(&pipes.ellipses, self.ellipses.ring, EllipseInstance, off, n);
3394 }
3395
3396 Cmd::EllipseBorder { off, cnt: n } => {
3397 draw_simple!(
3398 &pipes.ellipse_borders,
3399 self.ellipse_borders.ring,
3400 EllipseBorderInstance,
3401 off,
3402 n
3403 );
3404 }
3405
3406 Cmd::PushTransform(_) => {}
3407 Cmd::PopTransform => {}
3408 Cmd::CompositeLayer {
3409 off,
3410 cnt: n,
3411 layer_id,
3412 alpha: _,
3413 } => {
3414 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
3415 draw_with_bind!(
3416 &pipes.image_rgba,
3417 self.glyph_color.ring,
3418 GlyphInstance,
3419 <.bind,
3420 off,
3421 n
3422 );
3423 }
3424 }
3425 Cmd::CompositeShadow {
3426 off,
3427 cnt: n,
3428 layer_id,
3429 } => {
3430 if let Some(lt) = self.layer_pool.get(&layer_id).cloned() {
3431 draw_with_bind!(
3432 &pipes.blur,
3433 self.blur_ring,
3434 BlurInstance,
3435 <.bind,
3436 off,
3437 n
3438 );
3439 }
3440 }
3441 }
3442 }
3443 }
3444
3445 self.queue.submit(std::iter::once(encoder.finish()));
3446 if let Err(e) = catch_unwind(AssertUnwindSafe(|| frame.present())) {
3447 log::warn!("frame.present panicked: {:?}", e);
3448 }
3449
3450 self.evict_unused_images();
3452 }
3453}
3454
3455fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
3456 let x0 = a.x.max(b.x);
3457 let y0 = a.y.max(b.y);
3458 let x1 = (a.x + a.w).min(b.x + b.w);
3459 let y1 = (a.y + a.h).min(b.y + b.h);
3460 repose_core::Rect {
3461 x: x0,
3462 y: y0,
3463 w: (x1 - x0).max(0.0),
3464 h: (y1 - y0).max(0.0),
3465 }
3466}