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