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