Skip to content

Commit f1ab968

Browse files
ickshonpeProfLander
authored andcommitted
Perform text scaling calculations per text, not per glyph (bevyengine#7819)
1 parent 650ef23 commit f1ab968

File tree

5 files changed

+130
-61
lines changed

5 files changed

+130
-61
lines changed

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,6 +1553,16 @@ description = "Loads an animated fox model and spawns lots of them. Good for tes
15531553
category = "Stress Tests"
15541554
wasm = true
15551555

1556+
[[example]]
1557+
name = "many_glyphs"
1558+
path = "examples/stress_tests/many_glyphs.rs"
1559+
1560+
[package.metadata.example.many_glyphs]
1561+
name = "Many Glyphs"
1562+
description = "Simple benchmark to test text rendering."
1563+
category = "Stress Tests"
1564+
wasm = true
1565+
15561566
[[example]]
15571567
name = "many_lights"
15581568
path = "examples/stress_tests/many_lights.rs"

crates/bevy_text/src/text2d.rs

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ use bevy_utils::HashSet;
2323
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
2424

2525
use crate::{
26-
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
27-
TextSettings, YAxisOrientation,
26+
Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError, TextLayoutInfo,
27+
TextPipeline, TextSettings, YAxisOrientation,
2828
};
2929

3030
/// The maximum width and height of text. The text will wrap according to the specified size.
@@ -94,48 +94,42 @@ pub fn extract_text2d_sprite(
9494
.get_single()
9595
.map(|window| window.resolution.scale_factor() as f32)
9696
.unwrap_or(1.0);
97+
let scaling = GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()));
9798

98-
for (entity, computed_visibility, text, text_layout_info, anchor, text_transform) in
99+
for (entity, computed_visibility, text, text_layout_info, anchor, global_transform) in
99100
text2d_query.iter()
100101
{
101102
if !computed_visibility.is_visible() {
102103
continue;
103104
}
104105

105-
let text_glyphs = &text_layout_info.glyphs;
106106
let text_anchor = -(anchor.as_vec() + 0.5);
107-
let alignment_offset = text_layout_info.size * text_anchor;
107+
let alignment_translation = text_layout_info.size * text_anchor;
108+
let transform = *global_transform
109+
* scaling
110+
* GlobalTransform::from_translation(alignment_translation.extend(0.));
108111
let mut color = Color::WHITE;
109112
let mut current_section = usize::MAX;
110-
for text_glyph in text_glyphs {
111-
if text_glyph.section_index != current_section {
112-
color = text.sections[text_glyph.section_index]
113-
.style
114-
.color
115-
.as_rgba_linear();
116-
current_section = text_glyph.section_index;
113+
for PositionedGlyph {
114+
position,
115+
atlas_info,
116+
section_index,
117+
..
118+
} in &text_layout_info.glyphs
119+
{
120+
if *section_index != current_section {
121+
color = text.sections[*section_index].style.color.as_rgba_linear();
122+
current_section = *section_index;
117123
}
118-
let atlas = texture_atlases
119-
.get(&text_glyph.atlas_info.texture_atlas)
120-
.unwrap();
121-
let handle = atlas.texture.clone_weak();
122-
let index = text_glyph.atlas_info.glyph_index;
123-
let rect = Some(atlas.textures[index]);
124-
125-
let glyph_transform =
126-
Transform::from_translation((alignment_offset + text_glyph.position).extend(0.));
127-
128-
let transform = *text_transform
129-
* GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()))
130-
* glyph_transform;
124+
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
131125

132126
extracted_sprites.sprites.push(ExtractedSprite {
133127
entity,
134-
transform,
128+
transform: transform * GlobalTransform::from_translation(position.extend(0.)),
135129
color,
136-
rect,
130+
rect: Some(atlas.textures[atlas_info.glyph_index]),
137131
custom_size: None,
138-
image_handle_id: handle.id(),
132+
image_handle_id: atlas.texture.id(),
139133
flip_x: false,
140134
flip_y: false,
141135
anchor: Anchor::Center.as_vec(),

crates/bevy_ui/src/render/mod.rs

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use bevy_sprite::SpriteAssetEvents;
3131
#[cfg(feature = "bevy_text")]
3232
use bevy_sprite::TextureAtlas;
3333
#[cfg(feature = "bevy_text")]
34-
use bevy_text::{Text, TextLayoutInfo};
34+
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
3535
use bevy_transform::components::GlobalTransform;
3636
use bevy_utils::FloatOrd;
3737
use bevy_utils::HashMap;
@@ -318,52 +318,43 @@ pub fn extract_text_uinodes(
318318
.map(|window| window.resolution.scale_factor() as f32)
319319
.unwrap_or(1.0);
320320

321+
let scaling = Mat4::from_scale(Vec3::splat(scale_factor.recip()));
322+
321323
for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() {
322324
if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) =
323325
uinode_query.get(*entity)
324326
{
325-
if !visibility.is_visible() {
326-
continue;
327-
}
328-
// Skip if size is set to zero (e.g. when a parent is set to `Display::None`)
329-
if uinode.size() == Vec2::ZERO {
327+
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
328+
if !visibility.is_visible() || uinode.size().x == 0. || uinode.size().y == 0. {
330329
continue;
331330
}
332-
let text_glyphs = &text_layout_info.glyphs;
333-
let alignment_offset = (uinode.size() / -2.0).extend(0.0);
331+
332+
let transform = global_transform.compute_matrix()
333+
* Mat4::from_translation(-0.5 * uinode.size().extend(0.))
334+
* scaling;
334335

335336
let mut color = Color::WHITE;
336337
let mut current_section = usize::MAX;
337-
for text_glyph in text_glyphs {
338-
if text_glyph.section_index != current_section {
339-
color = text.sections[text_glyph.section_index]
340-
.style
341-
.color
342-
.as_rgba_linear();
343-
current_section = text_glyph.section_index;
338+
for PositionedGlyph {
339+
position,
340+
atlas_info,
341+
section_index,
342+
..
343+
} in &text_layout_info.glyphs
344+
{
345+
if *section_index != current_section {
346+
color = text.sections[*section_index].style.color.as_rgba_linear();
347+
current_section = *section_index;
344348
}
345-
let atlas = texture_atlases
346-
.get(&text_glyph.atlas_info.texture_atlas)
347-
.unwrap();
348-
let texture = atlas.texture.clone_weak();
349-
let index = text_glyph.atlas_info.glyph_index;
350-
let rect = atlas.textures[index];
351-
let atlas_size = Some(atlas.size);
352-
353-
// NOTE: Should match `bevy_text::text2d::extract_text2d_sprite`
354-
let extracted_transform = global_transform.compute_matrix()
355-
* Mat4::from_scale(Vec3::splat(scale_factor.recip()))
356-
* Mat4::from_translation(
357-
alignment_offset * scale_factor + text_glyph.position.extend(0.),
358-
);
349+
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
359350

360351
extracted_uinodes.uinodes.push(ExtractedUiNode {
361352
stack_index,
362-
transform: extracted_transform,
353+
transform: transform * Mat4::from_translation(position.extend(0.)),
363354
color,
364-
rect,
365-
image: texture,
366-
atlas_size,
355+
rect: atlas.textures[atlas_info.glyph_index],
356+
image: atlas.texture.clone_weak(),
357+
atlas_size: Some(atlas.size),
367358
clip: clip.map(|clip| clip.clip),
368359
flip_x: false,
369360
flip_y: false,

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ Example | Description
299299
[Many Buttons](../examples/stress_tests/many_buttons.rs) | Test rendering of many UI elements
300300
[Many Cubes](../examples/stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead. Run with the `sphere` argument to test frustum culling
301301
[Many Foxes](../examples/stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000
302+
[Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering.
302303
[Many Lights](../examples/stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights
303304
[Many Sprites](../examples/stress_tests/many_sprites.rs) | Displays many sprites in a grid arrangement! Used for performance testing. Use `--colored` to enable color tinted sprites.
304305
[Text Pipeline](../examples/stress_tests/text_pipeline.rs) | Text Pipeline benchmark
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//! Simple text rendering benchmark.
2+
//!
3+
//! Creates a `Text` with a single `TextSection` containing `100_000` glyphs,
4+
//! and renders it with the UI in a white color and with Text2d in a red color.
5+
use bevy::{
6+
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
7+
prelude::*,
8+
text::{BreakLineOn, Text2dBounds},
9+
window::{PresentMode, WindowPlugin},
10+
};
11+
12+
fn main() {
13+
App::new()
14+
.add_plugins(DefaultPlugins.set(WindowPlugin {
15+
primary_window: Some(Window {
16+
present_mode: PresentMode::Immediate,
17+
..default()
18+
}),
19+
..default()
20+
}))
21+
.add_plugin(FrameTimeDiagnosticsPlugin::default())
22+
.add_plugin(LogDiagnosticsPlugin::default())
23+
.add_startup_system(setup)
24+
.run();
25+
}
26+
27+
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
28+
commands.spawn(Camera2dBundle::default());
29+
let mut text = Text {
30+
sections: vec![TextSection {
31+
value: "0123456789".repeat(10_000),
32+
style: TextStyle {
33+
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
34+
font_size: 4.,
35+
color: Color::WHITE,
36+
},
37+
}],
38+
alignment: TextAlignment::Left,
39+
linebreak_behaviour: BreakLineOn::AnyCharacter,
40+
};
41+
42+
commands
43+
.spawn(NodeBundle {
44+
style: Style {
45+
flex_basis: Val::Percent(100.),
46+
align_items: AlignItems::Center,
47+
justify_content: JustifyContent::Center,
48+
..default()
49+
},
50+
..default()
51+
})
52+
.with_children(|commands| {
53+
commands.spawn(TextBundle {
54+
text: text.clone(),
55+
style: Style {
56+
size: Size::width(Val::Px(1000.)),
57+
..Default::default()
58+
},
59+
..Default::default()
60+
});
61+
});
62+
63+
text.sections[0].style.color = Color::RED;
64+
65+
commands.spawn(Text2dBundle {
66+
text,
67+
text_anchor: bevy::sprite::Anchor::Center,
68+
text_2d_bounds: Text2dBounds {
69+
size: Vec2::new(1000., f32::INFINITY),
70+
},
71+
..Default::default()
72+
});
73+
}

0 commit comments

Comments
 (0)