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