wgpu_glyph/
lib.rs

1//! A fast text renderer for [`wgpu`]. Powered by [`glyph_brush`].
2//!
3//! [`wgpu`]: https://github.com/gfx-rs/wgpu
4//! [`glyph_brush`]: https://github.com/alexheretic/glyph-brush/tree/master/glyph-brush
5#![deny(unused_results)]
6mod builder;
7mod pipeline;
8mod region;
9
10pub use region::Region;
11
12use pipeline::{Instance, Pipeline};
13
14pub use builder::GlyphBrushBuilder;
15pub use glyph_brush::ab_glyph;
16pub use glyph_brush::{
17    BuiltInLineBreaker, Extra, FontId, GlyphCruncher, GlyphPositioner,
18    HorizontalAlign, Layout, LineBreak, LineBreaker, OwnedSection, OwnedText,
19    Section, SectionGeometry, SectionGlyph, SectionGlyphIter, SectionText,
20    Text, VerticalAlign,
21};
22
23use ab_glyph::{Font, Rect};
24use core::hash::BuildHasher;
25use std::borrow::Cow;
26
27use glyph_brush::{BrushAction, BrushError, DefaultSectionHasher};
28use log::{log_enabled, warn};
29
30/// Object allowing glyph drawing, containing cache state. Manages glyph positioning cacheing,
31/// glyph draw caching & efficient GPU texture cache updating and re-sizing on demand.
32///
33/// Build using a [`GlyphBrushBuilder`](struct.GlyphBrushBuilder.html).
34pub struct GlyphBrush<Depth, F = ab_glyph::FontArc, H = DefaultSectionHasher> {
35    pipeline: Pipeline<Depth>,
36    glyph_brush: glyph_brush::GlyphBrush<Instance, Extra, F, H>,
37}
38
39impl<Depth, F: Font, H: BuildHasher> GlyphBrush<Depth, F, H> {
40    /// Queues a section/layout to be drawn by the next call of
41    /// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
42    /// called multiple times to queue multiple sections for drawing.
43    ///
44    /// Benefits from caching, see [caching behaviour](#caching-behaviour).
45    #[inline]
46    pub fn queue<'a, S>(&mut self, section: S)
47    where
48        S: Into<Cow<'a, Section<'a>>>,
49    {
50        self.glyph_brush.queue(section)
51    }
52
53    /// Queues a section/layout to be drawn by the next call of
54    /// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
55    /// called multiple times to queue multiple sections for drawing.
56    ///
57    /// Used to provide custom `GlyphPositioner` logic, if using built-in
58    /// [`Layout`](enum.Layout.html) simply use
59    /// [`queue`](struct.GlyphBrush.html#method.queue)
60    ///
61    /// Benefits from caching, see [caching behaviour](#caching-behaviour).
62    #[inline]
63    pub fn queue_custom_layout<'a, S, G>(
64        &mut self,
65        section: S,
66        custom_layout: &G,
67    ) where
68        G: GlyphPositioner,
69        S: Into<Cow<'a, Section<'a>>>,
70    {
71        self.glyph_brush.queue_custom_layout(section, custom_layout)
72    }
73
74    /// Queues pre-positioned glyphs to be processed by the next call of
75    /// [`draw_queued`](struct.GlyphBrush.html#method.draw_queued). Can be
76    /// called multiple times.
77    #[inline]
78    pub fn queue_pre_positioned(
79        &mut self,
80        glyphs: Vec<SectionGlyph>,
81        extra: Vec<Extra>,
82        bounds: Rect,
83    ) {
84        self.glyph_brush.queue_pre_positioned(glyphs, extra, bounds)
85    }
86
87    /// Retains the section in the cache as if it had been used in the last
88    /// draw-frame.
89    ///
90    /// Should not be necessary unless using multiple draws per frame with
91    /// distinct transforms, see [caching behaviour](#caching-behaviour).
92    #[inline]
93    pub fn keep_cached_custom_layout<'a, S, G>(
94        &mut self,
95        section: S,
96        custom_layout: &G,
97    ) where
98        S: Into<Cow<'a, Section<'a>>>,
99        G: GlyphPositioner,
100    {
101        self.glyph_brush
102            .keep_cached_custom_layout(section, custom_layout)
103    }
104
105    /// Retains the section in the cache as if it had been used in the last
106    /// draw-frame.
107    ///
108    /// Should not be necessary unless using multiple draws per frame with
109    /// distinct transforms, see [caching behaviour](#caching-behaviour).
110    #[inline]
111    pub fn keep_cached<'a, S>(&mut self, section: S)
112    where
113        S: Into<Cow<'a, Section<'a>>>,
114    {
115        self.glyph_brush.keep_cached(section)
116    }
117
118    /// Returns the available fonts.
119    ///
120    /// The `FontId` corresponds to the index of the font data.
121    #[inline]
122    pub fn fonts(&self) -> &[F] {
123        self.glyph_brush.fonts()
124    }
125
126    /// Adds an additional font to the one(s) initially added on build.
127    ///
128    /// Returns a new [`FontId`](struct.FontId.html) to reference this font.
129    pub fn add_font(&mut self, font: F) -> FontId {
130        self.glyph_brush.add_font(font)
131    }
132}
133
134impl<D, F, H> GlyphBrush<D, F, H>
135where
136    F: Font + Sync,
137    H: BuildHasher,
138{
139    fn process_queued(
140        &mut self,
141        device: &wgpu::Device,
142        staging_belt: &mut wgpu::util::StagingBelt,
143        encoder: &mut wgpu::CommandEncoder,
144    ) {
145        let pipeline = &mut self.pipeline;
146
147        let mut brush_action;
148
149        loop {
150            brush_action = self.glyph_brush.process_queued(
151                |rect, tex_data| {
152                    let offset = [rect.min[0] as u16, rect.min[1] as u16];
153                    let size = [rect.width() as u16, rect.height() as u16];
154
155                    pipeline.update_cache(
156                        device,
157                        staging_belt,
158                        encoder,
159                        offset,
160                        size,
161                        tex_data,
162                    );
163                },
164                Instance::from_vertex,
165            );
166
167            match brush_action {
168                Ok(_) => break,
169                Err(BrushError::TextureTooSmall { suggested }) => {
170                    // TODO: Obtain max texture dimensions using `wgpu`
171                    // This is currently not possible I think. Ask!
172                    let max_image_dimension = 2048;
173
174                    let (new_width, new_height) = if (suggested.0
175                        > max_image_dimension
176                        || suggested.1 > max_image_dimension)
177                        && (self.glyph_brush.texture_dimensions().0
178                            < max_image_dimension
179                            || self.glyph_brush.texture_dimensions().1
180                                < max_image_dimension)
181                    {
182                        (max_image_dimension, max_image_dimension)
183                    } else {
184                        suggested
185                    };
186
187                    if log_enabled!(log::Level::Warn) {
188                        warn!(
189                            "Increasing glyph texture size {old:?} -> {new:?}. \
190                             Consider building with `.initial_cache_size({new:?})` to avoid \
191                             resizing",
192                            old = self.glyph_brush.texture_dimensions(),
193                            new = (new_width, new_height),
194                        );
195                    }
196
197                    pipeline.increase_cache_size(device, new_width, new_height);
198                    self.glyph_brush.resize_texture(new_width, new_height);
199                }
200            }
201        }
202
203        match brush_action.unwrap() {
204            BrushAction::Draw(verts) => {
205                self.pipeline.upload(device, staging_belt, encoder, &verts);
206            }
207            BrushAction::ReDraw => {}
208        };
209    }
210}
211
212impl<F: Font + Sync, H: BuildHasher> GlyphBrush<(), F, H> {
213    fn new(
214        device: &wgpu::Device,
215        filter_mode: wgpu::FilterMode,
216        multisample: wgpu::MultisampleState,
217        render_format: wgpu::TextureFormat,
218        raw_builder: glyph_brush::GlyphBrushBuilder<F, H>,
219    ) -> Self {
220        let glyph_brush = raw_builder.build();
221        let (cache_width, cache_height) = glyph_brush.texture_dimensions();
222        GlyphBrush {
223            pipeline: Pipeline::<()>::new(
224                device,
225                filter_mode,
226                multisample,
227                render_format,
228                cache_width,
229                cache_height,
230            ),
231            glyph_brush,
232        }
233    }
234
235    /// Draws all queued sections onto a render target.
236    /// See [`queue`](struct.GlyphBrush.html#method.queue).
237    ///
238    /// It __does not__ submit the encoder command buffer to the device queue.
239    ///
240    /// Trims the cache, see [caching behaviour](#caching-behaviour).
241    ///
242    /// # Panics
243    /// Panics if the provided `target` has a texture format that does not match
244    /// the `render_format` provided on creation of the `GlyphBrush`.
245    #[inline]
246    pub fn draw_queued(
247        &mut self,
248        device: &wgpu::Device,
249        staging_belt: &mut wgpu::util::StagingBelt,
250        encoder: &mut wgpu::CommandEncoder,
251        target: &wgpu::TextureView,
252        target_width: u32,
253        target_height: u32,
254    ) -> Result<(), String> {
255        self.draw_queued_with_transform(
256            device,
257            staging_belt,
258            encoder,
259            target,
260            orthographic_projection(target_width, target_height),
261        )
262    }
263
264    /// Draws all queued sections onto a render target, applying a position
265    /// transform (e.g. a projection).
266    /// See [`queue`](struct.GlyphBrush.html#method.queue).
267    ///
268    /// It __does not__ submit the encoder command buffer to the device queue.
269    ///
270    /// Trims the cache, see [caching behaviour](#caching-behaviour).
271    ///
272    /// # Panics
273    /// Panics if the provided `target` has a texture format that does not match
274    /// the `render_format` provided on creation of the `GlyphBrush`.
275    #[inline]
276    pub fn draw_queued_with_transform(
277        &mut self,
278        device: &wgpu::Device,
279        staging_belt: &mut wgpu::util::StagingBelt,
280        encoder: &mut wgpu::CommandEncoder,
281        target: &wgpu::TextureView,
282        transform: [f32; 16],
283    ) -> Result<(), String> {
284        self.process_queued(device, staging_belt, encoder);
285        self.pipeline.draw(
286            device,
287            staging_belt,
288            encoder,
289            target,
290            transform,
291            None,
292        );
293
294        Ok(())
295    }
296
297    /// Draws all queued sections onto a render target, applying a position
298    /// transform (e.g. a projection) and a scissoring region.
299    /// See [`queue`](struct.GlyphBrush.html#method.queue).
300    ///
301    /// It __does not__ submit the encoder command buffer to the device queue.
302    ///
303    /// Trims the cache, see [caching behaviour](#caching-behaviour).
304    ///
305    /// # Panics
306    /// Panics if the provided `target` has a texture format that does not match
307    /// the `render_format` provided on creation of the `GlyphBrush`.
308    #[inline]
309    pub fn draw_queued_with_transform_and_scissoring(
310        &mut self,
311        device: &wgpu::Device,
312        staging_belt: &mut wgpu::util::StagingBelt,
313        encoder: &mut wgpu::CommandEncoder,
314        target: &wgpu::TextureView,
315        transform: [f32; 16],
316        region: Region,
317    ) -> Result<(), String> {
318        self.process_queued(device, staging_belt, encoder);
319        self.pipeline.draw(
320            device,
321            staging_belt,
322            encoder,
323            target,
324            transform,
325            Some(region),
326        );
327
328        Ok(())
329    }
330}
331
332impl<F: Font + Sync, H: BuildHasher> GlyphBrush<wgpu::DepthStencilState, F, H> {
333    fn new(
334        device: &wgpu::Device,
335        filter_mode: wgpu::FilterMode,
336        multisample: wgpu::MultisampleState,
337        render_format: wgpu::TextureFormat,
338        depth_stencil_state: wgpu::DepthStencilState,
339        raw_builder: glyph_brush::GlyphBrushBuilder<F, H>,
340    ) -> Self {
341        let glyph_brush = raw_builder.build();
342        let (cache_width, cache_height) = glyph_brush.texture_dimensions();
343        GlyphBrush {
344            pipeline: Pipeline::<wgpu::DepthStencilState>::new(
345                device,
346                filter_mode,
347                multisample,
348                render_format,
349                depth_stencil_state,
350                cache_width,
351                cache_height,
352            ),
353            glyph_brush,
354        }
355    }
356
357    /// Draws all queued sections onto a render target.
358    /// See [`queue`](struct.GlyphBrush.html#method.queue).
359    ///
360    /// It __does not__ submit the encoder command buffer to the device queue.
361    ///
362    /// Trims the cache, see [caching behaviour](#caching-behaviour).
363    ///
364    /// # Panics
365    /// Panics if the provided `target` has a texture format that does not match
366    /// the `render_format` provided on creation of the `GlyphBrush`.
367    #[inline]
368    pub fn draw_queued(
369        &mut self,
370        device: &wgpu::Device,
371        staging_belt: &mut wgpu::util::StagingBelt,
372        encoder: &mut wgpu::CommandEncoder,
373        target: &wgpu::TextureView,
374        depth_stencil_attachment: wgpu::RenderPassDepthStencilAttachment,
375        target_width: u32,
376        target_height: u32,
377    ) -> Result<(), String> {
378        self.draw_queued_with_transform(
379            device,
380            staging_belt,
381            encoder,
382            target,
383            depth_stencil_attachment,
384            orthographic_projection(target_width, target_height),
385        )
386    }
387
388    /// Draws all queued sections onto a render target, applying a position
389    /// transform (e.g. a projection).
390    /// See [`queue`](struct.GlyphBrush.html#method.queue).
391    ///
392    /// It __does not__ submit the encoder command buffer to the device queue.
393    ///
394    /// Trims the cache, see [caching behaviour](#caching-behaviour).
395    ///
396    /// # Panics
397    /// Panics if the provided `target` has a texture format that does not match
398    /// the `render_format` provided on creation of the `GlyphBrush`.
399    #[inline]
400    pub fn draw_queued_with_transform(
401        &mut self,
402        device: &wgpu::Device,
403        staging_belt: &mut wgpu::util::StagingBelt,
404        encoder: &mut wgpu::CommandEncoder,
405        target: &wgpu::TextureView,
406        depth_stencil_attachment: wgpu::RenderPassDepthStencilAttachment,
407        transform: [f32; 16],
408    ) -> Result<(), String> {
409        self.process_queued(device, staging_belt, encoder);
410        self.pipeline.draw(
411            device,
412            staging_belt,
413            encoder,
414            target,
415            depth_stencil_attachment,
416            transform,
417            None,
418        );
419
420        Ok(())
421    }
422
423    /// Draws all queued sections onto a render target, applying a position
424    /// transform (e.g. a projection) and a scissoring region.
425    /// See [`queue`](struct.GlyphBrush.html#method.queue).
426    ///
427    /// It __does not__ submit the encoder command buffer to the device queue.
428    ///
429    /// Trims the cache, see [caching behaviour](#caching-behaviour).
430    ///
431    /// # Panics
432    /// Panics if the provided `target` has a texture format that does not match
433    /// the `render_format` provided on creation of the `GlyphBrush`.
434    #[inline]
435    pub fn draw_queued_with_transform_and_scissoring(
436        &mut self,
437        device: &wgpu::Device,
438        staging_belt: &mut wgpu::util::StagingBelt,
439        encoder: &mut wgpu::CommandEncoder,
440        target: &wgpu::TextureView,
441        depth_stencil_attachment: wgpu::RenderPassDepthStencilAttachment,
442        transform: [f32; 16],
443        region: Region,
444    ) -> Result<(), String> {
445        self.process_queued(device, staging_belt, encoder);
446
447        self.pipeline.draw(
448            device,
449            staging_belt,
450            encoder,
451            target,
452            depth_stencil_attachment,
453            transform,
454            Some(region),
455        );
456
457        Ok(())
458    }
459}
460
461/// Helper function to generate a generate a transform matrix.
462#[rustfmt::skip]
463pub fn orthographic_projection(width: u32, height: u32) -> [f32; 16] {
464    [
465        2.0 / width as f32, 0.0, 0.0, 0.0,
466        0.0, -2.0 / height as f32, 0.0, 0.0,
467        0.0, 0.0, 1.0, 0.0,
468        -1.0, 1.0, 0.0, 1.0,
469    ]
470}
471
472impl<D, F: Font, H: BuildHasher> GlyphCruncher<F> for GlyphBrush<D, F, H> {
473    #[inline]
474    fn glyphs_custom_layout<'a, 'b, S, L>(
475        &'b mut self,
476        section: S,
477        custom_layout: &L,
478    ) -> SectionGlyphIter<'b>
479    where
480        L: GlyphPositioner + std::hash::Hash,
481        S: Into<Cow<'a, Section<'a>>>,
482    {
483        self.glyph_brush
484            .glyphs_custom_layout(section, custom_layout)
485    }
486
487    #[inline]
488    fn fonts(&self) -> &[F] {
489        self.glyph_brush.fonts()
490    }
491
492    #[inline]
493    fn glyph_bounds_custom_layout<'a, S, L>(
494        &mut self,
495        section: S,
496        custom_layout: &L,
497    ) -> Option<Rect>
498    where
499        L: GlyphPositioner + std::hash::Hash,
500        S: Into<Cow<'a, Section<'a>>>,
501    {
502        self.glyph_brush
503            .glyph_bounds_custom_layout(section, custom_layout)
504    }
505}
506
507impl<F, H> std::fmt::Debug for GlyphBrush<F, H> {
508    #[inline]
509    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
510        write!(f, "GlyphBrush")
511    }
512}