glyphon/
text_render.rs

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
14/// A text renderer that uses cached glyphs to render text into an existing render pass.
15pub struct TextRenderer {
16    vertex_buffer: Buffer,
17    vertex_buffer_size: u64,
18    pipeline: RenderPipeline,
19    glyph_vertices: Vec<GlyphToRender>,
20}
21
22impl TextRenderer {
23    /// Creates a new `TextRenderer`.
24    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    /// Prepares all of the provided text areas for rendering.
49    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    /// Prepares all of the provided text areas for rendering.
73    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    /// Prepares all of the provided text areas for rendering.
98    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    /// Prepares all of the provided text areas for rendering.
123    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                                    // Not implemented yet, but don't panic if this happens.
279                                    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    /// Renders all layouts that were previously provided to `prepare`.
334    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            // Find a position in the packer
448            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        // Insert the glyph into the cache and return the details reference
510        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    // Starts beyond right edge or ends beyond left edge
532    let max_x = x + width;
533    if x > bounds_max_x || max_x < bounds_min_x {
534        return Ok(None);
535    }
536
537    // Starts beyond bottom edge or ends beyond top edge
538    let max_y = y + height;
539    if y > bounds_max_y || max_y < bounds_min_y {
540        return Ok(None);
541    }
542
543    // Clip left ege
544    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    // Clip right edge
553    if x + width > bounds_max_x {
554        width = bounds_max_x - x;
555    }
556
557    // Clip top edge
558    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    // Clip bottom edge
567    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}