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