Skip to main content

ravenrs_render/
renderer.rs

1use bytemuck::{Pod, Zeroable};
2use ravenrs_core::{Brush, Color, ImageQuad, Primitive, Quad, Scene};
3
4use crate::atlas::{GlyphAtlas, GlyphKey, Lookup, SUBPIXEL_BINS};
5use crate::images::ImageCache;
6use crate::paths::PathEngine;
7use crate::timing::GpuTimer;
8
9const INITIAL_ATLAS_SIZE: u32 = 1024;
10
11#[repr(C)]
12#[derive(Clone, Copy, Pod, Zeroable)]
13struct Globals {
14    viewport: [f32; 2],
15    scale: f32,
16    srgb_target: u32,
17}
18
19#[repr(C)]
20#[derive(Clone, Copy, Pod, Zeroable)]
21struct QuadInstance {
22    rect: [f32; 4],
23    radii: [f32; 4],
24    color0: [f32; 4],
25    color1: [f32; 4],
26    gradient: [f32; 4],
27    border_color: [f32; 4],
28    params: [f32; 4],
29}
30
31#[repr(C)]
32#[derive(Clone, Copy, Pod, Zeroable)]
33struct GlyphInstance {
34    rect: [f32; 4],
35    uv: [f32; 4],
36    color: [f32; 4],
37}
38
39#[repr(C)]
40#[derive(Clone, Copy, Pod, Zeroable)]
41struct ImageInstance {
42    rect: [f32; 4],
43    uv: [f32; 4],
44    radii: [f32; 4],
45    params: [f32; 4],
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49enum BatchKind {
50    Quads,
51    Glyphs,
52    /// Images of one texture, identified by image id.
53    Image(u64),
54    /// Shapes made of curves, on this page of the coverage atlas.
55    Paths(u32),
56}
57
58#[derive(Clone, Copy, Debug)]
59struct Batch {
60    kind: BatchKind,
61    start: u32,
62    end: u32,
63    /// Scissor rectangle in device pixels: x, y, width, height.
64    scissor: [u32; 4],
65}
66
67/// Counters for profiling and tests.
68#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
69pub struct RenderStats {
70    /// Quad instances drawn, shadows included.
71    pub quads: usize,
72    /// Glyphs drawn.
73    pub glyphs: usize,
74    /// Draw calls issued for the frame.
75    pub draw_calls: usize,
76    /// Glyphs resident in the atlas.
77    pub atlas_glyphs: usize,
78    /// Image quads drawn.
79    pub images: usize,
80    /// Image textures resident on the GPU.
81    pub cached_images: usize,
82    /// Shapes made of curves drawn.
83    pub paths: usize,
84    /// Straight pieces those curves were cut into.
85    pub path_edges: usize,
86    /// Draw calls split because the kind of primitive changed, as text after a quad.
87    pub breaks_kind: usize,
88    /// Draw calls split because the image changed.
89    pub breaks_image: usize,
90    /// Draw calls split because the clip changed.
91    pub breaks_clip: usize,
92    /// Bytes of instances sent to the GPU this frame.
93    pub uploaded_bytes: usize,
94    /// Glyphs rasterized since the renderer was created.
95    pub glyphs_rasterized: u64,
96    /// Times the glyph atlas lost everything: it grew or it was full.
97    pub atlas_resets: u64,
98    /// How full the glyph atlas is, in thousandths of its height.
99    pub atlas_used: u32,
100    /// Side of the glyph atlas in pixels.
101    pub atlas_size: u32,
102    /// How long the GPU took on a recent frame, when timing is on and it can tell.
103    /// It arrives some frames late, so it is not this frame's.
104    pub gpu_time: Option<std::time::Duration>,
105}
106
107/// A growable instance buffer that is reused across frames.
108struct InstanceBuffer {
109    buffer: wgpu::Buffer,
110    capacity: usize,
111    stride: usize,
112    label: &'static str,
113}
114
115impl RenderStats {
116    /// The part of these stats the diagnostics keep.
117    pub fn work(&self) -> ravenrs_core::RenderWork {
118        ravenrs_core::RenderWork {
119            breaks_kind: self.breaks_kind as u32,
120            breaks_image: self.breaks_image as u32,
121            breaks_clip: self.breaks_clip as u32,
122            uploaded_bytes: self.uploaded_bytes as u64,
123            glyphs_rasterized: self.glyphs_rasterized,
124            atlas_resets: self.atlas_resets,
125            atlas_used: self.atlas_used,
126            atlas_size: self.atlas_size,
127            cached_images: self.cached_images as u32,
128            gpu: self.gpu_time,
129        }
130    }
131}
132
133impl InstanceBuffer {
134    fn new(device: &wgpu::Device, label: &'static str, stride: usize) -> Self {
135        let capacity = 256;
136        Self { buffer: Self::create(device, label, stride * capacity), capacity, stride, label }
137    }
138
139    fn create(device: &wgpu::Device, label: &'static str, size: usize) -> wgpu::Buffer {
140        device.create_buffer(&wgpu::BufferDescriptor {
141            label: Some(label),
142            size: size as u64,
143            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
144            mapped_at_creation: false,
145        })
146    }
147
148    fn upload(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, bytes: &[u8]) {
149        let needed = bytes.len() / self.stride;
150        if needed > self.capacity {
151            self.capacity = needed.next_power_of_two();
152            self.buffer = Self::create(device, self.label, self.capacity * self.stride);
153        }
154        if !bytes.is_empty() {
155            queue.write_buffer(&self.buffer, 0, bytes);
156        }
157    }
158}
159
160/// Draws [`Scene`]s with wgpu. Create one per device; it is reused every frame.
161pub struct Renderer {
162    device: wgpu::Device,
163    queue: wgpu::Queue,
164    srgb_target: bool,
165    quad_pipeline: wgpu::RenderPipeline,
166    glyph_pipeline: wgpu::RenderPipeline,
167    image_pipeline: wgpu::RenderPipeline,
168    paths: PathEngine,
169    image_sampler: wgpu::Sampler,
170    image_cache: ImageCache,
171    image_buffer: InstanceBuffer,
172    image_instances: Vec<ImageInstance>,
173    globals_buffer: wgpu::Buffer,
174    globals_bind_group: wgpu::BindGroup,
175    atlas_layout: wgpu::BindGroupLayout,
176    atlas_bind_group: wgpu::BindGroup,
177    sampler: wgpu::Sampler,
178    atlas: GlyphAtlas,
179    quad_buffer: InstanceBuffer,
180    glyph_buffer: InstanceBuffer,
181    quads: Vec<QuadInstance>,
182    glyphs: Vec<GlyphInstance>,
183    batches: Vec<Batch>,
184    clips: Vec<[u32; 4]>,
185    target_size: (u32, u32),
186    breaks: BatchBreaks,
187    atlas_resets: u64,
188    /// Glyphs rasterized by the atlases that are gone.
189    glyphs_rasterized: u64,
190    timer: Option<GpuTimer>,
191}
192
193/// Why the last frame needed more than one draw call.
194#[derive(Clone, Copy, Debug, Default)]
195struct BatchBreaks {
196    kind: usize,
197    image: usize,
198    clip: usize,
199}
200
201impl std::fmt::Debug for Renderer {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        f.debug_struct("Renderer")
204            .field("srgb_target", &self.srgb_target)
205            .field("atlas_size", &self.atlas.size)
206            .finish_non_exhaustive()
207    }
208}
209
210fn premultiplied_target(format: wgpu::TextureFormat) -> [Option<wgpu::ColorTargetState>; 1] {
211    [Some(wgpu::ColorTargetState {
212        format,
213        blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA_BLENDING),
214        write_mask: wgpu::ColorWrites::ALL,
215    })]
216}
217
218pub(crate) fn pipeline(
219    device: &wgpu::Device,
220    label: &str,
221    source: &str,
222    layout: &wgpu::PipelineLayout,
223    instance_layout: wgpu::VertexBufferLayout<'_>,
224    format: wgpu::TextureFormat,
225) -> wgpu::RenderPipeline {
226    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
227        label: Some(label),
228        source: wgpu::ShaderSource::Wgsl(source.into()),
229    });
230    device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
231        label: Some(label),
232        layout: Some(layout),
233        vertex: wgpu::VertexState {
234            module: &module,
235            entry_point: Some("vs_main"),
236            compilation_options: Default::default(),
237            buffers: &[Some(instance_layout)],
238        },
239        primitive: wgpu::PrimitiveState {
240            topology: wgpu::PrimitiveTopology::TriangleStrip,
241            ..Default::default()
242        },
243        depth_stencil: None,
244        multisample: wgpu::MultisampleState::default(),
245        fragment: Some(wgpu::FragmentState {
246            module: &module,
247            entry_point: Some("fs_main"),
248            compilation_options: Default::default(),
249            targets: &premultiplied_target(format),
250        }),
251        multiview_mask: None,
252        cache: None,
253    })
254}
255
256impl Renderer {
257    /// Creates the pipelines for rendering into textures of `target_format`.
258    pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, target_format: wgpu::TextureFormat) -> Self {
259        let globals_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
260            label: Some("raven globals"),
261            entries: &[wgpu::BindGroupLayoutEntry {
262                binding: 0,
263                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
264                ty: wgpu::BindingType::Buffer {
265                    ty: wgpu::BufferBindingType::Uniform,
266                    has_dynamic_offset: false,
267                    min_binding_size: None,
268                },
269                count: None,
270            }],
271        });
272        let atlas_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
273            label: Some("raven atlas"),
274            entries: &[
275                wgpu::BindGroupLayoutEntry {
276                    binding: 0,
277                    visibility: wgpu::ShaderStages::FRAGMENT,
278                    ty: wgpu::BindingType::Texture {
279                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
280                        view_dimension: wgpu::TextureViewDimension::D2,
281                        multisampled: false,
282                    },
283                    count: None,
284                },
285                wgpu::BindGroupLayoutEntry {
286                    binding: 1,
287                    visibility: wgpu::ShaderStages::FRAGMENT,
288                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
289                    count: None,
290                },
291            ],
292        });
293        let globals_buffer = device.create_buffer(&wgpu::BufferDescriptor {
294            label: Some("raven globals"),
295            size: std::mem::size_of::<Globals>() as u64,
296            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
297            mapped_at_creation: false,
298        });
299        let globals_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
300            label: Some("raven globals"),
301            layout: &globals_layout,
302            entries: &[wgpu::BindGroupEntry { binding: 0, resource: globals_buffer.as_entire_binding() }],
303        });
304        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
305            label: Some("raven atlas sampler"),
306            mag_filter: wgpu::FilterMode::Linear,
307            min_filter: wgpu::FilterMode::Linear,
308            ..Default::default()
309        });
310        let atlas = GlyphAtlas::new(device, INITIAL_ATLAS_SIZE);
311        let atlas_bind_group = Self::atlas_bind_group(device, &atlas_layout, &atlas, &sampler);
312
313        let quad_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
314            label: Some("raven quads"),
315            bind_group_layouts: &[Some(&globals_layout)],
316            immediate_size: 0,
317        });
318        let glyph_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
319            label: Some("raven glyphs"),
320            bind_group_layouts: &[Some(&globals_layout), Some(&atlas_layout)],
321            immediate_size: 0,
322        });
323        let quad_attributes = wgpu::vertex_attr_array![
324            0 => Float32x4, 1 => Float32x4, 2 => Float32x4, 3 => Float32x4,
325            4 => Float32x4, 5 => Float32x4, 6 => Float32x4
326        ];
327        let glyph_attributes = wgpu::vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x4];
328        let image_attributes =
329            wgpu::vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x4, 3 => Float32x4];
330        let image_pipeline = pipeline(
331            device,
332            "raven image pipeline",
333            include_str!("../shaders/image.wgsl"),
334            &glyph_layout,
335            wgpu::VertexBufferLayout {
336                array_stride: std::mem::size_of::<ImageInstance>() as u64,
337                step_mode: wgpu::VertexStepMode::Instance,
338                attributes: &image_attributes,
339            },
340            target_format,
341        );
342        let image_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
343            label: Some("raven image sampler"),
344            mag_filter: wgpu::FilterMode::Linear,
345            min_filter: wgpu::FilterMode::Linear,
346            mipmap_filter: wgpu::MipmapFilterMode::Linear,
347            ..Default::default()
348        });
349        let quad_pipeline = pipeline(
350            device,
351            "raven quad pipeline",
352            include_str!("../shaders/quad.wgsl"),
353            &quad_layout,
354            wgpu::VertexBufferLayout {
355                array_stride: std::mem::size_of::<QuadInstance>() as u64,
356                step_mode: wgpu::VertexStepMode::Instance,
357                attributes: &quad_attributes,
358            },
359            target_format,
360        );
361        let glyph_pipeline = pipeline(
362            device,
363            "raven glyph pipeline",
364            include_str!("../shaders/glyph.wgsl"),
365            &glyph_layout,
366            wgpu::VertexBufferLayout {
367                array_stride: std::mem::size_of::<GlyphInstance>() as u64,
368                step_mode: wgpu::VertexStepMode::Instance,
369                attributes: &glyph_attributes,
370            },
371            target_format,
372        );
373
374        let paths = PathEngine::new(device, &globals_layout, target_format);
375
376        Self {
377            device: device.clone(),
378            queue: queue.clone(),
379            paths,
380            srgb_target: target_format.is_srgb(),
381            quad_pipeline,
382            glyph_pipeline,
383            image_pipeline,
384            image_sampler,
385            image_cache: ImageCache::default(),
386            image_buffer: InstanceBuffer::new(
387                device,
388                "raven image instances",
389                std::mem::size_of::<ImageInstance>(),
390            ),
391            image_instances: Vec::new(),
392            globals_buffer,
393            globals_bind_group,
394            atlas_layout,
395            atlas_bind_group,
396            sampler,
397            atlas,
398            quad_buffer: InstanceBuffer::new(
399                device,
400                "raven quad instances",
401                std::mem::size_of::<QuadInstance>(),
402            ),
403            glyph_buffer: InstanceBuffer::new(
404                device,
405                "raven glyph instances",
406                std::mem::size_of::<GlyphInstance>(),
407            ),
408            quads: Vec::new(),
409            glyphs: Vec::new(),
410            batches: Vec::new(),
411            clips: Vec::new(),
412            target_size: (1, 1),
413            breaks: BatchBreaks::default(),
414            atlas_resets: 0,
415            glyphs_rasterized: 0,
416            timer: None,
417        }
418    }
419
420    /// Starts or stops timing the GPU, reported in [`RenderStats::gpu_time`]. It stays
421    /// off unless asked for, and does nothing on a GPU that cannot tell the time.
422    pub fn set_gpu_timing(&mut self, on: bool) {
423        match (on, &self.timer) {
424            (true, None) => self.timer = GpuTimer::new(&self.device, &self.queue),
425            (false, Some(_)) => self.timer = None,
426            _ => {}
427        }
428    }
429
430    fn lose_atlas(&mut self, size: u32) {
431        self.glyphs_rasterized += self.atlas.rasterized();
432        self.atlas_resets += 1;
433        self.atlas = GlyphAtlas::new(&self.device, size);
434        self.atlas_bind_group =
435            Self::atlas_bind_group(&self.device, &self.atlas_layout, &self.atlas, &self.sampler);
436    }
437
438    fn atlas_bind_group(
439        device: &wgpu::Device,
440        layout: &wgpu::BindGroupLayout,
441        atlas: &GlyphAtlas,
442        sampler: &wgpu::Sampler,
443    ) -> wgpu::BindGroup {
444        device.create_bind_group(&wgpu::BindGroupDescriptor {
445            label: Some("raven atlas"),
446            layout,
447            entries: &[
448                wgpu::BindGroupEntry {
449                    binding: 0,
450                    resource: wgpu::BindingResource::TextureView(&atlas.view),
451                },
452                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(sampler) },
453            ],
454        })
455    }
456
457    /// Renders `scene` into `target`, a texture of `size` device pixels.
458    /// `scale` converts the scene's logical pixels to device pixels.
459    pub fn render(
460        &mut self,
461        scene: &Scene,
462        target: &wgpu::TextureView,
463        size: (u32, u32),
464        scale: f32,
465    ) -> RenderStats {
466        let _span = tracing::trace_span!("ravenrs::render", primitives = scene.len()).entered();
467        if size.0 == 0 || size.1 == 0 || scale <= 0.0 {
468            return RenderStats::default();
469        }
470        self.target_size = size;
471        self.image_cache.begin_frame();
472        self.prepare(scene, scale);
473
474        let globals = Globals {
475            viewport: [size.0 as f32 / scale, size.1 as f32 / scale],
476            scale,
477            srgb_target: u32::from(self.srgb_target),
478        };
479        self.queue.write_buffer(&self.globals_buffer, 0, bytemuck::bytes_of(&globals));
480        self.quad_buffer.upload(&self.device, &self.queue, bytemuck::cast_slice(&self.quads));
481        self.glyph_buffer.upload(&self.device, &self.queue, bytemuck::cast_slice(&self.glyphs));
482        self.image_buffer.upload(&self.device, &self.queue, bytemuck::cast_slice(&self.image_instances));
483
484        let clear = self.color_for_target(scene.clear_color());
485        let mut encoder = self
486            .device
487            .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("raven frame") });
488        // A page of shapes has to be drawn before the shapes that read it, and the main pass
489        // cannot stop for that, so a page after the first splits it in two.
490        self.paths.prepare(&self.device, &self.queue);
491        let coverage_timing = self.timer.as_ref().map(GpuTimer::coverage_pass);
492        self.paths.draw_page(&mut encoder, 0, coverage_timing);
493        let mut load = wgpu::LoadOp::Clear(wgpu::Color {
494            r: f64::from(clear[0]),
495            g: f64::from(clear[1]),
496            b: f64::from(clear[2]),
497            a: f64::from(clear[3]),
498        });
499        let (mut from, mut page) = (0, 0);
500        loop {
501            let next = self.batches[from..]
502                .iter()
503                .position(|batch| matches!(batch.kind, BatchKind::Paths(p) if p > page))
504                .map_or(self.batches.len(), |at| from + at);
505            let timing = if from == 0 { self.timer.as_ref().map(GpuTimer::main_pass) } else { None };
506            self.draw_main_pass(&mut encoder, target, size, load, from..next, timing);
507            let Some(BatchKind::Paths(after)) = self.batches.get(next).map(|batch| batch.kind) else { break };
508            self.paths.draw_page(&mut encoder, after, None);
509            load = wgpu::LoadOp::Load;
510            (from, page) = (next, after);
511        }
512        let gpu_time = self.timer.as_mut().and_then(|timer| timer.finish_frame(&mut encoder));
513        self.queue.submit(Some(encoder.finish()));
514        if let Some(timer) = &mut self.timer {
515            timer.frame_submitted();
516        }
517
518        RenderStats {
519            quads: self.quads.len(),
520            glyphs: self.glyphs.len(),
521            draw_calls: self.batches.len() + self.paths.pages(),
522            atlas_glyphs: self.atlas.len(),
523            images: self.image_instances.len(),
524            cached_images: self.image_cache.len(),
525            paths: self.paths.shapes(),
526            path_edges: self.paths.edges(),
527            breaks_kind: self.breaks.kind,
528            breaks_image: self.breaks.image,
529            breaks_clip: self.breaks.clip,
530            uploaded_bytes: std::mem::size_of_val(self.quads.as_slice())
531                + std::mem::size_of_val(self.glyphs.as_slice())
532                + std::mem::size_of_val(self.image_instances.as_slice())
533                + self.paths.uploaded_bytes(),
534            glyphs_rasterized: self.glyphs_rasterized + self.atlas.rasterized(),
535            atlas_resets: self.atlas_resets,
536            atlas_used: (self.atlas.used() * 1000.0) as u32,
537            atlas_size: self.atlas.size,
538            gpu_time,
539        }
540    }
541
542    /// Draws these batches on `target`.
543    fn draw_main_pass(
544        &self,
545        encoder: &mut wgpu::CommandEncoder,
546        target: &wgpu::TextureView,
547        size: (u32, u32),
548        load: wgpu::LoadOp<wgpu::Color>,
549        batches: std::ops::Range<usize>,
550        timestamp_writes: Option<wgpu::RenderPassTimestampWrites<'_>>,
551    ) {
552        let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
553            label: Some("raven main pass"),
554            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
555                view: target,
556                depth_slice: None,
557                resolve_target: None,
558                ops: wgpu::Operations { load, store: wgpu::StoreOp::Store },
559            })],
560            depth_stencil_attachment: None,
561            timestamp_writes,
562            occlusion_query_set: None,
563            multiview_mask: None,
564        });
565        pass.set_bind_group(0, &self.globals_bind_group, &[]);
566        let mut current = None;
567        let mut scissor = [0, 0, size.0, size.1];
568        for batch in &self.batches[batches] {
569            if batch.scissor != scissor {
570                scissor = batch.scissor;
571                pass.set_scissor_rect(scissor[0], scissor[1], scissor[2], scissor[3]);
572            }
573            if current != Some(batch.kind) {
574                match batch.kind {
575                    BatchKind::Quads => {
576                        pass.set_pipeline(&self.quad_pipeline);
577                        pass.set_vertex_buffer(0, self.quad_buffer.buffer.slice(..));
578                    }
579                    BatchKind::Glyphs => {
580                        pass.set_pipeline(&self.glyph_pipeline);
581                        pass.set_bind_group(1, &self.atlas_bind_group, &[]);
582                        pass.set_vertex_buffer(0, self.glyph_buffer.buffer.slice(..));
583                    }
584                    BatchKind::Image(id) => {
585                        let Some(image) = self.image_cache.get(id) else { continue };
586                        pass.set_pipeline(&self.image_pipeline);
587                        pass.set_bind_group(1, &image.bind_group, &[]);
588                        pass.set_vertex_buffer(0, self.image_buffer.buffer.slice(..));
589                    }
590                    BatchKind::Paths(_) => self.paths.bind(&mut pass),
591                }
592                current = Some(batch.kind);
593            }
594            pass.draw(0..4, batch.start..batch.end);
595        }
596    }
597
598    fn color_for_target(&self, c: Color) -> [f32; 4] {
599        let [r, g, b, a] = if self.srgb_target { c.to_linear() } else { c.to_array() };
600        [r * a, g * a, b * a, a]
601    }
602
603    /// Builds instance lists and batches. Grows the atlas and starts over if it fills up.
604    fn prepare(&mut self, scene: &Scene, scale: f32) {
605        let max_atlas = self.device.limits().max_texture_dimension_2d.min(8192);
606        loop {
607            match self.try_prepare(scene, scale) {
608                Ok(()) => return,
609                Err(AtlasFull) if self.atlas.size < max_atlas => {
610                    let size = (self.atlas.size * 2).min(max_atlas);
611                    tracing::debug!(size, "growing glyph atlas");
612                    self.lose_atlas(size);
613                }
614                Err(AtlasFull) => {
615                    // Already at the device limit: start from an empty atlas. Text in
616                    // this frame that still does not fit is skipped and logged.
617                    tracing::warn!("glyph atlas is full at the device limit; clearing it");
618                    self.lose_atlas(self.atlas.size);
619                    let _ = self.try_prepare_lossy(scene, scale);
620                    return;
621                }
622            }
623        }
624    }
625
626    fn try_prepare(&mut self, scene: &Scene, scale: f32) -> Result<(), AtlasFull> {
627        self.build(scene, scale, false)
628    }
629
630    fn try_prepare_lossy(&mut self, scene: &Scene, scale: f32) -> Result<(), AtlasFull> {
631        self.build(scene, scale, true)
632    }
633
634    fn build(&mut self, scene: &Scene, scale: f32, skip_missing: bool) -> Result<(), AtlasFull> {
635        self.quads.clear();
636        self.glyphs.clear();
637        self.image_instances.clear();
638        self.batches.clear();
639        self.clips.clear();
640        self.breaks = BatchBreaks::default();
641        self.paths.begin_frame();
642        for primitive in scene.primitives() {
643            match primitive {
644                Primitive::PushClip(rect) => {
645                    let (w, h) = self.target_size;
646                    let x0 = (rect.x() * scale).floor().clamp(0.0, w as f32) as u32;
647                    let y0 = (rect.y() * scale).floor().clamp(0.0, h as f32) as u32;
648                    let x1 = (rect.max_x() * scale).ceil().clamp(0.0, w as f32) as u32;
649                    let y1 = (rect.max_y() * scale).ceil().clamp(0.0, h as f32) as u32;
650                    let outer = self.current_scissor();
651                    let ix0 = x0.max(outer[0]);
652                    let iy0 = y0.max(outer[1]);
653                    let ix1 = x1.min(outer[0] + outer[2]).max(ix0);
654                    let iy1 = y1.min(outer[1] + outer[3]).max(iy0);
655                    self.clips.push([ix0, iy0, ix1 - ix0, iy1 - iy0]);
656                }
657                Primitive::PopClip => {
658                    self.clips.pop();
659                }
660                Primitive::Image(image) => self.push_image(image),
661                Primitive::Path(path) => {
662                    let start = self.paths.shapes() as u32;
663                    let scissor = self.current_scissor();
664                    let limit = self.device.limits().max_texture_dimension_2d.min(8192);
665                    self.paths.push(path, scale, scissor, limit);
666                    self.extend_batch(BatchKind::Paths(self.paths.page()), start, self.paths.shapes() as u32);
667                }
668                Primitive::Quad(quad) => {
669                    let start = self.quads.len() as u32;
670                    push_quad(&mut self.quads, quad);
671                    self.extend_batch(BatchKind::Quads, start, self.quads.len() as u32);
672                }
673                Primitive::Glyphs(run) => {
674                    let start = self.glyphs.len() as u32;
675                    let size_px = run.size * scale;
676                    let size_q = (size_px * 4.0).round().max(1.0) as u32;
677                    let color = run.color.to_array();
678                    let inv_atlas = 1.0 / self.atlas.size as f32;
679                    for g in &run.glyphs {
680                        let device_x = g.x * scale;
681                        let mut ix = device_x.floor();
682                        let mut bin = ((device_x - ix) * SUBPIXEL_BINS).round();
683                        if bin >= SUBPIXEL_BINS {
684                            ix += 1.0;
685                            bin = 0.0;
686                        }
687                        let key = GlyphKey {
688                            font_id: run.font.id,
689                            font_index: run.font.index,
690                            glyph: g.id,
691                            size_q,
692                            subpixel: bin as u8,
693                        };
694                        let entry = match self.atlas.get_or_insert(&self.queue, key, run.font.bytes()) {
695                            Lookup::Hit(entry) => entry,
696                            Lookup::Full if skip_missing => None,
697                            Lookup::Full => return Err(AtlasFull),
698                        };
699                        let Some(a) = entry else { continue };
700                        let device_y = (g.y * scale).round();
701                        self.glyphs.push(GlyphInstance {
702                            rect: [
703                                ix + a.left as f32,
704                                device_y - a.top as f32,
705                                a.width as f32,
706                                a.height as f32,
707                            ],
708                            uv: [
709                                a.x as f32 * inv_atlas,
710                                a.y as f32 * inv_atlas,
711                                (a.x + a.width) as f32 * inv_atlas,
712                                (a.y + a.height) as f32 * inv_atlas,
713                            ],
714                            color,
715                        });
716                    }
717                    self.extend_batch(BatchKind::Glyphs, start, self.glyphs.len() as u32);
718                }
719                // Primitives from a newer ravenrs-core this renderer does not know yet are skipped.
720                _ => {}
721            }
722        }
723        Ok(())
724    }
725
726    fn push_image(&mut self, image: &ImageQuad) {
727        let layout = &self.atlas_layout;
728        self.image_cache.prepare(&self.device, &self.queue, layout, &self.image_sampler, &image.image);
729        let start = self.image_instances.len() as u32;
730        let r = image.rect;
731        self.image_instances.push(ImageInstance {
732            rect: [r.x(), r.y(), r.width(), r.height()],
733            uv: image.uv,
734            radii: image.corner_radii,
735            params: [image.opacity.clamp(0.0, 1.0), 0.0, 0.0, 0.0],
736        });
737        self.extend_batch(BatchKind::Image(image.image.id()), start, start + 1);
738    }
739
740    fn current_scissor(&self) -> [u32; 4] {
741        self.clips.last().copied().unwrap_or([0, 0, self.target_size.0, self.target_size.1])
742    }
743
744    fn extend_batch(&mut self, kind: BatchKind, start: u32, end: u32) {
745        if start == end {
746            return;
747        }
748        let scissor = self.current_scissor();
749        // A fully clipped batch draws nothing; scissor rects must not be empty anyway.
750        if scissor[2] == 0 || scissor[3] == 0 {
751            return;
752        }
753        match self.batches.last_mut() {
754            Some(last) if last.kind == kind && last.end == start && last.scissor == scissor => last.end = end,
755            Some(last) => {
756                if last.scissor != scissor {
757                    self.breaks.clip += 1;
758                } else if matches!((last.kind, kind), (BatchKind::Image(_), BatchKind::Image(_))) {
759                    self.breaks.image += 1;
760                } else {
761                    self.breaks.kind += 1;
762                }
763                self.batches.push(Batch { kind, start, end, scissor });
764            }
765            None => self.batches.push(Batch { kind, start, end, scissor }),
766        }
767    }
768}
769
770struct AtlasFull;
771
772fn push_quad(out: &mut Vec<QuadInstance>, quad: &Quad) {
773    let r = quad.rect;
774    let rect = [r.x(), r.y(), r.width(), r.height()];
775    if let Some(shadow) = quad.shadow {
776        out.push(QuadInstance {
777            rect: [rect[0] + shadow.offset.x, rect[1] + shadow.offset.y, rect[2], rect[3]],
778            radii: quad.corner_radii,
779            color0: shadow.color.to_array(),
780            color1: [0.0; 4],
781            gradient: [0.0; 4],
782            border_color: [0.0; 4],
783            params: [0.0, shadow.blur * 0.5, 1.0, 0.0],
784        });
785    }
786    let (color0, color1, gradient, gradient_on) = match quad.fill {
787        Brush::Solid(c) => (c.to_array(), c.to_array(), [0.0; 4], 0.0),
788        Brush::LinearGradient(g) => {
789            (g.from.to_array(), g.to.to_array(), [g.start.x, g.start.y, g.end.x, g.end.y], 1.0)
790        }
791        // Brushes from a newer ravenrs-core are drawn transparent rather than wrong.
792        _ => ([0.0; 4], [0.0; 4], [0.0; 4], 0.0),
793    };
794    let (border_width, border_color) = quad.border.map_or((0.0, [0.0; 4]), |b| (b.width, b.color.to_array()));
795    out.push(QuadInstance {
796        rect,
797        radii: quad.corner_radii,
798        color0,
799        color1,
800        gradient,
801        border_color,
802        params: [border_width, 0.0, 0.0, gradient_on],
803    });
804}