Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perform text scaling calculations per text, not per glyph #7819

Merged
merged 25 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fb6cfaa
move transform scaling calculations for text out of the inner loop
ickshonpe Feb 25, 2023
67519e5
fix scaling in text2d
ickshonpe Feb 25, 2023
39f2f86
add many_glyphs example
ickshonpe Feb 25, 2023
fa6f831
Added many_glyphs example
ickshonpe Feb 25, 2023
a2ef631
modify example
ickshonpe Feb 25, 2023
65763ab
switch to single text section
ickshonpe Feb 25, 2023
b48bf4a
swapped flex_basis for style constraint on text node
ickshonpe Feb 25, 2023
58c69ee
Merge branch 'many_glyphs' into text-perf
ickshonpe Feb 25, 2023
ed801f5
cargo fmt
ickshonpe Feb 25, 2023
c64ee5c
fix manual-str-repeat
ickshonpe Feb 25, 2023
734ddb1
* cleaned up example
ickshonpe Feb 25, 2023
983b44d
cleaned up imports and improve transform calculations
ickshonpe Feb 25, 2023
15a41e4
replaced `extend` chain with `Vec4::from`
ickshonpe Feb 25, 2023
838fba2
Changed the precompution in `extract_text_uinodes` to use `from_scale…
ickshonpe Feb 27, 2023
6eabeef
cleaned up `extract_text2d_sprite` to be more similar to`extract_text…
ickshonpe Feb 27, 2023
3b760e7
Cleaned up `extract_text2d_sprite` to be more similar to`extract_tex…
ickshonpe Feb 27, 2023
26a04fe
Added text drawn with Text2d to the many_glyphs example.
ickshonpe Feb 27, 2023
c8ca008
cargo fmt
ickshonpe Feb 27, 2023
63f60cc
removed an unnecessary `mut` and an unused variable
ickshonpe Feb 27, 2023
6944b78
simplify text transform calculations
ickshonpe Feb 27, 2023
faedcab
simplified text calculations
ickshonpe Feb 27, 2023
567749c
fixed imports and fmt
ickshonpe Feb 27, 2023
fdfe321
fix clippy::doc-markdown warning
ickshonpe Feb 27, 2023
f3343cc
Merge branch 'main' into text-perf
ickshonpe Mar 13, 2023
d01878e
Moved scaling inversion calculation out of loop extraction loop.
ickshonpe Mar 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,16 @@ description = "Loads an animated fox model and spawns lots of them. Good for tes
category = "Stress Tests"
wasm = true

[[example]]
name = "many_glyphs"
path = "examples/stress_tests/many_glyphs.rs"

[package.metadata.example.many_glyphs]
name = "Many Glyphs"
description = "Simple benchmark to test text rendering."
category = "Stress Tests"
wasm = true

[[example]]
name = "many_lights"
path = "examples/stress_tests/many_lights.rs"
Expand Down
50 changes: 22 additions & 28 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use bevy_utils::HashSet;
use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};

use crate::{
Font, FontAtlasSet, FontAtlasWarning, Text, TextError, TextLayoutInfo, TextPipeline,
TextSettings, YAxisOrientation,
Font, FontAtlasSet, FontAtlasWarning, PositionedGlyph, Text, TextError, TextLayoutInfo,
TextPipeline, TextSettings, YAxisOrientation,
};

/// The maximum width and height of text. The text will wrap according to the specified size.
Expand Down Expand Up @@ -95,47 +95,41 @@ pub fn extract_text2d_sprite(
.map(|window| window.resolution.scale_factor() as f32)
.unwrap_or(1.0);

for (entity, computed_visibility, text, text_layout_info, anchor, text_transform) in
for (entity, computed_visibility, text, text_layout_info, anchor, global_transform) in
text2d_query.iter()
{
if !computed_visibility.is_visible() {
continue;
}

let text_glyphs = &text_layout_info.glyphs;
let text_anchor = anchor.as_vec() * Vec2::new(1., -1.) - 0.5;
let alignment_offset = text_layout_info.size * text_anchor;
let alignment_translation = text_layout_info.size * text_anchor;
let transform = *global_transform
* Transform::from_scale(Vec3::splat(scale_factor.recip()))
* Transform::from_translation(alignment_translation.extend(0.));
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved

let mut color = Color::WHITE;
let mut current_section = usize::MAX;
for text_glyph in text_glyphs {
if text_glyph.section_index != current_section {
color = text.sections[text_glyph.section_index]
.style
.color
.as_rgba_linear();
current_section = text_glyph.section_index;
for PositionedGlyph {
position,
atlas_info,
section_index,
..
} in &text_layout_info.glyphs
{
if *section_index != current_section {
color = text.sections[*section_index].style.color.as_rgba_linear();
current_section = *section_index;
}
let atlas = texture_atlases
.get(&text_glyph.atlas_info.texture_atlas)
.unwrap();
let handle = atlas.texture.clone_weak();
let index = text_glyph.atlas_info.glyph_index;
let rect = Some(atlas.textures[index]);

let glyph_transform =
Transform::from_translation((alignment_offset + text_glyph.position).extend(0.));

let transform = *text_transform
* GlobalTransform::from_scale(Vec3::splat(scale_factor.recip()))
* glyph_transform;
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();

extracted_sprites.sprites.push(ExtractedSprite {
entity,
transform,
transform: transform * Transform::from_translation(position.extend(0.)),
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
color,
rect,
rect: Some(atlas.textures[atlas_info.glyph_index]),
custom_size: None,
image_handle_id: handle.id(),
image_handle_id: atlas.texture.clone_weak().id(),
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
flip_x: false,
flip_y: false,
anchor: Anchor::Center.as_vec(),
Expand Down
55 changes: 22 additions & 33 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use bevy_sprite::SpriteAssetEvents;
#[cfg(feature = "bevy_text")]
use bevy_sprite::TextureAtlas;
#[cfg(feature = "bevy_text")]
use bevy_text::{Text, TextLayoutInfo};
use bevy_text::{PositionedGlyph, Text, TextLayoutInfo};
use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd;
use bevy_utils::HashMap;
Expand Down Expand Up @@ -320,48 +320,37 @@ pub fn extract_text_uinodes(
if let Ok((uinode, global_transform, text, text_layout_info, visibility, clip)) =
uinode_query.get(*entity)
{
if !visibility.is_visible() {
// Skip if not visible or if size is set to zero (e.g. when a parent is set to `Display::None`)
if !visibility.is_visible() || uinode.size().x == 0. || uinode.size().y == 0. {
continue;
}
// Skip if size is set to zero (e.g. when a parent is set to `Display::None`)
if uinode.size() == Vec2::ZERO {
continue;
}
let text_glyphs = &text_layout_info.glyphs;
let alignment_offset = (uinode.size() / -2.0).extend(0.0);

let transform = global_transform.compute_matrix()
* Mat4::from_translation(-0.5 * uinode.size().extend(0.))
* Mat4::from_scale(Vec3::splat(scale_factor.recip()));

let mut color = Color::WHITE;
let mut current_section = usize::MAX;
for text_glyph in text_glyphs {
if text_glyph.section_index != current_section {
color = text.sections[text_glyph.section_index]
.style
.color
.as_rgba_linear();
current_section = text_glyph.section_index;
for PositionedGlyph {
position,
atlas_info,
section_index,
..
} in &text_layout_info.glyphs
{
if *section_index != current_section {
color = text.sections[*section_index].style.color.as_rgba_linear();
current_section = *section_index;
}
let atlas = texture_atlases
.get(&text_glyph.atlas_info.texture_atlas)
.unwrap();
let texture = atlas.texture.clone_weak();
let index = text_glyph.atlas_info.glyph_index;
let rect = atlas.textures[index];
let atlas_size = Some(atlas.size);

// NOTE: Should match `bevy_text::text2d::extract_text2d_sprite`
let extracted_transform = global_transform.compute_matrix()
* Mat4::from_scale(Vec3::splat(scale_factor.recip()))
* Mat4::from_translation(
alignment_offset * scale_factor + text_glyph.position.extend(0.),
);
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();

extracted_uinodes.uinodes.push(ExtractedUiNode {
stack_index,
transform: extracted_transform,
transform: transform * Mat4::from_translation(position.extend(0.)),
color,
rect,
image: texture,
atlas_size,
rect: atlas.textures[atlas_info.glyph_index],
image: atlas.texture.clone_weak(),
atlas_size: Some(atlas.size),
clip: clip.map(|clip| clip.clip),
flip_x: false,
flip_y: false,
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ Example | Description
[Many Buttons](../examples/stress_tests/many_buttons.rs) | Test rendering of many UI elements
[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
[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
[Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering.
[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
[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.
[Transform Hierarchy](../examples/stress_tests/transform_hierarchy.rs) | Various test cases for hierarchy and transform propagation performance
Expand Down
73 changes: 73 additions & 0 deletions examples/stress_tests/many_glyphs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! Simple text rendering benchmark.
//!
//! Creates a `Text` with a single `TextSection` containing `100_000` glyphs,
//! and renders it with the UI in a white color and with Text2d in a red color.
use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
text::{BreakLineOn, Text2dBounds},
window::{PresentMode, WindowPlugin},
};

fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
present_mode: PresentMode::Immediate,
..default()
}),
..default()
}))
.add_plugin(FrameTimeDiagnosticsPlugin::default())
.add_plugin(LogDiagnosticsPlugin::default())
.add_startup_system(setup)
.run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2dBundle::default());
let mut text = Text {
sections: vec![TextSection {
value: "0123456789".repeat(10_000),
style: TextStyle {
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 4.,
color: Color::WHITE,
},
}],
alignment: TextAlignment::Left,
linebreak_behaviour: BreakLineOn::AnyCharacter,
};

commands
.spawn(NodeBundle {
style: Style {
flex_basis: Val::Percent(100.),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
..default()
})
.with_children(|commands| {
commands.spawn(TextBundle {
text: text.clone(),
style: Style {
size: Size::width(Val::Px(1000.)),
..Default::default()
},
..Default::default()
});
});

text.sections[0].style.color = Color::RED;

commands.spawn(Text2dBundle {
text,
text_anchor: bevy::sprite::Anchor::Center,
text_2d_bounds: Text2dBounds {
size: Vec2::new(1000., f32::INFINITY),
},
..Default::default()
});
}