1use crate::{
2 ColorMode, FontSystem, GlyphDetails, GlyphToRender, GpuCacheStatus, PrepareError, RenderError,
3 SwashCache, SwashContent, TextArea, TextAtlas, Viewport,
4};
5use std::{num::NonZeroU64, slice};
6use wgpu::util::StagingBelt;
7use wgpu::{
8 Buffer, BufferDescriptor, BufferUsages, COPY_BUFFER_ALIGNMENT, CommandEncoder,
9 DepthStencilState, Device, Extent3d, MultisampleState, Origin3d, Queue, RenderPass,
10 RenderPipeline, TexelCopyBufferLayout, TexelCopyTextureInfo, TextureAspect,
11};
12
13pub struct TextRenderer {
15 staging_belt: StagingBelt,
16 vertex_buffer: Buffer,
17 vertex_buffer_size: u64,
18 pipeline: RenderPipeline,
19 glyph_vertices: Vec<GlyphToRender>,
20 glyphs_to_render: u32,
21}
22
23impl TextRenderer {
24 pub fn new(
26 atlas: &mut TextAtlas,
27 device: &Device,
28 multisample: MultisampleState,
29 depth_stencil: Option<DepthStencilState>,
30 ) -> Self {
31 let vertex_buffer_size = next_copy_buffer_size(4096);
32 let vertex_buffer = device.create_buffer(&BufferDescriptor {
33 label: Some("glyphon vertices"),
34 size: vertex_buffer_size,
35 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
36 mapped_at_creation: false,
37 });
38
39 let pipeline = atlas.get_or_create_pipeline(device, multisample, depth_stencil);
40
41 Self {
42 staging_belt: StagingBelt::new(device.clone(), vertex_buffer_size),
43 vertex_buffer,
44 vertex_buffer_size,
45 pipeline,
46 glyph_vertices: Vec::new(),
47 glyphs_to_render: 0,
48 }
49 }
50
51 pub fn prepare_with_depth<'a>(
53 &mut self,
54 device: &Device,
55 queue: &Queue,
56 encoder: &mut CommandEncoder,
57 font_system: &mut FontSystem,
58 atlas: &mut TextAtlas,
59 viewport: &Viewport,
60 text_areas: impl IntoIterator<Item = TextArea<'a>>,
61 cache: &mut SwashCache,
62 mut metadata_to_depth: impl FnMut(usize) -> f32,
63 ) -> Result<(), PrepareError> {
64 self.staging_belt.recall();
65 self.glyph_vertices.clear();
66
67 let resolution = viewport.resolution();
68
69 for text_area in text_areas {
70 let bounds_min_x = text_area.bounds.left.max(0);
71 let bounds_min_y = text_area.bounds.top.max(0);
72 let bounds_max_x = text_area.bounds.right.min(resolution.width as i32);
73 let bounds_max_y = text_area.bounds.bottom.min(resolution.height as i32);
74
75 let is_run_visible = |run: &cosmic_text::LayoutRun<'_>| {
76 let start_y_physical = (text_area.top + (run.line_top * text_area.scale)) as i32;
77 let end_y_physical = start_y_physical + (run.line_height * text_area.scale) as i32;
78
79 start_y_physical <= text_area.bounds.bottom
80 && text_area.bounds.top <= end_y_physical
81 };
82
83 let layout_runs = text_area
84 .buffer
85 .layout_runs()
86 .skip_while(|run| !is_run_visible(run))
87 .take_while(is_run_visible);
88
89 for run in layout_runs {
90 for glyph in run.glyphs.iter() {
91 let physical_glyph =
92 glyph.physical((text_area.left, text_area.top), text_area.scale);
93
94 let cache_key = physical_glyph.cache_key;
95
96 let details = if let Some(details) =
97 atlas.mask_atlas.glyph_cache.get(&cache_key)
98 {
99 atlas.mask_atlas.glyphs_in_use.insert(cache_key);
100 details
101 } else if let Some(details) = atlas.color_atlas.glyph_cache.get(&cache_key) {
102 atlas.color_atlas.glyphs_in_use.insert(cache_key);
103 details
104 } else {
105 let Some(image) =
106 cache.get_image_uncached(font_system, physical_glyph.cache_key)
107 else {
108 continue;
109 };
110
111 let content_type = match image.content {
112 SwashContent::Color => ContentType::Color,
113 SwashContent::Mask => ContentType::Mask,
114 SwashContent::SubpixelMask => {
115 ContentType::Mask
117 }
118 };
119
120 let width = image.placement.width as usize;
121 let height = image.placement.height as usize;
122
123 let should_rasterize = width > 0 && height > 0;
124
125 let (gpu_cache, atlas_id, inner) = if should_rasterize {
126 let mut inner = atlas.inner_for_content_mut(content_type);
127
128 let allocation = loop {
130 match inner.try_allocate(width, height) {
131 Some(a) => break a,
132 None => {
133 if !atlas.grow(
134 device,
135 queue,
136 font_system,
137 cache,
138 content_type,
139 ) {
140 return Err(PrepareError::AtlasFull);
141 }
142
143 inner = atlas.inner_for_content_mut(content_type);
144 }
145 }
146 };
147 let atlas_min = allocation.rectangle.min;
148
149 queue.write_texture(
150 TexelCopyTextureInfo {
151 texture: &inner.texture,
152 mip_level: 0,
153 origin: Origin3d {
154 x: atlas_min.x as u32,
155 y: atlas_min.y as u32,
156 z: 0,
157 },
158 aspect: TextureAspect::All,
159 },
160 &image.data,
161 TexelCopyBufferLayout {
162 offset: 0,
163 bytes_per_row: Some(width as u32 * inner.num_channels() as u32),
164 rows_per_image: None,
165 },
166 Extent3d {
167 width: width as u32,
168 height: height as u32,
169 depth_or_array_layers: 1,
170 },
171 );
172
173 (
174 GpuCacheStatus::InAtlas {
175 x: atlas_min.x as u16,
176 y: atlas_min.y as u16,
177 content_type,
178 },
179 Some(allocation.id),
180 inner,
181 )
182 } else {
183 let inner = &mut atlas.color_atlas;
184 (GpuCacheStatus::SkipRasterization, None, inner)
185 };
186
187 inner.glyphs_in_use.insert(cache_key);
188 inner.glyph_cache.get_or_insert(cache_key, || GlyphDetails {
190 width: image.placement.width as u16,
191 height: image.placement.height as u16,
192 gpu_cache,
193 atlas_id,
194 top: image.placement.top as i16,
195 left: image.placement.left as i16,
196 })
197 };
198
199 let mut x = physical_glyph.x + details.left as i32;
200 let mut y = (run.line_y * text_area.scale).round() as i32 + physical_glyph.y
201 - details.top as i32;
202
203 let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
204 GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
205 GpuCacheStatus::SkipRasterization => continue,
206 };
207
208 let mut width = details.width as i32;
209 let mut height = details.height as i32;
210
211 let max_x = x + width;
213 if x > bounds_max_x || max_x < bounds_min_x {
214 continue;
215 }
216
217 let max_y = y + height;
219 if y > bounds_max_y || max_y < bounds_min_y {
220 continue;
221 }
222
223 if x < bounds_min_x {
225 let right_shift = bounds_min_x - x;
226
227 x = bounds_min_x;
228 width = max_x - bounds_min_x;
229 atlas_x += right_shift as u16;
230 }
231
232 if x + width > bounds_max_x {
234 width = bounds_max_x - x;
235 }
236
237 if y < bounds_min_y {
239 let bottom_shift = bounds_min_y - y;
240
241 y = bounds_min_y;
242 height = max_y - bounds_min_y;
243 atlas_y += bottom_shift as u16;
244 }
245
246 if y + height > bounds_max_y {
248 height = bounds_max_y - y;
249 }
250
251 let color = match glyph.color_opt {
252 Some(some) => some,
253 None => text_area.default_color,
254 };
255
256 let depth = metadata_to_depth(glyph.metadata);
257
258 self.glyph_vertices.push(GlyphToRender {
259 pos: [x, y],
260 dim: [width as u16, height as u16],
261 uv: [atlas_x, atlas_y],
262 color: color.0,
263 content_type_with_srgb: [
264 content_type as u16,
265 match atlas.color_mode {
266 ColorMode::Accurate => TextColorConversion::ConvertToLinear,
267 ColorMode::Web => TextColorConversion::None,
268 } as u16,
269 ],
270 depth,
271 });
272 }
273 }
274 }
275
276 self.glyphs_to_render = self.glyph_vertices.len() as u32;
277
278 let will_render = !self.glyph_vertices.is_empty();
279 if !will_render {
280 return Ok(());
281 }
282
283 let vertices = self.glyph_vertices.as_slice();
284 let vertices_raw = unsafe {
285 slice::from_raw_parts(
286 vertices as *const _ as *const u8,
287 std::mem::size_of_val(vertices),
288 )
289 };
290
291 if self.vertex_buffer_size >= vertices_raw.len() as u64 {
292 self.staging_belt
293 .write_buffer(
294 encoder,
295 &self.vertex_buffer,
296 0,
297 NonZeroU64::new(vertices_raw.len() as u64).expect("Non-empty vertices"),
298 )
299 .copy_from_slice(vertices_raw);
300 } else {
301 self.vertex_buffer.destroy();
302
303 let (buffer, buffer_size) = create_oversized_buffer(
304 device,
305 Some("glyphon vertices"),
306 vertices_raw,
307 BufferUsages::VERTEX | BufferUsages::COPY_DST,
308 );
309
310 self.vertex_buffer = buffer;
311 self.vertex_buffer_size = buffer_size;
312
313 self.staging_belt.finish();
314 self.staging_belt = StagingBelt::new(device.clone(), buffer_size);
315 }
316
317 self.staging_belt.finish();
318
319 Ok(())
320 }
321
322 pub fn prepare<'a>(
323 &mut self,
324 device: &Device,
325 queue: &Queue,
326 encoder: &mut CommandEncoder,
327 font_system: &mut FontSystem,
328 atlas: &mut TextAtlas,
329 viewport: &Viewport,
330 text_areas: impl IntoIterator<Item = TextArea<'a>>,
331 cache: &mut SwashCache,
332 ) -> Result<(), PrepareError> {
333 self.prepare_with_depth(
334 device,
335 queue,
336 encoder,
337 font_system,
338 atlas,
339 viewport,
340 text_areas,
341 cache,
342 zero_depth,
343 )
344 }
345
346 pub fn render(
348 &self,
349 atlas: &TextAtlas,
350 viewport: &Viewport,
351 pass: &mut RenderPass<'_>,
352 ) -> Result<(), RenderError> {
353 if self.glyphs_to_render == 0 {
354 return Ok(());
355 }
356
357 pass.set_pipeline(&self.pipeline);
358 pass.set_bind_group(0, &atlas.bind_group, &[]);
359 pass.set_bind_group(1, &viewport.bind_group, &[]);
360 pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
361 pass.draw(0..4, 0..self.glyphs_to_render);
362
363 Ok(())
364 }
365}
366
367#[repr(u16)]
368#[derive(Debug, Clone, Copy, Eq, PartialEq)]
369pub enum ContentType {
370 Color = 0,
371 Mask = 1,
372}
373
374#[repr(u16)]
375#[derive(Debug, Clone, Copy, Eq, PartialEq)]
376enum TextColorConversion {
377 None = 0,
378 ConvertToLinear = 1,
379}
380
381fn next_copy_buffer_size(size: u64) -> u64 {
382 let align_mask = COPY_BUFFER_ALIGNMENT - 1;
383 ((size.next_power_of_two() + align_mask) & !align_mask).max(COPY_BUFFER_ALIGNMENT)
384}
385
386fn create_oversized_buffer(
387 device: &Device,
388 label: Option<&str>,
389 contents: &[u8],
390 usage: BufferUsages,
391) -> (Buffer, u64) {
392 let size = next_copy_buffer_size(contents.len() as u64);
393 let buffer = device.create_buffer(&BufferDescriptor {
394 label,
395 size,
396 usage,
397 mapped_at_creation: true,
398 });
399 buffer.slice(..).get_mapped_range_mut()[..contents.len()].copy_from_slice(contents);
400 buffer.unmap();
401 (buffer, size)
402}
403
404fn zero_depth(_: usize) -> f32 {
405 0f32
406}