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 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 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}