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