rootvg_text/
pipeline.rs

1use std::cell::Ref;
2
3use glyphon::{SwashCache, TextArea, TextAtlas, TextRenderer};
4
5use rootvg_core::math::{PhysicalSizeI32, ScaleFactor};
6
7use crate::{primitive::TextPrimitive, WRITE_LOCK_PANIC_MSG};
8
9pub struct TextBatchBuffer {
10    text_renderer: TextRenderer,
11    prev_primitives: Vec<TextPrimitive>,
12}
13
14pub struct TextPipeline {
15    cache: SwashCache,
16    atlas: TextAtlas,
17    multisample: wgpu::MultisampleState,
18    screen_size: PhysicalSizeI32,
19    scale_factor: ScaleFactor,
20    prepare_all_batches: bool,
21    atlas_needs_trimmed: bool,
22}
23
24impl TextPipeline {
25    pub fn new(
26        device: &wgpu::Device,
27        queue: &wgpu::Queue,
28        format: wgpu::TextureFormat,
29        multisample: wgpu::MultisampleState,
30    ) -> Self {
31        let cache = SwashCache::new();
32        let atlas = TextAtlas::with_color_mode(device, queue, format, glyphon::ColorMode::Accurate);
33
34        Self {
35            cache,
36            atlas,
37            multisample,
38            screen_size: PhysicalSizeI32::default(),
39            scale_factor: ScaleFactor::default(),
40            prepare_all_batches: true,
41            atlas_needs_trimmed: false,
42        }
43    }
44
45    pub fn create_batch(&mut self, device: &wgpu::Device) -> TextBatchBuffer {
46        TextBatchBuffer {
47            text_renderer: TextRenderer::new(&mut self.atlas, device, self.multisample, None),
48            prev_primitives: Vec::new(),
49        }
50    }
51
52    pub fn start_preparations(
53        &mut self,
54        _device: &wgpu::Device,
55        _queue: &wgpu::Queue,
56        screen_size: PhysicalSizeI32,
57        scale_factor: ScaleFactor,
58    ) {
59        if self.screen_size == screen_size && self.scale_factor == scale_factor {
60            return;
61        }
62
63        self.screen_size = screen_size;
64        self.scale_factor = scale_factor;
65        self.prepare_all_batches = true;
66    }
67
68    pub fn prepare_batch(
69        &mut self,
70        batch: &mut TextBatchBuffer,
71        primitives: &[TextPrimitive],
72        device: &wgpu::Device,
73        queue: &wgpu::Queue,
74    ) -> Result<(), glyphon::PrepareError> {
75        // Don't prepare if the list of primitives hasn't changed since the last
76        // preparation.
77        let primitives_are_the_same = primitives == &batch.prev_primitives;
78        if primitives_are_the_same && !self.prepare_all_batches {
79            return Ok(());
80        }
81
82        if !primitives_are_the_same {
83            batch.prev_primitives = primitives.into();
84        }
85
86        self.atlas_needs_trimmed = true;
87
88        let mut font_system = crate::font_system().write().expect(WRITE_LOCK_PANIC_MSG);
89
90        // TODO: Reuse the allocation of these Vecs?
91        let borrowed_buffers: Vec<Ref<'_, glyphon::Buffer>> =
92            primitives.iter().map(|p| p.buffer.raw_buffer()).collect();
93
94        let text_areas: Vec<TextArea<'_>> = primitives
95            .iter()
96            .zip(borrowed_buffers.iter())
97            .map(|(p, b)| TextArea {
98                buffer: &*b,
99                left: p.pos.x * self.scale_factor,
100                top: p.pos.y * self.scale_factor,
101                scale: self.scale_factor.0,
102                bounds: glyphon::TextBounds {
103                    left: (p.pos.x * self.scale_factor).round() as i32,
104                    top: (p.pos.y * self.scale_factor).round() as i32,
105                    right: ((p.pos.x + p.bounds_size.width) * self.scale_factor).round() as i32,
106                    bottom: ((p.pos.y + p.bounds_size.height) * self.scale_factor).round() as i32,
107                },
108                default_color: glyphon::Color::rgba(p.color.r, p.color.g, p.color.b, p.color.a),
109            })
110            .collect();
111
112        batch.text_renderer.prepare(
113            device,
114            queue,
115            font_system.raw_mut(),
116            &mut self.atlas,
117            glyphon::Resolution {
118                width: self.screen_size.width as u32,
119                height: self.screen_size.height as u32,
120            },
121            text_areas,
122            &mut self.cache,
123        )
124    }
125
126    pub fn finish_preparations(&mut self, _device: &wgpu::Device, _queue: &wgpu::Queue) {
127        self.prepare_all_batches = false;
128
129        if !self.atlas_needs_trimmed {
130            return;
131        }
132        self.atlas_needs_trimmed = false;
133
134        self.atlas.trim();
135    }
136
137    pub fn render_batch<'pass>(
138        &'pass self,
139        batch: &'pass TextBatchBuffer,
140        render_pass: &mut wgpu::RenderPass<'pass>,
141    ) -> Result<(), glyphon::RenderError> {
142        batch.text_renderer.render(&self.atlas, render_pass)
143    }
144}