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