repose_render_wgpu/
lib.rs

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    text_bind_layout: wgpu::BindGroupLayout,
88
89    // Stencil clip pipelines
90    clip_pipeline_a2c: wgpu::RenderPipeline,
91    clip_pipeline_bin: wgpu::RenderPipeline,
92    msaa_samples: u32,
93
94    // Depth-stencil target
95    depth_stencil_tex: wgpu::Texture,
96    depth_stencil_view: wgpu::TextureView,
97
98    // Optional MSAA color target
99    msaa_tex: Option<wgpu::Texture>,
100    msaa_view: Option<wgpu::TextureView>,
101
102    globals_layout: wgpu::BindGroupLayout,
103    globals_buf: wgpu::Buffer,
104    globals_bind: wgpu::BindGroup,
105
106    // Glyph atlas
107    atlas_mask: AtlasA8,
108    atlas_color: AtlasRGBA,
109
110    // per-frame upload rings
111    ring_rect: UploadRing,
112    ring_border: UploadRing,
113    ring_ellipse: UploadRing,
114    ring_ellipse_border: UploadRing,
115    ring_glyph_mask: UploadRing,
116    ring_glyph_color: UploadRing,
117    ring_clip: UploadRing,
118
119    next_image_handle: u64,
120    images: std::collections::HashMap<u64, ImageTex>,
121}
122
123struct ImageTex {
124    view: wgpu::TextureView,
125    bind: wgpu::BindGroup,
126    w: u32,
127    h: u32,
128}
129
130struct AtlasA8 {
131    tex: wgpu::Texture,
132    view: wgpu::TextureView,
133    sampler: wgpu::Sampler,
134    size: u32,
135    next_x: u32,
136    next_y: u32,
137    row_h: u32,
138    map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
139}
140
141struct AtlasRGBA {
142    tex: wgpu::Texture,
143    view: wgpu::TextureView,
144    sampler: wgpu::Sampler,
145    size: u32,
146    next_x: u32,
147    next_y: u32,
148    row_h: u32,
149    map: HashMap<(repose_text::GlyphKey, u32), GlyphInfo>,
150}
151
152#[derive(Clone, Copy)]
153struct GlyphInfo {
154    u0: f32,
155    v0: f32,
156    u1: f32,
157    v1: f32,
158    w: f32,
159    h: f32,
160    bearing_x: f32,
161    bearing_y: f32,
162    advance: f32,
163}
164
165#[repr(C)]
166#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
167struct RectInstance {
168    // xy in NDC, wh in NDC extents
169    xywh: [f32; 4],
170    // radius in NDC units
171    radius: f32,
172    brush_type: u32,
173    color0: [f32; 4],
174    color1: [f32; 4],
175    grad_start: [f32; 2],
176    grad_end: [f32; 2],
177}
178
179#[repr(C)]
180#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
181struct BorderInstance {
182    xywh: [f32; 4],
183    radius: f32,
184    stroke: f32,
185    color: [f32; 4],
186}
187
188#[repr(C)]
189#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
190struct EllipseInstance {
191    xywh: [f32; 4],
192    color: [f32; 4],
193}
194
195#[repr(C)]
196#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
197struct EllipseBorderInstance {
198    xywh: [f32; 4],
199    stroke: f32,
200    color: [f32; 4],
201}
202
203#[repr(C)]
204#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
205struct GlyphInstance {
206    xywh: [f32; 4],
207    uv: [f32; 4],
208    color: [f32; 4],
209}
210
211#[repr(C)]
212#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
213struct ClipInstance {
214    xywh: [f32; 4],
215    radius: f32,
216    _pad: [f32; 3],
217}
218
219fn swash_to_a8_coverage(content: cosmic_text::SwashContent, data: &[u8]) -> Option<Vec<u8>> {
220    match content {
221        cosmic_text::SwashContent::Mask => Some(data.to_vec()),
222        cosmic_text::SwashContent::SubpixelMask => {
223            let mut out = Vec::with_capacity(data.len() / 4);
224            for px in data.chunks_exact(4) {
225                let r = px[0];
226                let g = px[1];
227                let b = px[2];
228                out.push(r.max(g).max(b));
229            }
230            Some(out)
231        }
232        cosmic_text::SwashContent::Color => None,
233    }
234}
235
236impl WgpuBackend {
237    pub async fn new_async(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
238        let mut desc = wgpu::InstanceDescriptor::from_env_or_default();
239        let instance: Instance;
240
241        if cfg!(target_arch = "wasm32") {
242            desc.backends = wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL;
243            instance = wgpu::util::new_instance_with_webgpu_detection(&desc).await;
244        } else {
245            instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::from_env_or_default());
246        };
247
248        let surface = instance.create_surface(window.clone())?;
249
250        let adapter = instance
251            .request_adapter(&wgpu::RequestAdapterOptions {
252                power_preference: wgpu::PowerPreference::HighPerformance,
253                compatible_surface: Some(&surface),
254                force_fallback_adapter: false,
255            })
256            .await
257            .map_err(|e| anyhow::anyhow!("No suitable adapter: {e:?}"))?;
258
259        let limits = if cfg!(target_arch = "wasm32") {
260            wgpu::Limits::downlevel_webgl2_defaults()
261        } else {
262            wgpu::Limits::default()
263        };
264
265        let (device, queue) = adapter
266            .request_device(&wgpu::DeviceDescriptor {
267                label: Some("repose-rs device"),
268                required_features: wgpu::Features::empty(),
269                required_limits: limits,
270                experimental_features: wgpu::ExperimentalFeatures::disabled(),
271                memory_hints: wgpu::MemoryHints::default(),
272                trace: wgpu::Trace::Off,
273            })
274            .await
275            .map_err(|e| anyhow::anyhow!("request_device failed: {e:?}"))?;
276
277        let size = window.inner_size();
278
279        let caps = surface.get_capabilities(&adapter);
280        let format = caps
281            .formats
282            .iter()
283            .copied()
284            .find(|f| f.is_srgb())
285            .unwrap_or(caps.formats[0]);
286        let present_mode = caps
287            .present_modes
288            .iter()
289            .copied()
290            .find(|m| *m == wgpu::PresentMode::Mailbox || *m == wgpu::PresentMode::Immediate)
291            .unwrap_or(wgpu::PresentMode::Fifo);
292        let alpha_mode = caps.alpha_modes[0];
293
294        let config = wgpu::SurfaceConfiguration {
295            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
296            format,
297            width: size.width.max(1),
298            height: size.height.max(1),
299            present_mode,
300            alpha_mode,
301            view_formats: vec![],
302            desired_maximum_frame_latency: 2,
303        };
304        surface.configure(&device, &config);
305
306        let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
307            label: Some("globals layout"),
308            entries: &[wgpu::BindGroupLayoutEntry {
309                binding: 0,
310                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
311                ty: wgpu::BindingType::Buffer {
312                    ty: wgpu::BufferBindingType::Uniform,
313                    has_dynamic_offset: false,
314                    min_binding_size: None,
315                },
316                count: None,
317            }],
318        });
319
320        let globals_buf = device.create_buffer(&wgpu::BufferDescriptor {
321            label: Some("globals buf"),
322            size: std::mem::size_of::<Globals>() as u64,
323            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
324            mapped_at_creation: false,
325        });
326
327        let globals_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
328            label: Some("globals bind"),
329            layout: &globals_layout,
330            entries: &[wgpu::BindGroupEntry {
331                binding: 0,
332                resource: globals_buf.as_entire_binding(),
333            }],
334        });
335
336        // Pick MSAA sample count
337        let fmt_features = adapter.get_texture_format_features(format);
338        let msaa_samples = if fmt_features.flags.sample_count_supported(4)
339            && fmt_features
340                .flags
341                .contains(wgpu::TextureFormatFeatureFlags::MULTISAMPLE_RESOLVE)
342        {
343            4
344        } else {
345            1
346        };
347
348        let ds_format = wgpu::TextureFormat::Depth24PlusStencil8;
349
350        let stencil_for_content = wgpu::DepthStencilState {
351            format: ds_format,
352            depth_write_enabled: false,
353            depth_compare: wgpu::CompareFunction::Always,
354            stencil: wgpu::StencilState {
355                front: wgpu::StencilFaceState {
356                    compare: wgpu::CompareFunction::Equal,
357                    fail_op: wgpu::StencilOperation::Keep,
358                    depth_fail_op: wgpu::StencilOperation::Keep,
359                    pass_op: wgpu::StencilOperation::Keep,
360                },
361                back: wgpu::StencilFaceState {
362                    compare: wgpu::CompareFunction::Equal,
363                    fail_op: wgpu::StencilOperation::Keep,
364                    depth_fail_op: wgpu::StencilOperation::Keep,
365                    pass_op: wgpu::StencilOperation::Keep,
366                },
367                read_mask: 0xFF,
368                write_mask: 0x00,
369            },
370            bias: wgpu::DepthBiasState::default(),
371        };
372
373        let stencil_for_clip_inc = wgpu::DepthStencilState {
374            format: ds_format,
375            depth_write_enabled: false,
376            depth_compare: wgpu::CompareFunction::Always,
377            stencil: wgpu::StencilState {
378                front: wgpu::StencilFaceState {
379                    compare: wgpu::CompareFunction::Equal,
380                    fail_op: wgpu::StencilOperation::Keep,
381                    depth_fail_op: wgpu::StencilOperation::Keep,
382                    pass_op: wgpu::StencilOperation::IncrementClamp,
383                },
384                back: wgpu::StencilFaceState {
385                    compare: wgpu::CompareFunction::Equal,
386                    fail_op: wgpu::StencilOperation::Keep,
387                    depth_fail_op: wgpu::StencilOperation::Keep,
388                    pass_op: wgpu::StencilOperation::IncrementClamp,
389                },
390                read_mask: 0xFF,
391                write_mask: 0xFF,
392            },
393            bias: wgpu::DepthBiasState::default(),
394        };
395
396        let multisample_state = wgpu::MultisampleState {
397            count: msaa_samples,
398            mask: !0,
399            alpha_to_coverage_enabled: false,
400        };
401
402        // Rect pipeline
403        let rect_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
404            label: Some("rect.wgsl"),
405            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/rect.wgsl"))),
406        });
407        let rect_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
408            label: Some("rect pipeline layout"),
409            bind_group_layouts: &[&globals_layout],
410            immediate_size: 0,
411        });
412        let rect_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
413            label: Some("rect pipeline"),
414            layout: Some(&rect_pipeline_layout),
415            vertex: wgpu::VertexState {
416                module: &rect_shader,
417                entry_point: Some("vs_main"),
418                buffers: &[wgpu::VertexBufferLayout {
419                    array_stride: std::mem::size_of::<RectInstance>() as u64,
420                    step_mode: wgpu::VertexStepMode::Instance,
421                    attributes: &[
422                        wgpu::VertexAttribute {
423                            shader_location: 0,
424                            offset: 0,
425                            format: wgpu::VertexFormat::Float32x4,
426                        },
427                        wgpu::VertexAttribute {
428                            shader_location: 1,
429                            offset: 16,
430                            format: wgpu::VertexFormat::Float32,
431                        },
432                        wgpu::VertexAttribute {
433                            shader_location: 2,
434                            offset: 20,
435                            format: wgpu::VertexFormat::Uint32,
436                        },
437                        wgpu::VertexAttribute {
438                            shader_location: 3,
439                            offset: 24,
440                            format: wgpu::VertexFormat::Float32x4,
441                        },
442                        wgpu::VertexAttribute {
443                            shader_location: 4,
444                            offset: 40,
445                            format: wgpu::VertexFormat::Float32x4,
446                        },
447                        wgpu::VertexAttribute {
448                            shader_location: 5,
449                            offset: 56,
450                            format: wgpu::VertexFormat::Float32x2,
451                        },
452                        wgpu::VertexAttribute {
453                            shader_location: 6,
454                            offset: 64,
455                            format: wgpu::VertexFormat::Float32x2,
456                        },
457                    ],
458                }],
459                compilation_options: wgpu::PipelineCompilationOptions::default(),
460            },
461            fragment: Some(wgpu::FragmentState {
462                module: &rect_shader,
463                entry_point: Some("fs_main"),
464                targets: &[Some(wgpu::ColorTargetState {
465                    format: config.format,
466                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
467                    write_mask: wgpu::ColorWrites::ALL,
468                })],
469                compilation_options: wgpu::PipelineCompilationOptions::default(),
470            }),
471            primitive: wgpu::PrimitiveState::default(),
472            depth_stencil: Some(stencil_for_content.clone()),
473            multisample: multisample_state,
474            multiview_mask: None,
475            cache: None,
476        });
477
478        // Border pipeline
479        let border_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
480            label: Some("border.wgsl"),
481            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/border.wgsl"))),
482        });
483        let border_pipeline_layout =
484            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
485                label: Some("border pipeline layout"),
486                bind_group_layouts: &[&globals_layout],
487                immediate_size: 0,
488            });
489        let border_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
490            label: Some("border pipeline"),
491            layout: Some(&border_pipeline_layout),
492            vertex: wgpu::VertexState {
493                module: &border_shader,
494                entry_point: Some("vs_main"),
495                buffers: &[wgpu::VertexBufferLayout {
496                    array_stride: std::mem::size_of::<BorderInstance>() as u64,
497                    step_mode: wgpu::VertexStepMode::Instance,
498                    attributes: &[
499                        wgpu::VertexAttribute {
500                            shader_location: 0,
501                            offset: 0,
502                            format: wgpu::VertexFormat::Float32x4,
503                        },
504                        wgpu::VertexAttribute {
505                            shader_location: 1,
506                            offset: 16,
507                            format: wgpu::VertexFormat::Float32,
508                        },
509                        wgpu::VertexAttribute {
510                            shader_location: 2,
511                            offset: 20,
512                            format: wgpu::VertexFormat::Float32,
513                        },
514                        wgpu::VertexAttribute {
515                            shader_location: 3,
516                            offset: 24,
517                            format: wgpu::VertexFormat::Float32x4,
518                        },
519                    ],
520                }],
521                compilation_options: wgpu::PipelineCompilationOptions::default(),
522            },
523            fragment: Some(wgpu::FragmentState {
524                module: &border_shader,
525                entry_point: Some("fs_main"),
526                targets: &[Some(wgpu::ColorTargetState {
527                    format: config.format,
528                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
529                    write_mask: wgpu::ColorWrites::ALL,
530                })],
531                compilation_options: wgpu::PipelineCompilationOptions::default(),
532            }),
533            primitive: wgpu::PrimitiveState::default(),
534            depth_stencil: Some(stencil_for_content.clone()),
535            multisample: multisample_state,
536            multiview_mask: None,
537            cache: None,
538        });
539
540        // Ellipse pipeline
541        let ellipse_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
542            label: Some("ellipse.wgsl"),
543            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/ellipse.wgsl"))),
544        });
545        let ellipse_pipeline_layout =
546            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
547                label: Some("ellipse pipeline layout"),
548                bind_group_layouts: &[&globals_layout],
549                immediate_size: 0,
550            });
551        let ellipse_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
552            label: Some("ellipse pipeline"),
553            layout: Some(&ellipse_pipeline_layout),
554            vertex: wgpu::VertexState {
555                module: &ellipse_shader,
556                entry_point: Some("vs_main"),
557                buffers: &[wgpu::VertexBufferLayout {
558                    array_stride: std::mem::size_of::<EllipseInstance>() as u64,
559                    step_mode: wgpu::VertexStepMode::Instance,
560                    attributes: &[
561                        wgpu::VertexAttribute {
562                            shader_location: 0,
563                            offset: 0,
564                            format: wgpu::VertexFormat::Float32x4,
565                        },
566                        wgpu::VertexAttribute {
567                            shader_location: 1,
568                            offset: 16,
569                            format: wgpu::VertexFormat::Float32x4,
570                        },
571                    ],
572                }],
573                compilation_options: wgpu::PipelineCompilationOptions::default(),
574            },
575            fragment: Some(wgpu::FragmentState {
576                module: &ellipse_shader,
577                entry_point: Some("fs_main"),
578                targets: &[Some(wgpu::ColorTargetState {
579                    format: config.format,
580                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
581                    write_mask: wgpu::ColorWrites::ALL,
582                })],
583                compilation_options: wgpu::PipelineCompilationOptions::default(),
584            }),
585            primitive: wgpu::PrimitiveState::default(),
586            depth_stencil: Some(stencil_for_content.clone()),
587            multisample: multisample_state,
588            multiview_mask: None,
589            cache: None,
590        });
591
592        // Ellipse border pipeline
593        let ellipse_border_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
594            label: Some("ellipse_border.wgsl"),
595            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
596                "shaders/ellipse_border.wgsl"
597            ))),
598        });
599        let ellipse_border_layout =
600            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
601                label: Some("ellipse border layout"),
602                bind_group_layouts: &[&globals_layout],
603                immediate_size: 0,
604            });
605        let ellipse_border_pipeline =
606            device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
607                label: Some("ellipse border pipeline"),
608                layout: Some(&ellipse_border_layout),
609                vertex: wgpu::VertexState {
610                    module: &ellipse_border_shader,
611                    entry_point: Some("vs_main"),
612                    buffers: &[wgpu::VertexBufferLayout {
613                        array_stride: std::mem::size_of::<EllipseBorderInstance>() as u64,
614                        step_mode: wgpu::VertexStepMode::Instance,
615                        attributes: &[
616                            wgpu::VertexAttribute {
617                                shader_location: 0,
618                                offset: 0,
619                                format: wgpu::VertexFormat::Float32x4,
620                            },
621                            wgpu::VertexAttribute {
622                                shader_location: 1,
623                                offset: 16,
624                                format: wgpu::VertexFormat::Float32,
625                            },
626                            wgpu::VertexAttribute {
627                                shader_location: 2,
628                                offset: 20,
629                                format: wgpu::VertexFormat::Float32x4,
630                            },
631                        ],
632                    }],
633                    compilation_options: wgpu::PipelineCompilationOptions::default(),
634                },
635                fragment: Some(wgpu::FragmentState {
636                    module: &ellipse_border_shader,
637                    entry_point: Some("fs_main"),
638                    targets: &[Some(wgpu::ColorTargetState {
639                        format: config.format,
640                        blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
641                        write_mask: wgpu::ColorWrites::ALL,
642                    })],
643                    compilation_options: wgpu::PipelineCompilationOptions::default(),
644                }),
645                primitive: wgpu::PrimitiveState::default(),
646                depth_stencil: Some(stencil_for_content.clone()),
647                multisample: multisample_state,
648                multiview_mask: None,
649                cache: None,
650            });
651
652        // Text pipelines
653        let text_mask_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
654            label: Some("text.wgsl"),
655            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shaders/text.wgsl"))),
656        });
657        let text_color_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
658            label: Some("text_color.wgsl"),
659            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
660                "shaders/text_color.wgsl"
661            ))),
662        });
663        let text_bind_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
664            label: Some("text bind layout"),
665            entries: &[
666                wgpu::BindGroupLayoutEntry {
667                    binding: 0,
668                    visibility: wgpu::ShaderStages::FRAGMENT,
669                    ty: wgpu::BindingType::Texture {
670                        multisampled: false,
671                        view_dimension: wgpu::TextureViewDimension::D2,
672                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
673                    },
674                    count: None,
675                },
676                wgpu::BindGroupLayoutEntry {
677                    binding: 1,
678                    visibility: wgpu::ShaderStages::FRAGMENT,
679                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
680                    count: None,
681                },
682            ],
683        });
684        let text_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
685            label: Some("text pipeline layout"),
686            bind_group_layouts: &[&globals_layout, &text_bind_layout],
687            immediate_size: 0,
688        });
689        let text_pipeline_mask = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
690            label: Some("text pipeline (mask)"),
691            layout: Some(&text_pipeline_layout),
692            vertex: wgpu::VertexState {
693                module: &text_mask_shader,
694                entry_point: Some("vs_main"),
695                buffers: &[wgpu::VertexBufferLayout {
696                    array_stride: std::mem::size_of::<GlyphInstance>() as u64,
697                    step_mode: wgpu::VertexStepMode::Instance,
698                    attributes: &[
699                        wgpu::VertexAttribute {
700                            shader_location: 0,
701                            offset: 0,
702                            format: wgpu::VertexFormat::Float32x4,
703                        },
704                        wgpu::VertexAttribute {
705                            shader_location: 1,
706                            offset: 16,
707                            format: wgpu::VertexFormat::Float32x4,
708                        },
709                        wgpu::VertexAttribute {
710                            shader_location: 2,
711                            offset: 32,
712                            format: wgpu::VertexFormat::Float32x4,
713                        },
714                    ],
715                }],
716                compilation_options: wgpu::PipelineCompilationOptions::default(),
717            },
718            fragment: Some(wgpu::FragmentState {
719                module: &text_mask_shader,
720                entry_point: Some("fs_main"),
721                targets: &[Some(wgpu::ColorTargetState {
722                    format: config.format,
723                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
724                    write_mask: wgpu::ColorWrites::ALL,
725                })],
726                compilation_options: wgpu::PipelineCompilationOptions::default(),
727            }),
728            primitive: wgpu::PrimitiveState::default(),
729            depth_stencil: Some(stencil_for_content.clone()),
730            multisample: multisample_state,
731            multiview_mask: None,
732            cache: None,
733        });
734        let text_pipeline_color = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
735            label: Some("text pipeline (color)"),
736            layout: Some(&text_pipeline_layout),
737            vertex: wgpu::VertexState {
738                module: &text_color_shader,
739                entry_point: Some("vs_main"),
740                buffers: &[wgpu::VertexBufferLayout {
741                    array_stride: std::mem::size_of::<GlyphInstance>() as u64,
742                    step_mode: wgpu::VertexStepMode::Instance,
743                    attributes: &[
744                        wgpu::VertexAttribute {
745                            shader_location: 0,
746                            offset: 0,
747                            format: wgpu::VertexFormat::Float32x4,
748                        },
749                        wgpu::VertexAttribute {
750                            shader_location: 1,
751                            offset: 16,
752                            format: wgpu::VertexFormat::Float32x4,
753                        },
754                        wgpu::VertexAttribute {
755                            shader_location: 2,
756                            offset: 32,
757                            format: wgpu::VertexFormat::Float32x4,
758                        },
759                    ],
760                }],
761                compilation_options: wgpu::PipelineCompilationOptions::default(),
762            },
763            fragment: Some(wgpu::FragmentState {
764                module: &text_color_shader,
765                entry_point: Some("fs_main"),
766                targets: &[Some(wgpu::ColorTargetState {
767                    format: config.format,
768                    blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
769                    write_mask: wgpu::ColorWrites::ALL,
770                })],
771                compilation_options: wgpu::PipelineCompilationOptions::default(),
772            }),
773            primitive: wgpu::PrimitiveState::default(),
774            depth_stencil: Some(stencil_for_content.clone()),
775            multisample: multisample_state,
776            multiview_mask: None,
777            cache: None,
778        });
779
780        // Clip pipelines
781        let clip_shader_a2c = device.create_shader_module(wgpu::ShaderModuleDescriptor {
782            label: Some("clip_round_rect_a2c.wgsl"),
783            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
784                "shaders/clip_round_rect_a2c.wgsl"
785            ))),
786        });
787        let clip_shader_bin = device.create_shader_module(wgpu::ShaderModuleDescriptor {
788            label: Some("clip_round_rect_bin.wgsl"),
789            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!(
790                "shaders/clip_round_rect_bin.wgsl"
791            ))),
792        });
793
794        let clip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
795            label: Some("clip pipeline layout"),
796            bind_group_layouts: &[&globals_layout],
797            immediate_size: 0,
798        });
799
800        let clip_vertex_layout = wgpu::VertexBufferLayout {
801            array_stride: std::mem::size_of::<ClipInstance>() as u64,
802            step_mode: wgpu::VertexStepMode::Instance,
803            attributes: &[
804                wgpu::VertexAttribute {
805                    shader_location: 0,
806                    offset: 0,
807                    format: wgpu::VertexFormat::Float32x4,
808                },
809                wgpu::VertexAttribute {
810                    shader_location: 1,
811                    offset: 16,
812                    format: wgpu::VertexFormat::Float32,
813                },
814            ],
815        };
816
817        let clip_color_target = wgpu::ColorTargetState {
818            format: config.format,
819            blend: None,
820            write_mask: wgpu::ColorWrites::empty(),
821        };
822
823        let clip_pipeline_a2c = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
824            label: Some("clip pipeline (a2c)"),
825            layout: Some(&clip_pipeline_layout),
826            vertex: wgpu::VertexState {
827                module: &clip_shader_a2c,
828                entry_point: Some("vs_main"),
829                buffers: &[clip_vertex_layout.clone()],
830                compilation_options: wgpu::PipelineCompilationOptions::default(),
831            },
832            fragment: Some(wgpu::FragmentState {
833                module: &clip_shader_a2c,
834                entry_point: Some("fs_main"),
835                targets: &[Some(clip_color_target.clone())],
836                compilation_options: wgpu::PipelineCompilationOptions::default(),
837            }),
838            primitive: wgpu::PrimitiveState::default(),
839            depth_stencil: Some(stencil_for_clip_inc.clone()),
840            multisample: wgpu::MultisampleState {
841                count: msaa_samples,
842                mask: !0,
843                alpha_to_coverage_enabled: msaa_samples > 1,
844            },
845            multiview_mask: None,
846            cache: None,
847        });
848
849        let clip_pipeline_bin = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
850            label: Some("clip pipeline (bin)"),
851            layout: Some(&clip_pipeline_layout),
852            vertex: wgpu::VertexState {
853                module: &clip_shader_bin,
854                entry_point: Some("vs_main"),
855                buffers: &[clip_vertex_layout],
856                compilation_options: wgpu::PipelineCompilationOptions::default(),
857            },
858            fragment: Some(wgpu::FragmentState {
859                module: &clip_shader_bin,
860                entry_point: Some("fs_main"),
861                targets: &[Some(clip_color_target)],
862                compilation_options: wgpu::PipelineCompilationOptions::default(),
863            }),
864            primitive: wgpu::PrimitiveState::default(),
865            depth_stencil: Some(stencil_for_clip_inc),
866            multisample: wgpu::MultisampleState {
867                count: msaa_samples,
868                mask: !0,
869                alpha_to_coverage_enabled: false,
870            },
871            multiview_mask: None,
872            cache: None,
873        });
874
875        // Atlases
876        let atlas_mask = Self::init_atlas_mask(&device)?;
877        let atlas_color = Self::init_atlas_color(&device)?;
878
879        // Upload rings
880        let ring_rect = UploadRing::new(&device, "ring rect", 1 << 20);
881        let ring_border = UploadRing::new(&device, "ring border", 1 << 20);
882        let ring_ellipse = UploadRing::new(&device, "ring ellipse", 1 << 20);
883        let ring_ellipse_border = UploadRing::new(&device, "ring ellipse border", 1 << 20);
884        let ring_glyph_mask = UploadRing::new(&device, "ring glyph mask", 1 << 20);
885        let ring_glyph_color = UploadRing::new(&device, "ring glyph color", 1 << 20);
886        let ring_clip = UploadRing::new(&device, "ring clip", 1 << 16);
887
888        // Placeholder textures
889        let depth_stencil_tex = device.create_texture(&wgpu::TextureDescriptor {
890            label: Some("temp ds"),
891            size: wgpu::Extent3d {
892                width: 1,
893                height: 1,
894                depth_or_array_layers: 1,
895            },
896            mip_level_count: 1,
897            sample_count: 1,
898            dimension: wgpu::TextureDimension::D2,
899            format: wgpu::TextureFormat::Depth24PlusStencil8,
900            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
901            view_formats: &[],
902        });
903        let depth_stencil_view =
904            depth_stencil_tex.create_view(&wgpu::TextureViewDescriptor::default());
905
906        let mut backend = Self {
907            surface,
908            device,
909            queue,
910            config,
911            rect_pipeline,
912            border_pipeline,
913            text_pipeline_mask,
914            text_pipeline_color,
915            text_bind_layout,
916            ellipse_pipeline,
917            ellipse_border_pipeline,
918            atlas_mask,
919            atlas_color,
920            ring_rect,
921            ring_border,
922            ring_ellipse,
923            ring_ellipse_border,
924            ring_glyph_color,
925            ring_glyph_mask,
926            ring_clip,
927            next_image_handle: 1,
928            images: HashMap::new(),
929            clip_pipeline_a2c,
930            clip_pipeline_bin,
931            msaa_samples,
932            depth_stencil_tex,
933            depth_stencil_view,
934            msaa_tex: None,
935            msaa_view: None,
936            globals_bind,
937            globals_buf,
938            globals_layout,
939        };
940
941        backend.recreate_msaa_and_depth_stencil();
942        Ok(backend)
943    }
944
945    #[cfg(not(target_arch = "wasm32"))]
946    pub fn new(window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
947        pollster::block_on(Self::new_async(window))
948    }
949
950    #[cfg(target_arch = "wasm32")]
951    pub fn new(_window: Arc<winit::window::Window>) -> anyhow::Result<Self> {
952        anyhow::bail!("Use WgpuBackend::new_async(window).await on wasm32")
953    }
954
955    fn recreate_msaa_and_depth_stencil(&mut self) {
956        if self.msaa_samples > 1 {
957            let tex = self.device.create_texture(&wgpu::TextureDescriptor {
958                label: Some("msaa color"),
959                size: wgpu::Extent3d {
960                    width: self.config.width.max(1),
961                    height: self.config.height.max(1),
962                    depth_or_array_layers: 1,
963                },
964                mip_level_count: 1,
965                sample_count: self.msaa_samples,
966                dimension: wgpu::TextureDimension::D2,
967                format: self.config.format,
968                usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
969                view_formats: &[],
970            });
971            let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
972            self.msaa_tex = Some(tex);
973            self.msaa_view = Some(view);
974        } else {
975            self.msaa_tex = None;
976            self.msaa_view = None;
977        }
978
979        self.depth_stencil_tex = self.device.create_texture(&wgpu::TextureDescriptor {
980            label: Some("depth-stencil (stencil clips)"),
981            size: wgpu::Extent3d {
982                width: self.config.width.max(1),
983                height: self.config.height.max(1),
984                depth_or_array_layers: 1,
985            },
986            mip_level_count: 1,
987            sample_count: self.msaa_samples,
988            dimension: wgpu::TextureDimension::D2,
989            format: wgpu::TextureFormat::Depth24PlusStencil8,
990            usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
991            view_formats: &[],
992        });
993        self.depth_stencil_view = self
994            .depth_stencil_tex
995            .create_view(&wgpu::TextureViewDescriptor::default());
996    }
997
998    pub fn register_image_from_bytes(&mut self, data: &[u8], srgb: bool) -> u64 {
999        let img = image::load_from_memory(data).expect("decode image");
1000        let rgba = img.to_rgba8();
1001        let (w, h) = rgba.dimensions();
1002        let format = if srgb {
1003            wgpu::TextureFormat::Rgba8UnormSrgb
1004        } else {
1005            wgpu::TextureFormat::Rgba8Unorm
1006        };
1007        let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1008            label: Some("user image"),
1009            size: wgpu::Extent3d {
1010                width: w,
1011                height: h,
1012                depth_or_array_layers: 1,
1013            },
1014            mip_level_count: 1,
1015            sample_count: 1,
1016            dimension: wgpu::TextureDimension::D2,
1017            format,
1018            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1019            view_formats: &[],
1020        });
1021        self.queue.write_texture(
1022            wgpu::TexelCopyTextureInfoBase {
1023                texture: &tex,
1024                mip_level: 0,
1025                origin: wgpu::Origin3d::ZERO,
1026                aspect: wgpu::TextureAspect::All,
1027            },
1028            &rgba,
1029            wgpu::TexelCopyBufferLayout {
1030                offset: 0,
1031                bytes_per_row: Some(4 * w),
1032                rows_per_image: Some(h),
1033            },
1034            wgpu::Extent3d {
1035                width: w,
1036                height: h,
1037                depth_or_array_layers: 1,
1038            },
1039        );
1040        let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1041        let bind = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1042            label: Some("image bind"),
1043            layout: &self.text_bind_layout,
1044            entries: &[
1045                wgpu::BindGroupEntry {
1046                    binding: 0,
1047                    resource: wgpu::BindingResource::TextureView(&view),
1048                },
1049                wgpu::BindGroupEntry {
1050                    binding: 1,
1051                    resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
1052                },
1053            ],
1054        });
1055        let handle = self.next_image_handle;
1056        self.next_image_handle += 1;
1057        self.images.insert(handle, ImageTex { view, bind, w, h });
1058        handle
1059    }
1060
1061    fn init_atlas_mask(device: &wgpu::Device) -> anyhow::Result<AtlasA8> {
1062        let size = 1024u32;
1063        let tex = device.create_texture(&wgpu::TextureDescriptor {
1064            label: Some("glyph atlas A8"),
1065            size: wgpu::Extent3d {
1066                width: size,
1067                height: size,
1068                depth_or_array_layers: 1,
1069            },
1070            mip_level_count: 1,
1071            sample_count: 1,
1072            dimension: wgpu::TextureDimension::D2,
1073            format: wgpu::TextureFormat::R8Unorm,
1074            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1075            view_formats: &[],
1076        });
1077        let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1078        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1079            label: Some("glyph atlas sampler A8"),
1080            address_mode_u: wgpu::AddressMode::ClampToEdge,
1081            address_mode_v: wgpu::AddressMode::ClampToEdge,
1082            address_mode_w: wgpu::AddressMode::ClampToEdge,
1083            mag_filter: wgpu::FilterMode::Linear,
1084            min_filter: wgpu::FilterMode::Linear,
1085            mipmap_filter: wgpu::MipmapFilterMode::Linear,
1086            ..Default::default()
1087        });
1088
1089        Ok(AtlasA8 {
1090            tex,
1091            view,
1092            sampler,
1093            size,
1094            next_x: 1,
1095            next_y: 1,
1096            row_h: 0,
1097            map: HashMap::new(),
1098        })
1099    }
1100
1101    fn init_atlas_color(device: &wgpu::Device) -> anyhow::Result<AtlasRGBA> {
1102        let size = 1024u32;
1103        let tex = device.create_texture(&wgpu::TextureDescriptor {
1104            label: Some("glyph atlas RGBA"),
1105            size: wgpu::Extent3d {
1106                width: size,
1107                height: size,
1108                depth_or_array_layers: 1,
1109            },
1110            mip_level_count: 1,
1111            sample_count: 1,
1112            dimension: wgpu::TextureDimension::D2,
1113            format: wgpu::TextureFormat::Rgba8UnormSrgb,
1114            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1115            view_formats: &[],
1116        });
1117        let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
1118        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1119            label: Some("glyph atlas sampler RGBA"),
1120            address_mode_u: wgpu::AddressMode::ClampToEdge,
1121            address_mode_v: wgpu::AddressMode::ClampToEdge,
1122            address_mode_w: wgpu::AddressMode::ClampToEdge,
1123            mag_filter: wgpu::FilterMode::Linear,
1124            min_filter: wgpu::FilterMode::Linear,
1125            mipmap_filter: wgpu::MipmapFilterMode::Linear,
1126            ..Default::default()
1127        });
1128        Ok(AtlasRGBA {
1129            tex,
1130            view,
1131            sampler,
1132            size,
1133            next_x: 1,
1134            next_y: 1,
1135            row_h: 0,
1136            map: HashMap::new(),
1137        })
1138    }
1139
1140    fn atlas_bind_group_mask(&self) -> wgpu::BindGroup {
1141        self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1142            label: Some("atlas bind"),
1143            layout: &self.text_bind_layout,
1144            entries: &[
1145                wgpu::BindGroupEntry {
1146                    binding: 0,
1147                    resource: wgpu::BindingResource::TextureView(&self.atlas_mask.view),
1148                },
1149                wgpu::BindGroupEntry {
1150                    binding: 1,
1151                    resource: wgpu::BindingResource::Sampler(&self.atlas_mask.sampler),
1152                },
1153            ],
1154        })
1155    }
1156
1157    fn atlas_bind_group_color(&self) -> wgpu::BindGroup {
1158        self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1159            label: Some("atlas bind color"),
1160            layout: &self.text_bind_layout,
1161            entries: &[
1162                wgpu::BindGroupEntry {
1163                    binding: 0,
1164                    resource: wgpu::BindingResource::TextureView(&self.atlas_color.view),
1165                },
1166                wgpu::BindGroupEntry {
1167                    binding: 1,
1168                    resource: wgpu::BindingResource::Sampler(&self.atlas_color.sampler),
1169                },
1170            ],
1171        })
1172    }
1173
1174    fn upload_glyph_mask(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
1175        let keyp = (key, px);
1176        if let Some(info) = self.atlas_mask.map.get(&keyp) {
1177            return Some(*info);
1178        }
1179
1180        let gb = repose_text::rasterize(key, px as f32)?;
1181        if gb.w == 0 || gb.h == 0 || gb.data.is_empty() {
1182            return None;
1183        }
1184
1185        let coverage = match swash_to_a8_coverage(gb.content, &gb.data) {
1186            Some(c) => c,
1187            None => return None,
1188        };
1189
1190        let w = gb.w.max(1);
1191        let h = gb.h.max(1);
1192
1193        if !self.alloc_space_mask(w, h) {
1194            self.grow_mask_and_rebuild();
1195        }
1196        if !self.alloc_space_mask(w, h) {
1197            return None;
1198        }
1199        let x = self.atlas_mask.next_x;
1200        let y = self.atlas_mask.next_y;
1201        self.atlas_mask.next_x += w + 1;
1202        self.atlas_mask.row_h = self.atlas_mask.row_h.max(h + 1);
1203
1204        let layout = wgpu::TexelCopyBufferLayout {
1205            offset: 0,
1206            bytes_per_row: Some(w),
1207            rows_per_image: Some(h),
1208        };
1209        let size = wgpu::Extent3d {
1210            width: w,
1211            height: h,
1212            depth_or_array_layers: 1,
1213        };
1214        self.queue.write_texture(
1215            wgpu::TexelCopyTextureInfoBase {
1216                texture: &self.atlas_mask.tex,
1217                mip_level: 0,
1218                origin: wgpu::Origin3d { x, y, z: 0 },
1219                aspect: wgpu::TextureAspect::All,
1220            },
1221            &coverage,
1222            layout,
1223            size,
1224        );
1225
1226        let info = GlyphInfo {
1227            u0: x as f32 / self.atlas_mask.size as f32,
1228            v0: y as f32 / self.atlas_mask.size as f32,
1229            u1: (x + w) as f32 / self.atlas_mask.size as f32,
1230            v1: (y + h) as f32 / self.atlas_mask.size as f32,
1231            w: w as f32,
1232            h: h as f32,
1233            bearing_x: 0.0,
1234            bearing_y: 0.0,
1235            advance: 0.0,
1236        };
1237        self.atlas_mask.map.insert(keyp, info);
1238        Some(info)
1239    }
1240
1241    fn upload_glyph_color(&mut self, key: repose_text::GlyphKey, px: u32) -> Option<GlyphInfo> {
1242        let keyp = (key, px);
1243        if let Some(info) = self.atlas_color.map.get(&keyp) {
1244            return Some(*info);
1245        }
1246        let gb = repose_text::rasterize(key, px as f32)?;
1247        if !matches!(gb.content, cosmic_text::SwashContent::Color) {
1248            return None;
1249        }
1250        let w = gb.w.max(1);
1251        let h = gb.h.max(1);
1252        if !self.alloc_space_color(w, h) {
1253            self.grow_color_and_rebuild();
1254        }
1255        if !self.alloc_space_color(w, h) {
1256            return None;
1257        }
1258        let x = self.atlas_color.next_x;
1259        let y = self.atlas_color.next_y;
1260        self.atlas_color.next_x += w + 1;
1261        self.atlas_color.row_h = self.atlas_color.row_h.max(h + 1);
1262
1263        let layout = wgpu::TexelCopyBufferLayout {
1264            offset: 0,
1265            bytes_per_row: Some(w * 4),
1266            rows_per_image: Some(h),
1267        };
1268        let size = wgpu::Extent3d {
1269            width: w,
1270            height: h,
1271            depth_or_array_layers: 1,
1272        };
1273        self.queue.write_texture(
1274            wgpu::TexelCopyTextureInfoBase {
1275                texture: &self.atlas_color.tex,
1276                mip_level: 0,
1277                origin: wgpu::Origin3d { x, y, z: 0 },
1278                aspect: wgpu::TextureAspect::All,
1279            },
1280            &gb.data,
1281            layout,
1282            size,
1283        );
1284        let info = GlyphInfo {
1285            u0: x as f32 / self.atlas_color.size as f32,
1286            v0: y as f32 / self.atlas_color.size as f32,
1287            u1: (x + w) as f32 / self.atlas_color.size as f32,
1288            v1: (y + h) as f32 / self.atlas_color.size as f32,
1289            w: w as f32,
1290            h: h as f32,
1291            bearing_x: 0.0,
1292            bearing_y: 0.0,
1293            advance: 0.0,
1294        };
1295        self.atlas_color.map.insert(keyp, info);
1296        Some(info)
1297    }
1298
1299    fn alloc_space_mask(&mut self, w: u32, h: u32) -> bool {
1300        if self.atlas_mask.next_x + w + 1 >= self.atlas_mask.size {
1301            self.atlas_mask.next_x = 1;
1302            self.atlas_mask.next_y += self.atlas_mask.row_h + 1;
1303            self.atlas_mask.row_h = 0;
1304        }
1305        if self.atlas_mask.next_y + h + 1 >= self.atlas_mask.size {
1306            return false;
1307        }
1308        true
1309    }
1310
1311    fn grow_mask_and_rebuild(&mut self) {
1312        let new_size = (self.atlas_mask.size * 2).min(4096);
1313        if new_size == self.atlas_mask.size {
1314            return;
1315        }
1316        let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1317            label: Some("glyph atlas A8 (grown)"),
1318            size: wgpu::Extent3d {
1319                width: new_size,
1320                height: new_size,
1321                depth_or_array_layers: 1,
1322            },
1323            mip_level_count: 1,
1324            sample_count: 1,
1325            dimension: wgpu::TextureDimension::D2,
1326            format: wgpu::TextureFormat::R8Unorm,
1327            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1328            view_formats: &[],
1329        });
1330        self.atlas_mask.tex = tex;
1331        self.atlas_mask.view = self
1332            .atlas_mask
1333            .tex
1334            .create_view(&wgpu::TextureViewDescriptor::default());
1335        self.atlas_mask.size = new_size;
1336        self.atlas_mask.next_x = 1;
1337        self.atlas_mask.next_y = 1;
1338        self.atlas_mask.row_h = 0;
1339        let keys: Vec<(repose_text::GlyphKey, u32)> = self.atlas_mask.map.keys().copied().collect();
1340        self.atlas_mask.map.clear();
1341        for (k, px) in keys {
1342            let _ = self.upload_glyph_mask(k, px);
1343        }
1344    }
1345
1346    fn alloc_space_color(&mut self, w: u32, h: u32) -> bool {
1347        if self.atlas_color.next_x + w + 1 >= self.atlas_color.size {
1348            self.atlas_color.next_x = 1;
1349            self.atlas_color.next_y += self.atlas_color.row_h + 1;
1350            self.atlas_color.row_h = 0;
1351        }
1352        if self.atlas_color.next_y + h + 1 >= self.atlas_color.size {
1353            return false;
1354        }
1355        true
1356    }
1357
1358    fn grow_color_and_rebuild(&mut self) {
1359        let new_size = (self.atlas_color.size * 2).min(4096);
1360        if new_size == self.atlas_color.size {
1361            return;
1362        }
1363        let tex = self.device.create_texture(&wgpu::TextureDescriptor {
1364            label: Some("glyph atlas RGBA (grown)"),
1365            size: wgpu::Extent3d {
1366                width: new_size,
1367                height: new_size,
1368                depth_or_array_layers: 1,
1369            },
1370            mip_level_count: 1,
1371            sample_count: 1,
1372            dimension: wgpu::TextureDimension::D2,
1373            format: wgpu::TextureFormat::Rgba8UnormSrgb,
1374            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1375            view_formats: &[],
1376        });
1377        self.atlas_color.tex = tex;
1378        self.atlas_color.view = self
1379            .atlas_color
1380            .tex
1381            .create_view(&wgpu::TextureViewDescriptor::default());
1382        self.atlas_color.size = new_size;
1383        self.atlas_color.next_x = 1;
1384        self.atlas_color.next_y = 1;
1385        self.atlas_color.row_h = 0;
1386        let keys: Vec<(repose_text::GlyphKey, u32)> =
1387            self.atlas_color.map.keys().copied().collect();
1388        self.atlas_color.map.clear();
1389        for (k, px) in keys {
1390            let _ = self.upload_glyph_color(k, px);
1391        }
1392    }
1393}
1394
1395fn brush_to_instance_fields(brush: &Brush) -> (u32, [f32; 4], [f32; 4], [f32; 2], [f32; 2]) {
1396    match brush {
1397        Brush::Solid(c) => (
1398            0u32,
1399            c.to_linear(),
1400            [0.0, 0.0, 0.0, 0.0],
1401            [0.0, 0.0],
1402            [0.0, 1.0],
1403        ),
1404        Brush::Linear {
1405            start,
1406            end,
1407            start_color,
1408            end_color,
1409        } => (
1410            1u32,
1411            start_color.to_linear(),
1412            end_color.to_linear(),
1413            [start.x, start.y],
1414            [end.x, end.y],
1415        ),
1416    }
1417}
1418
1419fn brush_to_solid_color(brush: &Brush) -> [f32; 4] {
1420    match brush {
1421        Brush::Solid(c) => c.to_linear(),
1422        Brush::Linear { start_color, .. } => start_color.to_linear(),
1423    }
1424}
1425
1426impl RenderBackend for WgpuBackend {
1427    fn configure_surface(&mut self, width: u32, height: u32) {
1428        if width == 0 || height == 0 {
1429            return;
1430        }
1431        self.config.width = width;
1432        self.config.height = height;
1433        self.surface.configure(&self.device, &self.config);
1434        self.recreate_msaa_and_depth_stencil();
1435    }
1436
1437    fn frame(&mut self, scene: &Scene, _glyph_cfg: GlyphRasterConfig) {
1438        if self.config.width == 0 || self.config.height == 0 {
1439            return;
1440        }
1441        let frame = loop {
1442            match self.surface.get_current_texture() {
1443                Ok(f) => break f,
1444                Err(wgpu::SurfaceError::Lost) => {
1445                    log::warn!("surface lost; reconfiguring");
1446                    self.surface.configure(&self.device, &self.config);
1447                }
1448                Err(wgpu::SurfaceError::Outdated) => {
1449                    log::warn!("surface outdated; reconfiguring");
1450                    self.surface.configure(&self.device, &self.config);
1451                }
1452                Err(wgpu::SurfaceError::Timeout) => {
1453                    log::warn!("surface timeout; retrying");
1454                    continue;
1455                }
1456                Err(wgpu::SurfaceError::OutOfMemory) => {
1457                    log::error!("surface OOM");
1458                    return;
1459                }
1460                Err(wgpu::SurfaceError::Other) => {
1461                    log::error!("Other error");
1462                    return;
1463                }
1464            }
1465        };
1466
1467        fn to_ndc(x: f32, y: f32, w: f32, h: f32, fb_w: f32, fb_h: f32) -> [f32; 4] {
1468            let x0 = (x / fb_w) * 2.0 - 1.0;
1469            let y0 = 1.0 - (y / fb_h) * 2.0;
1470            let x1 = ((x + w) / fb_w) * 2.0 - 1.0;
1471            let y1 = 1.0 - ((y + h) / fb_h) * 2.0;
1472            let min_x = x0.min(x1);
1473            let min_y = y0.min(y1);
1474            let w_ndc = (x1 - x0).abs();
1475            let h_ndc = (y1 - y0).abs();
1476            [min_x, min_y, w_ndc, h_ndc]
1477        }
1478
1479        fn to_ndc_scalar(px: f32, fb_dim: f32) -> f32 {
1480            (px / fb_dim) * 2.0
1481        }
1482
1483        fn to_scissor(r: &repose_core::Rect, fb_w: u32, fb_h: u32) -> (u32, u32, u32, u32) {
1484            let mut x = r.x.floor() as i64;
1485            let mut y = r.y.floor() as i64;
1486            let fb_wi = fb_w as i64;
1487            let fb_hi = fb_h as i64;
1488            x = x.clamp(0, fb_wi.saturating_sub(1));
1489            y = y.clamp(0, fb_hi.saturating_sub(1));
1490            let w_req = r.w.ceil().max(1.0) as i64;
1491            let h_req = r.h.ceil().max(1.0) as i64;
1492            let w = (w_req).min(fb_wi - x).max(1);
1493            let h = (h_req).min(fb_hi - y).max(1);
1494            (x as u32, y as u32, w as u32, h as u32)
1495        }
1496
1497        let fb_w = self.config.width as f32;
1498        let fb_h = self.config.height as f32;
1499
1500        let globals = Globals {
1501            ndc_to_px: [fb_w * 0.5, fb_h * 0.5],
1502            _pad: [0.0, 0.0],
1503        };
1504        self.queue
1505            .write_buffer(&self.globals_buf, 0, bytemuck::bytes_of(&globals));
1506
1507        enum Cmd {
1508            ClipPush {
1509                off: u64,
1510                cnt: u32,
1511                scissor: (u32, u32, u32, u32),
1512            },
1513            ClipPop {
1514                scissor: (u32, u32, u32, u32),
1515            },
1516            Rect {
1517                off: u64,
1518                cnt: u32,
1519            },
1520            Border {
1521                off: u64,
1522                cnt: u32,
1523            },
1524            Ellipse {
1525                off: u64,
1526                cnt: u32,
1527            },
1528            EllipseBorder {
1529                off: u64,
1530                cnt: u32,
1531            },
1532            GlyphsMask {
1533                off: u64,
1534                cnt: u32,
1535            },
1536            GlyphsColor {
1537                off: u64,
1538                cnt: u32,
1539            },
1540            Image {
1541                off: u64,
1542                cnt: u32,
1543                handle: u64,
1544            },
1545            PushTransform(Transform),
1546            PopTransform,
1547        }
1548
1549        let mut cmds: Vec<Cmd> = Vec::with_capacity(scene.nodes.len());
1550
1551        struct Batch {
1552            rects: Vec<RectInstance>,
1553            borders: Vec<BorderInstance>,
1554            ellipses: Vec<EllipseInstance>,
1555            e_borders: Vec<EllipseBorderInstance>,
1556            masks: Vec<GlyphInstance>,
1557            colors: Vec<GlyphInstance>,
1558        }
1559
1560        impl Batch {
1561            fn new() -> Self {
1562                Self {
1563                    rects: vec![],
1564                    borders: vec![],
1565                    ellipses: vec![],
1566                    e_borders: vec![],
1567                    masks: vec![],
1568                    colors: vec![],
1569                }
1570            }
1571
1572            fn flush(
1573                &mut self,
1574                rings: (
1575                    &mut UploadRing,
1576                    &mut UploadRing,
1577                    &mut UploadRing,
1578                    &mut UploadRing,
1579                    &mut UploadRing,
1580                    &mut UploadRing,
1581                ),
1582                device: &wgpu::Device,
1583                queue: &wgpu::Queue,
1584                cmds: &mut Vec<Cmd>,
1585            ) {
1586                let (
1587                    ring_rect,
1588                    ring_border,
1589                    ring_ellipse,
1590                    ring_ellipse_border,
1591                    ring_mask,
1592                    ring_color,
1593                ) = rings;
1594
1595                if !self.rects.is_empty() {
1596                    let bytes = bytemuck::cast_slice(&self.rects);
1597                    ring_rect.grow_to_fit(device, bytes.len() as u64);
1598                    let (off, wrote) = ring_rect.alloc_write(queue, bytes);
1599                    debug_assert_eq!(wrote as usize, bytes.len());
1600                    cmds.push(Cmd::Rect {
1601                        off,
1602                        cnt: self.rects.len() as u32,
1603                    });
1604                    self.rects.clear();
1605                }
1606                if !self.borders.is_empty() {
1607                    let bytes = bytemuck::cast_slice(&self.borders);
1608                    ring_border.grow_to_fit(device, bytes.len() as u64);
1609                    let (off, wrote) = ring_border.alloc_write(queue, bytes);
1610                    debug_assert_eq!(wrote as usize, bytes.len());
1611                    cmds.push(Cmd::Border {
1612                        off,
1613                        cnt: self.borders.len() as u32,
1614                    });
1615                    self.borders.clear();
1616                }
1617                if !self.ellipses.is_empty() {
1618                    let bytes = bytemuck::cast_slice(&self.ellipses);
1619                    ring_ellipse.grow_to_fit(device, bytes.len() as u64);
1620                    let (off, wrote) = ring_ellipse.alloc_write(queue, bytes);
1621                    debug_assert_eq!(wrote as usize, bytes.len());
1622                    cmds.push(Cmd::Ellipse {
1623                        off,
1624                        cnt: self.ellipses.len() as u32,
1625                    });
1626                    self.ellipses.clear();
1627                }
1628                if !self.e_borders.is_empty() {
1629                    let bytes = bytemuck::cast_slice(&self.e_borders);
1630                    ring_ellipse_border.grow_to_fit(device, bytes.len() as u64);
1631                    let (off, wrote) = ring_ellipse_border.alloc_write(queue, bytes);
1632                    debug_assert_eq!(wrote as usize, bytes.len());
1633                    cmds.push(Cmd::EllipseBorder {
1634                        off,
1635                        cnt: self.e_borders.len() as u32,
1636                    });
1637                    self.e_borders.clear();
1638                }
1639                if !self.masks.is_empty() {
1640                    let bytes = bytemuck::cast_slice(&self.masks);
1641                    ring_mask.grow_to_fit(device, bytes.len() as u64);
1642                    let (off, wrote) = ring_mask.alloc_write(queue, bytes);
1643                    debug_assert_eq!(wrote as usize, bytes.len());
1644                    cmds.push(Cmd::GlyphsMask {
1645                        off,
1646                        cnt: self.masks.len() as u32,
1647                    });
1648                    self.masks.clear();
1649                }
1650                if !self.colors.is_empty() {
1651                    let bytes = bytemuck::cast_slice(&self.colors);
1652                    ring_color.grow_to_fit(device, bytes.len() as u64);
1653                    let (off, wrote) = ring_color.alloc_write(queue, bytes);
1654                    debug_assert_eq!(wrote as usize, bytes.len());
1655                    cmds.push(Cmd::GlyphsColor {
1656                        off,
1657                        cnt: self.colors.len() as u32,
1658                    });
1659                    self.colors.clear();
1660                }
1661            }
1662        }
1663
1664        self.ring_rect.reset();
1665        self.ring_border.reset();
1666        self.ring_ellipse.reset();
1667        self.ring_ellipse_border.reset();
1668        self.ring_glyph_mask.reset();
1669        self.ring_glyph_color.reset();
1670        self.ring_clip.reset();
1671
1672        let mut batch = Batch::new();
1673        let mut transform_stack: Vec<Transform> = vec![Transform::identity()];
1674        let mut scissor_stack: Vec<repose_core::Rect> = Vec::with_capacity(8);
1675        let root_clip_rect = repose_core::Rect {
1676            x: 0.0,
1677            y: 0.0,
1678            w: fb_w,
1679            h: fb_h,
1680        };
1681
1682        for node in &scene.nodes {
1683            let t_identity = Transform::identity();
1684            let current_transform = transform_stack.last().unwrap_or(&t_identity);
1685
1686            match node {
1687                SceneNode::Rect {
1688                    rect,
1689                    brush,
1690                    radius,
1691                } => {
1692                    let transformed_rect = current_transform.apply_to_rect(*rect);
1693                    let (brush_type, color0, color1, grad_start, grad_end) =
1694                        brush_to_instance_fields(brush);
1695                    batch.rects.push(RectInstance {
1696                        xywh: to_ndc(
1697                            transformed_rect.x,
1698                            transformed_rect.y,
1699                            transformed_rect.w,
1700                            transformed_rect.h,
1701                            fb_w,
1702                            fb_h,
1703                        ),
1704                        radius: *radius,
1705                        brush_type,
1706                        color0,
1707                        color1,
1708                        grad_start,
1709                        grad_end,
1710                    });
1711                }
1712                SceneNode::Border {
1713                    rect,
1714                    color,
1715                    width,
1716                    radius,
1717                } => {
1718                    let transformed_rect = current_transform.apply_to_rect(*rect);
1719                    batch.borders.push(BorderInstance {
1720                        xywh: to_ndc(
1721                            transformed_rect.x,
1722                            transformed_rect.y,
1723                            transformed_rect.w,
1724                            transformed_rect.h,
1725                            fb_w,
1726                            fb_h,
1727                        ),
1728                        radius: *radius,
1729                        stroke: *width,
1730                        color: color.to_linear(),
1731                    });
1732                }
1733                SceneNode::Ellipse { rect, brush } => {
1734                    let transformed = current_transform.apply_to_rect(*rect);
1735                    let color = brush_to_solid_color(brush);
1736                    batch.ellipses.push(EllipseInstance {
1737                        xywh: to_ndc(
1738                            transformed.x,
1739                            transformed.y,
1740                            transformed.w,
1741                            transformed.h,
1742                            fb_w,
1743                            fb_h,
1744                        ),
1745                        color,
1746                    });
1747                }
1748                SceneNode::EllipseBorder { rect, color, width } => {
1749                    let transformed = current_transform.apply_to_rect(*rect);
1750                    batch.e_borders.push(EllipseBorderInstance {
1751                        xywh: to_ndc(
1752                            transformed.x,
1753                            transformed.y,
1754                            transformed.w,
1755                            transformed.h,
1756                            fb_w,
1757                            fb_h,
1758                        ),
1759                        stroke: *width,
1760                        color: color.to_linear(),
1761                    });
1762                }
1763                SceneNode::Text {
1764                    rect,
1765                    text,
1766                    color,
1767                    size,
1768                } => {
1769                    let px = (*size).clamp(8.0, 96.0);
1770                    let shaped = repose_text::shape_line(text, px);
1771                    let transformed_rect = current_transform.apply_to_rect(*rect);
1772
1773                    for sg in shaped {
1774                        if let Some(info) = self.upload_glyph_color(sg.key, px as u32) {
1775                            let x = transformed_rect.x + sg.x + sg.bearing_x;
1776                            let y = transformed_rect.y + sg.y - sg.bearing_y;
1777                            batch.colors.push(GlyphInstance {
1778                                xywh: to_ndc(x, y, info.w, info.h, fb_w, fb_h),
1779                                uv: [info.u0, info.v1, info.u1, info.v0],
1780                                color: [1.0, 1.0, 1.0, 1.0],
1781                            });
1782                        } else if let Some(info) = self.upload_glyph_mask(sg.key, px as u32) {
1783                            let x = transformed_rect.x + sg.x + sg.bearing_x;
1784                            let y = transformed_rect.y + sg.y - sg.bearing_y;
1785                            batch.masks.push(GlyphInstance {
1786                                xywh: to_ndc(x, y, info.w, info.h, fb_w, fb_h),
1787                                uv: [info.u0, info.v1, info.u1, info.v0],
1788                                color: color.to_linear(),
1789                            });
1790                        }
1791                    }
1792                }
1793                SceneNode::Image {
1794                    rect,
1795                    handle,
1796                    tint,
1797                    fit,
1798                } => {
1799                    let tex = if let Some(t) = self.images.get(handle) {
1800                        t
1801                    } else {
1802                        log::warn!("Image handle {} not found", handle);
1803                        continue;
1804                    };
1805                    let src_w = tex.w as f32;
1806                    let src_h = tex.h as f32;
1807                    let dst_w = rect.w.max(0.0);
1808                    let dst_h = rect.h.max(0.0);
1809                    if dst_w <= 0.0 || dst_h <= 0.0 {
1810                        continue;
1811                    }
1812                    let (xywh_ndc, uv_rect) = match fit {
1813                        repose_core::view::ImageFit::Contain => {
1814                            let scale = (dst_w / src_w).min(dst_h / src_h);
1815                            let w = src_w * scale;
1816                            let h = src_h * scale;
1817                            let x = rect.x + (dst_w - w) * 0.5;
1818                            let y = rect.y + (dst_h - h) * 0.5;
1819                            (to_ndc(x, y, w, h, fb_w, fb_h), [0.0, 1.0, 1.0, 0.0])
1820                        }
1821                        repose_core::view::ImageFit::Cover => {
1822                            let scale = (dst_w / src_w).max(dst_h / src_h);
1823                            let content_w = src_w * scale;
1824                            let content_h = src_h * scale;
1825                            let overflow_x = (content_w - dst_w) * 0.5;
1826                            let overflow_y = (content_h - dst_h) * 0.5;
1827                            let u0 = (overflow_x / content_w).clamp(0.0, 1.0);
1828                            let v0 = (overflow_y / content_h).clamp(0.0, 1.0);
1829                            let u1 = ((overflow_x + dst_w) / content_w).clamp(0.0, 1.0);
1830                            let v1 = ((overflow_y + dst_h) / content_h).clamp(0.0, 1.0);
1831                            (
1832                                to_ndc(rect.x, rect.y, dst_w, dst_h, fb_w, fb_h),
1833                                [u0, 1.0 - v1, u1, 1.0 - v0],
1834                            )
1835                        }
1836                        repose_core::view::ImageFit::FitWidth => {
1837                            let scale = dst_w / src_w;
1838                            let w = dst_w;
1839                            let h = src_h * scale;
1840                            let y = rect.y + (dst_h - h) * 0.5;
1841                            (to_ndc(rect.x, y, w, h, fb_w, fb_h), [0.0, 1.0, 1.0, 0.0])
1842                        }
1843                        repose_core::view::ImageFit::FitHeight => {
1844                            let scale = dst_h / src_h;
1845                            let w = src_w * scale;
1846                            let h = dst_h;
1847                            let x = rect.x + (dst_w - w) * 0.5;
1848                            (to_ndc(x, rect.y, w, h, fb_w, fb_h), [0.0, 1.0, 1.0, 0.0])
1849                        }
1850                    };
1851                    let inst = GlyphInstance {
1852                        xywh: xywh_ndc,
1853                        uv: uv_rect,
1854                        color: tint.to_linear(),
1855                    };
1856                    let bytes = bytemuck::bytes_of(&inst);
1857                    self.ring_glyph_color
1858                        .grow_to_fit(&self.device, bytes.len() as u64);
1859                    let (off, wrote) = self.ring_glyph_color.alloc_write(&self.queue, bytes);
1860                    debug_assert_eq!(wrote as usize, bytes.len());
1861                    batch.flush(
1862                        (
1863                            &mut self.ring_rect,
1864                            &mut self.ring_border,
1865                            &mut self.ring_ellipse,
1866                            &mut self.ring_ellipse_border,
1867                            &mut self.ring_glyph_mask,
1868                            &mut self.ring_glyph_color,
1869                        ),
1870                        &self.device,
1871                        &self.queue,
1872                        &mut cmds,
1873                    );
1874                    cmds.push(Cmd::Image {
1875                        off,
1876                        cnt: 1,
1877                        handle: *handle,
1878                    });
1879                }
1880                SceneNode::PushClip { rect, radius } => {
1881                    batch.flush(
1882                        (
1883                            &mut self.ring_rect,
1884                            &mut self.ring_border,
1885                            &mut self.ring_ellipse,
1886                            &mut self.ring_ellipse_border,
1887                            &mut self.ring_glyph_mask,
1888                            &mut self.ring_glyph_color,
1889                        ),
1890                        &self.device,
1891                        &self.queue,
1892                        &mut cmds,
1893                    );
1894
1895                    let t_identity = Transform::identity();
1896                    let current_transform = transform_stack.last().unwrap_or(&t_identity);
1897                    let transformed = current_transform.apply_to_rect(*rect);
1898
1899                    let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
1900                    let next_scissor = intersect(top, transformed);
1901                    scissor_stack.push(next_scissor);
1902                    let scissor = to_scissor(&next_scissor, self.config.width, self.config.height);
1903
1904                    let inst = ClipInstance {
1905                        xywh: to_ndc(
1906                            transformed.x,
1907                            transformed.y,
1908                            transformed.w,
1909                            transformed.h,
1910                            fb_w,
1911                            fb_h,
1912                        ),
1913                        radius: *radius,
1914                        _pad: [0.0; 3],
1915                    };
1916                    let bytes = bytemuck::bytes_of(&inst);
1917                    self.ring_clip.grow_to_fit(&self.device, bytes.len() as u64);
1918                    let (off, wrote) = self.ring_clip.alloc_write(&self.queue, bytes);
1919                    debug_assert_eq!(wrote as usize, bytes.len());
1920
1921                    cmds.push(Cmd::ClipPush {
1922                        off,
1923                        cnt: 1,
1924                        scissor,
1925                    });
1926                }
1927                SceneNode::PopClip => {
1928                    batch.flush(
1929                        (
1930                            &mut self.ring_rect,
1931                            &mut self.ring_border,
1932                            &mut self.ring_ellipse,
1933                            &mut self.ring_ellipse_border,
1934                            &mut self.ring_glyph_mask,
1935                            &mut self.ring_glyph_color,
1936                        ),
1937                        &self.device,
1938                        &self.queue,
1939                        &mut cmds,
1940                    );
1941
1942                    if !scissor_stack.is_empty() {
1943                        scissor_stack.pop();
1944                    } else {
1945                        log::warn!("PopClip with empty stack");
1946                    }
1947
1948                    let top = scissor_stack.last().copied().unwrap_or(root_clip_rect);
1949                    let scissor = to_scissor(&top, self.config.width, self.config.height);
1950                    cmds.push(Cmd::ClipPop { scissor });
1951                }
1952                SceneNode::PushTransform { transform } => {
1953                    let combined = current_transform.combine(transform);
1954                    if transform.rotate != 0.0 {
1955                        ROT_WARN_ONCE.call_once(|| {
1956                            log::warn!(
1957                                "Transform rotation is not supported for Rect/Text/Image; rotation will be ignored."
1958                            );
1959                        });
1960                    }
1961                    transform_stack.push(combined);
1962                }
1963                SceneNode::PopTransform => {
1964                    transform_stack.pop();
1965                }
1966            }
1967        }
1968
1969        batch.flush(
1970            (
1971                &mut self.ring_rect,
1972                &mut self.ring_border,
1973                &mut self.ring_ellipse,
1974                &mut self.ring_ellipse_border,
1975                &mut self.ring_glyph_mask,
1976                &mut self.ring_glyph_color,
1977            ),
1978            &self.device,
1979            &self.queue,
1980            &mut cmds,
1981        );
1982
1983        let mut encoder = self
1984            .device
1985            .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1986                label: Some("frame encoder"),
1987            });
1988
1989        {
1990            let swap_view = frame
1991                .texture
1992                .create_view(&wgpu::TextureViewDescriptor::default());
1993
1994            let (color_view, resolve_target) = if let Some(msaa_view) = &self.msaa_view {
1995                (msaa_view, Some(&swap_view))
1996            } else {
1997                (&swap_view, None)
1998            };
1999
2000            let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
2001                label: Some("main pass"),
2002                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
2003                    view: color_view,
2004                    resolve_target,
2005                    ops: wgpu::Operations {
2006                        load: wgpu::LoadOp::Clear(wgpu::Color {
2007                            r: scene.clear_color.0 as f64 / 255.0,
2008                            g: scene.clear_color.1 as f64 / 255.0,
2009                            b: scene.clear_color.2 as f64 / 255.0,
2010                            a: scene.clear_color.3 as f64 / 255.0,
2011                        }),
2012                        store: wgpu::StoreOp::Store,
2013                    },
2014                    depth_slice: None,
2015                })],
2016                depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
2017                    view: &self.depth_stencil_view,
2018                    depth_ops: None,
2019                    stencil_ops: Some(wgpu::Operations {
2020                        load: wgpu::LoadOp::Clear(0),
2021                        store: wgpu::StoreOp::Store,
2022                    }),
2023                }),
2024                timestamp_writes: None,
2025                occlusion_query_set: None,
2026                multiview_mask: None,
2027            });
2028
2029            let mut clip_depth: u32 = 0;
2030            rpass.set_bind_group(0, &self.globals_bind, &[]);
2031            rpass.set_stencil_reference(clip_depth);
2032            rpass.set_scissor_rect(0, 0, self.config.width, self.config.height);
2033
2034            let bind_mask = self.atlas_bind_group_mask();
2035            let bind_color = self.atlas_bind_group_color();
2036
2037            for cmd in cmds {
2038                match cmd {
2039                    Cmd::ClipPush {
2040                        off,
2041                        cnt: n,
2042                        scissor,
2043                    } => {
2044                        rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2045                        rpass.set_stencil_reference(clip_depth);
2046
2047                        if self.msaa_samples > 1 {
2048                            rpass.set_pipeline(&self.clip_pipeline_a2c);
2049                        } else {
2050                            rpass.set_pipeline(&self.clip_pipeline_bin);
2051                        }
2052
2053                        let bytes = (n as u64) * std::mem::size_of::<ClipInstance>() as u64;
2054                        rpass.set_vertex_buffer(0, self.ring_clip.buf.slice(off..off + bytes));
2055                        rpass.draw(0..6, 0..n);
2056
2057                        clip_depth = (clip_depth + 1).min(255);
2058                        rpass.set_stencil_reference(clip_depth);
2059                    }
2060
2061                    Cmd::ClipPop { scissor } => {
2062                        clip_depth = clip_depth.saturating_sub(1);
2063                        rpass.set_stencil_reference(clip_depth);
2064                        rpass.set_scissor_rect(scissor.0, scissor.1, scissor.2, scissor.3);
2065                    }
2066
2067                    Cmd::Rect { off, cnt: n } => {
2068                        rpass.set_pipeline(&self.rect_pipeline);
2069                        let bytes = (n as u64) * std::mem::size_of::<RectInstance>() as u64;
2070                        rpass.set_vertex_buffer(0, self.ring_rect.buf.slice(off..off + bytes));
2071                        rpass.draw(0..6, 0..n);
2072                    }
2073
2074                    Cmd::Border { off, cnt: n } => {
2075                        rpass.set_pipeline(&self.border_pipeline);
2076                        let bytes = (n as u64) * std::mem::size_of::<BorderInstance>() as u64;
2077                        rpass.set_vertex_buffer(0, self.ring_border.buf.slice(off..off + bytes));
2078                        rpass.draw(0..6, 0..n);
2079                    }
2080
2081                    Cmd::GlyphsMask { off, cnt: n } => {
2082                        rpass.set_pipeline(&self.text_pipeline_mask);
2083                        rpass.set_bind_group(1, &bind_mask, &[]);
2084                        let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
2085                        rpass
2086                            .set_vertex_buffer(0, self.ring_glyph_mask.buf.slice(off..off + bytes));
2087                        rpass.draw(0..6, 0..n);
2088                    }
2089
2090                    Cmd::GlyphsColor { off, cnt: n } => {
2091                        rpass.set_pipeline(&self.text_pipeline_color);
2092                        rpass.set_bind_group(1, &bind_color, &[]);
2093                        let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
2094                        rpass.set_vertex_buffer(
2095                            0,
2096                            self.ring_glyph_color.buf.slice(off..off + bytes),
2097                        );
2098                        rpass.draw(0..6, 0..n);
2099                    }
2100
2101                    Cmd::Image {
2102                        off,
2103                        cnt: n,
2104                        handle,
2105                    } => {
2106                        if let Some(tex) = self.images.get(&handle) {
2107                            rpass.set_pipeline(&self.text_pipeline_color);
2108                            rpass.set_bind_group(1, &tex.bind, &[]);
2109                            let bytes = (n as u64) * std::mem::size_of::<GlyphInstance>() as u64;
2110                            rpass.set_vertex_buffer(
2111                                0,
2112                                self.ring_glyph_color.buf.slice(off..off + bytes),
2113                            );
2114                            rpass.draw(0..6, 0..n);
2115                        } else {
2116                            log::warn!("Image handle {} not found; skipping draw", handle);
2117                        }
2118                    }
2119
2120                    Cmd::Ellipse { off, cnt: n } => {
2121                        rpass.set_pipeline(&self.ellipse_pipeline);
2122                        let bytes = (n as u64) * std::mem::size_of::<EllipseInstance>() as u64;
2123                        rpass.set_vertex_buffer(0, self.ring_ellipse.buf.slice(off..off + bytes));
2124                        rpass.draw(0..6, 0..n);
2125                    }
2126
2127                    Cmd::EllipseBorder { off, cnt: n } => {
2128                        rpass.set_pipeline(&self.ellipse_border_pipeline);
2129                        let bytes =
2130                            (n as u64) * std::mem::size_of::<EllipseBorderInstance>() as u64;
2131                        rpass.set_vertex_buffer(
2132                            0,
2133                            self.ring_ellipse_border.buf.slice(off..off + bytes),
2134                        );
2135                        rpass.draw(0..6, 0..n);
2136                    }
2137
2138                    Cmd::PushTransform(_) => {}
2139                    Cmd::PopTransform => {}
2140                }
2141            }
2142        }
2143
2144        self.queue.submit(std::iter::once(encoder.finish()));
2145        if let Err(e) = catch_unwind(AssertUnwindSafe(|| frame.present())) {
2146            log::warn!("frame.present panicked: {:?}", e);
2147        }
2148    }
2149}
2150
2151fn intersect(a: repose_core::Rect, b: repose_core::Rect) -> repose_core::Rect {
2152    let x0 = a.x.max(b.x);
2153    let y0 = a.y.max(b.y);
2154    let x1 = (a.x + a.w).min(b.x + b.w);
2155    let y1 = (a.y + a.h).min(b.y + b.h);
2156    repose_core::Rect {
2157        x: x0,
2158        y: y0,
2159        w: (x1 - x0).max(0.0),
2160        h: (y1 - y0).max(0.0),
2161    }
2162}