1use crate::{
2 custom_glyph::CustomGlyphCacheKey, ColorMode, ContentType, FontSystem, GlyphDetails,
3 GlyphToRender, GpuCacheStatus, PrepareError, RasterizeCustomGlyphRequest,
4 RasterizedCustomGlyph, RenderError, SwashCache, SwashContent, TextArea, TextAtlas, Viewport,
5};
6use cosmic_text::{Color, SubpixelBin};
7use std::slice;
8use wgpu::{
9 Buffer, BufferDescriptor, BufferUsages, DepthStencilState, Device, Extent3d, MultisampleState,
10 Origin3d, Queue, RenderPass, RenderPipeline, TexelCopyBufferLayout, TexelCopyTextureInfo,
11 TextureAspect, COPY_BUFFER_ALIGNMENT,
12};
13
14pub struct TextRenderer {
16 vertex_buffer: Buffer,
17 vertex_buffer_size: u64,
18 pipeline: RenderPipeline,
19 glyph_vertices: Vec<GlyphToRender>,
20}
21
22impl TextRenderer {
23 pub fn new(
25 atlas: &mut TextAtlas,
26 device: &Device,
27 multisample: MultisampleState,
28 depth_stencil: Option<DepthStencilState>,
29 ) -> Self {
30 let vertex_buffer_size = next_copy_buffer_size(4096);
31 let vertex_buffer = device.create_buffer(&BufferDescriptor {
32 label: Some("glyphon vertices"),
33 size: vertex_buffer_size,
34 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
35 mapped_at_creation: false,
36 });
37
38 let pipeline = atlas.get_or_create_pipeline(device, multisample, depth_stencil);
39
40 Self {
41 vertex_buffer,
42 vertex_buffer_size,
43 pipeline,
44 glyph_vertices: Vec::new(),
45 }
46 }
47
48 pub fn prepare<'a>(
50 &mut self,
51 device: &Device,
52 queue: &Queue,
53 font_system: &mut FontSystem,
54 atlas: &mut TextAtlas,
55 viewport: &Viewport,
56 text_areas: impl IntoIterator<Item = TextArea<'a>>,
57 cache: &mut SwashCache,
58 ) -> Result<(), PrepareError> {
59 self.prepare_with_depth_and_custom(
60 device,
61 queue,
62 font_system,
63 atlas,
64 viewport,
65 text_areas,
66 cache,
67 zero_depth,
68 |_| None,
69 )
70 }
71
72 pub fn prepare_with_depth<'a>(
74 &mut self,
75 device: &Device,
76 queue: &Queue,
77 font_system: &mut FontSystem,
78 atlas: &mut TextAtlas,
79 viewport: &Viewport,
80 text_areas: impl IntoIterator<Item = TextArea<'a>>,
81 cache: &mut SwashCache,
82 metadata_to_depth: impl FnMut(usize) -> f32,
83 ) -> Result<(), PrepareError> {
84 self.prepare_with_depth_and_custom(
85 device,
86 queue,
87 font_system,
88 atlas,
89 viewport,
90 text_areas,
91 cache,
92 metadata_to_depth,
93 |_| None,
94 )
95 }
96
97 pub fn prepare_with_custom<'a>(
99 &mut self,
100 device: &Device,
101 queue: &Queue,
102 font_system: &mut FontSystem,
103 atlas: &mut TextAtlas,
104 viewport: &Viewport,
105 text_areas: impl IntoIterator<Item = TextArea<'a>>,
106 cache: &mut SwashCache,
107 rasterize_custom_glyph: impl FnMut(RasterizeCustomGlyphRequest) -> Option<RasterizedCustomGlyph>,
108 ) -> Result<(), PrepareError> {
109 self.prepare_with_depth_and_custom(
110 device,
111 queue,
112 font_system,
113 atlas,
114 viewport,
115 text_areas,
116 cache,
117 zero_depth,
118 rasterize_custom_glyph,
119 )
120 }
121
122 pub fn prepare_with_depth_and_custom<'a>(
124 &mut self,
125 device: &Device,
126 queue: &Queue,
127 font_system: &mut FontSystem,
128 atlas: &mut TextAtlas,
129 viewport: &Viewport,
130 text_areas: impl IntoIterator<Item = TextArea<'a>>,
131 cache: &mut SwashCache,
132 mut metadata_to_depth: impl FnMut(usize) -> f32,
133 mut rasterize_custom_glyph: impl FnMut(
134 RasterizeCustomGlyphRequest,
135 ) -> Option<RasterizedCustomGlyph>,
136 ) -> Result<(), PrepareError> {
137 self.glyph_vertices.clear();
138
139 let resolution = viewport.resolution();
140
141 for text_area in text_areas {
142 let bounds_min_x = text_area.bounds.left.max(0);
143 let bounds_min_y = text_area.bounds.top.max(0);
144 let bounds_max_x = text_area.bounds.right.min(resolution.width as i32);
145 let bounds_max_y = text_area.bounds.bottom.min(resolution.height as i32);
146
147 for glyph in text_area.custom_glyphs.iter() {
148 let x = text_area.left + (glyph.left * text_area.scale);
149 let y = text_area.top + (glyph.top * text_area.scale);
150 let width = (glyph.width * text_area.scale).round() as u16;
151 let height = (glyph.height * text_area.scale).round() as u16;
152
153 let (x, y, x_bin, y_bin) = if glyph.snap_to_physical_pixel {
154 (
155 x.round() as i32,
156 y.round() as i32,
157 SubpixelBin::Zero,
158 SubpixelBin::Zero,
159 )
160 } else {
161 let (x, x_bin) = SubpixelBin::new(x);
162 let (y, y_bin) = SubpixelBin::new(y);
163 (x, y, x_bin, y_bin)
164 };
165
166 let cache_key = GlyphonCacheKey::Custom(CustomGlyphCacheKey {
167 glyph_id: glyph.id,
168 width,
169 height,
170 x_bin,
171 y_bin,
172 });
173
174 let color = glyph.color.unwrap_or(text_area.default_color);
175
176 if let Some(glyph_to_render) = prepare_glyph(
177 x,
178 y,
179 0.0,
180 color,
181 glyph.metadata,
182 cache_key,
183 atlas,
184 device,
185 queue,
186 cache,
187 font_system,
188 text_area.scale,
189 bounds_min_x,
190 bounds_min_y,
191 bounds_max_x,
192 bounds_max_y,
193 |_cache, _font_system, rasterize_custom_glyph| -> Option<GetGlyphImageResult> {
194 if width == 0 || height == 0 {
195 return None;
196 }
197
198 let input = RasterizeCustomGlyphRequest {
199 id: glyph.id,
200 width,
201 height,
202 x_bin,
203 y_bin,
204 scale: text_area.scale,
205 };
206
207 let output = (rasterize_custom_glyph)(input)?;
208
209 output.validate(&input, None);
210
211 Some(GetGlyphImageResult {
212 content_type: output.content_type,
213 top: 0,
214 left: 0,
215 width,
216 height,
217 data: output.data,
218 })
219 },
220 &mut metadata_to_depth,
221 &mut rasterize_custom_glyph,
222 )? {
223 self.glyph_vertices.push(glyph_to_render);
224 }
225 }
226
227 let is_run_visible = |run: &cosmic_text::LayoutRun| {
228 let start_y_physical = (text_area.top + (run.line_top * text_area.scale)) as i32;
229 let end_y_physical = start_y_physical + (run.line_height * text_area.scale) as i32;
230
231 start_y_physical <= text_area.bounds.bottom && text_area.bounds.top <= end_y_physical
232 };
233
234 let layout_runs = text_area
235 .buffer
236 .layout_runs()
237 .skip_while(|run| !is_run_visible(run))
238 .take_while(is_run_visible);
239
240 for run in layout_runs {
241 for glyph in run.glyphs.iter() {
242 let physical_glyph =
243 glyph.physical((text_area.left, text_area.top), text_area.scale);
244
245 let color = match glyph.color_opt {
246 Some(some) => some,
247 None => text_area.default_color,
248 };
249
250 if let Some(glyph_to_render) = prepare_glyph(
251 physical_glyph.x,
252 physical_glyph.y,
253 run.line_y,
254 color,
255 glyph.metadata,
256 GlyphonCacheKey::Text(physical_glyph.cache_key),
257 atlas,
258 device,
259 queue,
260 cache,
261 font_system,
262 text_area.scale,
263 bounds_min_x,
264 bounds_min_y,
265 bounds_max_x,
266 bounds_max_y,
267 |cache,
268 font_system,
269 _rasterize_custom_glyph|
270 -> Option<GetGlyphImageResult> {
271 let image =
272 cache.get_image_uncached(font_system, physical_glyph.cache_key)?;
273
274 let content_type = match image.content {
275 SwashContent::Color => ContentType::Color,
276 SwashContent::Mask => ContentType::Mask,
277 SwashContent::SubpixelMask => {
278 ContentType::Mask
280 }
281 };
282
283 Some(GetGlyphImageResult {
284 content_type,
285 top: image.placement.top as i16,
286 left: image.placement.left as i16,
287 width: image.placement.width as u16,
288 height: image.placement.height as u16,
289 data: image.data,
290 })
291 },
292 &mut metadata_to_depth,
293 &mut rasterize_custom_glyph,
294 )? {
295 self.glyph_vertices.push(glyph_to_render);
296 }
297 }
298 }
299 }
300
301 let will_render = !self.glyph_vertices.is_empty();
302 if !will_render {
303 return Ok(());
304 }
305
306 let vertices = self.glyph_vertices.as_slice();
307 let vertices_raw = unsafe {
308 slice::from_raw_parts(
309 vertices as *const _ as *const u8,
310 std::mem::size_of_val(vertices),
311 )
312 };
313
314 if self.vertex_buffer_size >= vertices_raw.len() as u64 {
315 queue.write_buffer(&self.vertex_buffer, 0, vertices_raw);
316 } else {
317 self.vertex_buffer.destroy();
318
319 let (buffer, buffer_size) = create_oversized_buffer(
320 device,
321 Some("glyphon vertices"),
322 vertices_raw,
323 BufferUsages::VERTEX | BufferUsages::COPY_DST,
324 );
325
326 self.vertex_buffer = buffer;
327 self.vertex_buffer_size = buffer_size;
328 }
329
330 Ok(())
331 }
332
333 pub fn render(
335 &self,
336 atlas: &TextAtlas,
337 viewport: &Viewport,
338 pass: &mut RenderPass<'_>,
339 ) -> Result<(), RenderError> {
340 if self.glyph_vertices.is_empty() {
341 return Ok(());
342 }
343
344 pass.set_pipeline(&self.pipeline);
345 pass.set_bind_group(0, &atlas.bind_group, &[]);
346 pass.set_bind_group(1, &viewport.bind_group, &[]);
347 pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
348 pass.draw(0..4, 0..self.glyph_vertices.len() as u32);
349
350 Ok(())
351 }
352}
353
354#[repr(u16)]
355#[derive(Debug, Clone, Copy, Eq, PartialEq)]
356enum TextColorConversion {
357 None = 0,
358 ConvertToLinear = 1,
359}
360
361#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
362pub(crate) enum GlyphonCacheKey {
363 Text(cosmic_text::CacheKey),
364 Custom(CustomGlyphCacheKey),
365}
366
367fn next_copy_buffer_size(size: u64) -> u64 {
368 let align_mask = COPY_BUFFER_ALIGNMENT - 1;
369 ((size.next_power_of_two() + align_mask) & !align_mask).max(COPY_BUFFER_ALIGNMENT)
370}
371
372fn create_oversized_buffer(
373 device: &Device,
374 label: Option<&str>,
375 contents: &[u8],
376 usage: BufferUsages,
377) -> (Buffer, u64) {
378 let size = next_copy_buffer_size(contents.len() as u64);
379 let buffer = device.create_buffer(&BufferDescriptor {
380 label,
381 size,
382 usage,
383 mapped_at_creation: true,
384 });
385 buffer.slice(..).get_mapped_range_mut()[..contents.len()].copy_from_slice(contents);
386 buffer.unmap();
387 (buffer, size)
388}
389
390fn zero_depth(_: usize) -> f32 {
391 0f32
392}
393
394struct GetGlyphImageResult {
395 content_type: ContentType,
396 top: i16,
397 left: i16,
398 width: u16,
399 height: u16,
400 data: Vec<u8>,
401}
402
403fn prepare_glyph<R>(
404 x: i32,
405 y: i32,
406 line_y: f32,
407 color: Color,
408 metadata: usize,
409 cache_key: GlyphonCacheKey,
410 atlas: &mut TextAtlas,
411 device: &Device,
412 queue: &Queue,
413 cache: &mut SwashCache,
414 font_system: &mut FontSystem,
415 scale_factor: f32,
416 bounds_min_x: i32,
417 bounds_min_y: i32,
418 bounds_max_x: i32,
419 bounds_max_y: i32,
420 get_glyph_image: impl FnOnce(
421 &mut SwashCache,
422 &mut FontSystem,
423 &mut R,
424 ) -> Option<GetGlyphImageResult>,
425 mut metadata_to_depth: impl FnMut(usize) -> f32,
426 mut rasterize_custom_glyph: R,
427) -> Result<Option<GlyphToRender>, PrepareError>
428where
429 R: FnMut(RasterizeCustomGlyphRequest) -> Option<RasterizedCustomGlyph>,
430{
431 let details = if let Some(details) = atlas.mask_atlas.glyph_cache.get(&cache_key) {
432 atlas.mask_atlas.glyphs_in_use.insert(cache_key);
433 details
434 } else if let Some(details) = atlas.color_atlas.glyph_cache.get(&cache_key) {
435 atlas.color_atlas.glyphs_in_use.insert(cache_key);
436 details
437 } else {
438 let Some(image) = (get_glyph_image)(cache, font_system, &mut rasterize_custom_glyph) else {
439 return Ok(None);
440 };
441
442 let should_rasterize = image.width > 0 && image.height > 0;
443
444 let (gpu_cache, atlas_id, inner) = if should_rasterize {
445 let mut inner = atlas.inner_for_content_mut(image.content_type);
446
447 let allocation = loop {
449 match inner.try_allocate(image.width as usize, image.height as usize) {
450 Some(a) => break a,
451 None => {
452 if !atlas.grow(
453 device,
454 queue,
455 font_system,
456 cache,
457 image.content_type,
458 scale_factor,
459 &mut rasterize_custom_glyph,
460 ) {
461 return Err(PrepareError::AtlasFull);
462 }
463
464 inner = atlas.inner_for_content_mut(image.content_type);
465 }
466 }
467 };
468 let atlas_min = allocation.rectangle.min;
469
470 queue.write_texture(
471 TexelCopyTextureInfo {
472 texture: &inner.texture,
473 mip_level: 0,
474 origin: Origin3d {
475 x: atlas_min.x as u32,
476 y: atlas_min.y as u32,
477 z: 0,
478 },
479 aspect: TextureAspect::All,
480 },
481 &image.data,
482 TexelCopyBufferLayout {
483 offset: 0,
484 bytes_per_row: Some(image.width as u32 * inner.num_channels() as u32),
485 rows_per_image: None,
486 },
487 Extent3d {
488 width: image.width as u32,
489 height: image.height as u32,
490 depth_or_array_layers: 1,
491 },
492 );
493
494 (
495 GpuCacheStatus::InAtlas {
496 x: atlas_min.x as u16,
497 y: atlas_min.y as u16,
498 content_type: image.content_type,
499 },
500 Some(allocation.id),
501 inner,
502 )
503 } else {
504 let inner = &mut atlas.color_atlas;
505 (GpuCacheStatus::SkipRasterization, None, inner)
506 };
507
508 inner.glyphs_in_use.insert(cache_key);
509 inner.glyph_cache.get_or_insert(cache_key, || GlyphDetails {
511 width: image.width,
512 height: image.height,
513 gpu_cache,
514 atlas_id,
515 top: image.top,
516 left: image.left,
517 })
518 };
519
520 let mut x = x + details.left as i32;
521 let mut y = (line_y * scale_factor).round() as i32 + y - details.top as i32;
522
523 let (mut atlas_x, mut atlas_y, content_type) = match details.gpu_cache {
524 GpuCacheStatus::InAtlas { x, y, content_type } => (x, y, content_type),
525 GpuCacheStatus::SkipRasterization => return Ok(None),
526 };
527
528 let mut width = details.width as i32;
529 let mut height = details.height as i32;
530
531 let max_x = x + width;
533 if x > bounds_max_x || max_x < bounds_min_x {
534 return Ok(None);
535 }
536
537 let max_y = y + height;
539 if y > bounds_max_y || max_y < bounds_min_y {
540 return Ok(None);
541 }
542
543 if x < bounds_min_x {
545 let right_shift = bounds_min_x - x;
546
547 x = bounds_min_x;
548 width = max_x - bounds_min_x;
549 atlas_x += right_shift as u16;
550 }
551
552 if x + width > bounds_max_x {
554 width = bounds_max_x - x;
555 }
556
557 if y < bounds_min_y {
559 let bottom_shift = bounds_min_y - y;
560
561 y = bounds_min_y;
562 height = max_y - bounds_min_y;
563 atlas_y += bottom_shift as u16;
564 }
565
566 if y + height > bounds_max_y {
568 height = bounds_max_y - y;
569 }
570
571 let depth = metadata_to_depth(metadata);
572
573 Ok(Some(GlyphToRender {
574 pos: [x, y],
575 dim: [width as u16, height as u16],
576 uv: [atlas_x, atlas_y],
577 color: color.0,
578 content_type_with_srgb: [
579 content_type as u16,
580 match atlas.color_mode {
581 ColorMode::Accurate => TextColorConversion::ConvertToLinear,
582 ColorMode::Web => TextColorConversion::None,
583 } as u16,
584 ],
585 depth,
586 }))
587}