Skip to main content

cryoglyph/
text_render.rs

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
13/// A text renderer that uses cached glyphs to render text into an existing render pass.
14pub 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    /// Creates a new `TextRenderer`.
25    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    /// Prepares all of the provided text areas for rendering.
52    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                                // Not implemented yet, but don't panic if this happens.
116                                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                            // Find a position in the packer
129                            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                        // Insert the glyph into the cache and return the details reference
189                        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                    // Starts beyond right edge or ends beyond left edge
212                    let max_x = x + width;
213                    if x > bounds_max_x || max_x < bounds_min_x {
214                        continue;
215                    }
216
217                    // Starts beyond bottom edge or ends beyond top edge
218                    let max_y = y + height;
219                    if y > bounds_max_y || max_y < bounds_min_y {
220                        continue;
221                    }
222
223                    // Clip left ege
224                    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                    // Clip right edge
233                    if x + width > bounds_max_x {
234                        width = bounds_max_x - x;
235                    }
236
237                    // Clip top edge
238                    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                    // Clip bottom edge
247                    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    /// Renders all layouts that were previously provided to `prepare`.
347    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}