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