1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
use crate::*;
use std::path::Path;
use wgpu_glyph::ab_glyph;
/// Font data that is readily available to be rendered.
///
/// This **requires** a depth stencil state as text draw calls cannot be visually interleaved with regular draw calls otherwise.
#[derive(Debug)]
pub struct FontBrush {
pub brush: wgpu_glyph::GlyphBrush<wgpu::DepthStencilState>,
}
impl FontBrush {
/// Loads a font from a path.
///
/// See [FontBrush::new].
pub fn from_path(
path: impl AsRef<Path>,
cx: &Context,
format: wgpu::TextureFormat,
depth_stencil: wgpu::DepthStencilState,
) -> Result<Self> {
Self::from_vec(std::fs::read(path)?, cx, format, depth_stencil)
}
/// Loads a font from owned font data.
///
/// See [FontBrush::new].
pub fn from_vec(
data: Vec<u8>,
cx: &Context,
format: wgpu::TextureFormat,
depth_stencil: wgpu::DepthStencilState,
) -> Result<Self> {
Ok(Self::new(
ab_glyph::FontArc::try_from_vec(data)?,
cx,
format,
depth_stencil,
))
}
/// Loads a font from borrowed font data.
///
/// See [FontBrush::new].
pub fn from_slice(
data: &'static [u8],
cx: &Context,
format: wgpu::TextureFormat,
depth_stencil: wgpu::DepthStencilState,
) -> Result<Self> {
Ok(Self::new(
ab_glyph::FontArc::try_from_slice(data)?,
cx,
format,
depth_stencil,
))
}
/// Creates a new [FontBrush] from an existing [ab_glyph::FontArc].
///
/// `format` and `depth_stencil` state specify information about the color target and depth target
/// that the text will be rendered into.
pub fn new(
handle: ab_glyph::FontArc,
cx: &Context,
format: wgpu::TextureFormat,
depth_stencil: wgpu::DepthStencilState,
) -> Self {
let brush = wgpu_glyph::GlyphBrushBuilder::using_font(handle)
.depth_stencil_state(depth_stencil)
.build(&cx.device, format);
FontBrush { brush }
}
}
/// Text rendering helper type which renders text using a [FontBrush].
pub struct TextRenderer {
staging_belt: wgpu::util::StagingBelt,
}
impl TextRenderer {
/// Create a new [TextRenderer].
pub fn new() -> Self {
let staging_belt = wgpu::util::StagingBelt::new(1024);
TextRenderer { staging_belt }
}
/// Frees the staging belt used when rendering the text.
///
/// Call this *after* [Frame::submit].
#[inline]
pub fn free(&mut self) -> impl std::future::Future<Output = ()> + Send {
self.staging_belt.recall()
}
/// Commits all the draw data.
///
/// Call this *before* [Frame::submit] but after all [TextRenderer::draw].
#[inline]
pub fn submit(&mut self) {
self.staging_belt.finish();
}
/// Draws text into a specified target, with a given transform and optional clipping rectangle.
///
/// Call this as least times as possible, batching text into arrays of [TextDraw] as much as is practicable.
pub fn draw(
&mut self,
cx: &Context,
font: &mut FontBrush,
draws: &[TextDraw],
frame: &mut Frame,
target: &wgpu::TextureView,
depth_stencil: wgpu::RenderPassDepthStencilAttachment,
transform: glam::Mat4,
clip: Option<Rect>,
) -> Result<()> {
for draw in draws {
self.queue(font, draw);
}
let transform = transform.to_cols_array();
if let Some(clip) = clip {
font.brush
.draw_queued_with_transform_and_scissoring(
&cx.device,
&mut self.staging_belt,
&mut frame.cmd,
target,
depth_stencil,
transform,
wgpu_glyph::Region {
x: clip.origin.x as _,
y: clip.origin.y as _,
width: clip.size.x as _,
height: clip.size.y as _,
},
)
.map_err(|s| Error::TextDraw(s))?;
} else {
font.brush
.draw_queued_with_transform(
&cx.device,
&mut self.staging_belt,
&mut frame.cmd,
target,
depth_stencil,
transform,
)
.map_err(|s| Error::TextDraw(s))?;
}
Ok(())
}
fn queue(&self, font: &mut FontBrush, draw: &TextDraw) {
font.brush.queue(wgpu_glyph::Section {
screen_position: (draw.origin.x, draw.origin.y),
bounds: draw
.bounds
.map(|bounds| bounds.into())
.unwrap_or((f32::INFINITY, f32::INFINITY)),
text: vec![wgpu_glyph::Text::default()
.with_text(draw.text)
.with_scale(draw.scale)
.with_color([draw.color.r, draw.color.g, draw.color.b, draw.color.a])
.with_z(draw.depth)],
..wgpu_glyph::Section::default()
});
}
}
/// Draw data for rendering text.
pub struct TextDraw<'a> {
/// Top-left of the text.
pub origin: glam::Vec2,
/// Depth (z position) of the text.
pub depth: f32,
/// Layout bounds of the text.
///
/// `None` means unbounded and thus will render as a single line.
pub bounds: Option<glam::Vec2>,
/// The text to be renderer.
pub text: &'a str,
/// Pixel scale of the text.
pub scale: f32,
/// Color of the text.
pub color: Color,
}
impl<'a> TextDraw<'a> {
/// Constructor for [TextDraw] without any layout bounds (single line).
pub fn unbounded(
origin: glam::Vec2,
depth: f32,
text: &'a str,
scale: f32,
color: Color,
) -> Self {
TextDraw {
origin,
depth,
bounds: None,
text,
scale,
color,
}
}
}