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