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