Skip to content

Commit 6f7d0e5

Browse files
authored
split up TextStyle (#15857)
# Objective Currently text is recomputed unnecessarily on any changes to its color, which is extremely expensive. ## Solution Split up `TextStyle` into two separate components `TextFont` and `TextColor`. ## Testing I added this system to `many_buttons`: ```rust fn set_text_colors_changed(mut colors: Query<&mut TextColor>) { for mut text_color in colors.iter_mut() { text_color.set_changed(); } } ``` reports ~4fps on main, ~50fps with this PR. ## Migration Guide `TextStyle` has been renamed to `TextFont` and its `color` field has been moved to a separate component named `TextColor` which newtypes `Color`.
1 parent 6521e75 commit 6f7d0e5

File tree

83 files changed

+752
-641
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+752
-641
lines changed

crates/bevy_dev_tools/src/fps_overlay.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use bevy_ecs::{
1414
};
1515
use bevy_hierarchy::{BuildChildren, ChildBuild};
1616
use bevy_render::view::Visibility;
17-
use bevy_text::{Font, TextSpan, TextStyle};
17+
use bevy_text::{Font, TextColor, TextFont, TextSpan};
1818
use bevy_ui::{
1919
node_bundles::NodeBundle,
2020
widget::{Text, UiTextWriter},
@@ -62,20 +62,22 @@ impl Plugin for FpsOverlayPlugin {
6262
#[derive(Resource, Clone)]
6363
pub struct FpsOverlayConfig {
6464
/// Configuration of text in the overlay.
65-
pub text_config: TextStyle,
65+
pub text_config: TextFont,
66+
/// Color of text in the overlay.
67+
pub text_color: Color,
6668
/// Displays the FPS overlay if true.
6769
pub enabled: bool,
6870
}
6971

7072
impl Default for FpsOverlayConfig {
7173
fn default() -> Self {
7274
FpsOverlayConfig {
73-
text_config: TextStyle {
75+
text_config: TextFont {
7476
font: Handle::<Font>::default(),
7577
font_size: 32.0,
76-
color: Color::WHITE,
7778
..default()
7879
},
80+
text_color: Color::WHITE,
7981
enabled: true,
8082
}
8183
}
@@ -102,6 +104,7 @@ fn setup(mut commands: Commands, overlay_config: Res<FpsOverlayConfig>) {
102104
p.spawn((
103105
Text::new("FPS: "),
104106
overlay_config.text_config.clone(),
107+
TextColor(overlay_config.text_color),
105108
FpsText,
106109
))
107110
.with_child((TextSpan::default(), overlay_config.text_config.clone()));
@@ -128,9 +131,10 @@ fn customize_text(
128131
mut writer: UiTextWriter,
129132
) {
130133
for entity in &query {
131-
writer.for_each_style(entity, |mut style| {
132-
*style = overlay_config.text_config.clone();
134+
writer.for_each_font(entity, |mut font| {
135+
*font = overlay_config.text_config.clone();
133136
});
137+
writer.for_each_color(entity, |mut color| color.0 = overlay_config.text_color);
134138
}
135139
}
136140

crates/bevy_text/src/font_atlas_set.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ pub struct FontAtlasKey(pub u32, pub FontSmoothing);
6060
/// A `FontAtlasSet` is an [`Asset`].
6161
///
6262
/// There is one `FontAtlasSet` for each font:
63-
/// - When a [`Font`] is loaded as an asset and then used in [`TextStyle`](crate::TextStyle),
63+
/// - When a [`Font`] is loaded as an asset and then used in [`TextFont`](crate::TextFont),
6464
/// a `FontAtlasSet` asset is created from a weak handle to the `Font`.
65-
/// - ~When a font is loaded as a system font, and then used in [`TextStyle`](crate::TextStyle),
65+
/// - ~When a font is loaded as a system font, and then used in [`TextFont`](crate::TextFont),
6666
/// a `FontAtlasSet` asset is created and stored with a strong handle to the `FontAtlasSet`.~
6767
/// (*Note that system fonts are not currently supported by the `TextPipeline`.*)
6868
///

crates/bevy_text/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ pub use text_access::*;
6565
pub mod prelude {
6666
#[doc(hidden)]
6767
pub use crate::{
68-
Font, JustifyText, LineBreak, Text2d, TextError, TextLayout, TextReader2d, TextSpan,
69-
TextStyle, TextWriter2d,
68+
Font, JustifyText, LineBreak, Text2d, TextColor, TextError, TextFont, TextLayout,
69+
TextReader2d, TextSpan, TextWriter2d,
7070
};
7171
}
7272

crates/bevy_text/src/pipeline.rs

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use alloc::sync::Arc;
22

33
use bevy_asset::{AssetId, Assets};
4+
use bevy_color::Color;
45
use bevy_ecs::{
56
component::Component,
67
entity::Entity,
@@ -17,7 +18,7 @@ use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap};
1718

1819
use crate::{
1920
error::TextError, ComputedTextBlock, Font, FontAtlasSets, FontSmoothing, JustifyText,
20-
LineBreak, PositionedGlyph, TextBounds, TextEntity, TextLayout, TextStyle, YAxisOrientation,
21+
LineBreak, PositionedGlyph, TextBounds, TextEntity, TextFont, TextLayout, YAxisOrientation,
2122
};
2223

2324
/// A wrapper resource around a [`cosmic_text::FontSystem`]
@@ -70,7 +71,7 @@ pub struct TextPipeline {
7071
/// Buffered vec for collecting spans.
7172
///
7273
/// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10).
73-
spans_buffer: Vec<(usize, &'static str, &'static TextStyle, FontFaceInfo)>,
74+
spans_buffer: Vec<(usize, &'static str, &'static TextFont, FontFaceInfo)>,
7475
/// Buffered vec for collecting info for glyph assembly.
7576
glyph_info: Vec<(AssetId<Font>, FontSmoothing)>,
7677
}
@@ -83,7 +84,7 @@ impl TextPipeline {
8384
pub fn update_buffer<'a>(
8485
&mut self,
8586
fonts: &Assets<Font>,
86-
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>,
87+
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color)>,
8788
linebreak: LineBreak,
8889
justify: JustifyText,
8990
bounds: TextBounds,
@@ -96,22 +97,22 @@ impl TextPipeline {
9697
// Collect span information into a vec. This is necessary because font loading requires mut access
9798
// to FontSystem, which the cosmic-text Buffer also needs.
9899
let mut font_size: f32 = 0.;
99-
let mut spans: Vec<(usize, &str, &TextStyle, FontFaceInfo)> =
100+
let mut spans: Vec<(usize, &str, &TextFont, FontFaceInfo, Color)> =
100101
core::mem::take(&mut self.spans_buffer)
101102
.into_iter()
102-
.map(|_| -> (usize, &str, &TextStyle, FontFaceInfo) { unreachable!() })
103+
.map(|_| -> (usize, &str, &TextFont, FontFaceInfo, Color) { unreachable!() })
103104
.collect();
104105

105106
computed.entities.clear();
106107

107-
for (span_index, (entity, depth, span, style)) in text_spans.enumerate() {
108+
for (span_index, (entity, depth, span, text_font, color)) in text_spans.enumerate() {
108109
// Return early if a font is not loaded yet.
109-
if !fonts.contains(style.font.id()) {
110+
if !fonts.contains(text_font.font.id()) {
110111
spans.clear();
111112
self.spans_buffer = spans
112113
.into_iter()
113114
.map(
114-
|_| -> (usize, &'static str, &'static TextStyle, FontFaceInfo) {
115+
|_| -> (usize, &'static str, &'static TextFont, FontFaceInfo) {
115116
unreachable!()
116117
},
117118
)
@@ -124,17 +125,21 @@ impl TextPipeline {
124125
computed.entities.push(TextEntity { entity, depth });
125126

126127
// Get max font size for use in cosmic Metrics.
127-
font_size = font_size.max(style.font_size);
128+
font_size = font_size.max(text_font.font_size);
128129

129130
// Load Bevy fonts into cosmic-text's font system.
130-
let face_info =
131-
load_font_to_fontdb(style, font_system, &mut self.map_handle_to_font_id, fonts);
131+
let face_info = load_font_to_fontdb(
132+
text_font,
133+
font_system,
134+
&mut self.map_handle_to_font_id,
135+
fonts,
136+
);
132137

133138
// Save spans that aren't zero-sized.
134-
if scale_factor <= 0.0 || style.font_size <= 0.0 {
139+
if scale_factor <= 0.0 || text_font.font_size <= 0.0 {
135140
continue;
136141
}
137-
spans.push((span_index, span, style, face_info));
142+
spans.push((span_index, span, text_font, face_info, color));
138143
}
139144

140145
let line_height = font_size * 1.2;
@@ -151,12 +156,14 @@ impl TextPipeline {
151156
// The section index is stored in the metadata of the spans, and could be used
152157
// to look up the section the span came from and is not used internally
153158
// in cosmic-text.
154-
let spans_iter = spans.iter().map(|(span_index, span, style, font_info)| {
155-
(
156-
*span,
157-
get_attrs(*span_index, style, font_info, scale_factor),
158-
)
159-
});
159+
let spans_iter = spans
160+
.iter()
161+
.map(|(span_index, span, text_font, font_info, color)| {
162+
(
163+
*span,
164+
get_attrs(*span_index, text_font, *color, font_info, scale_factor),
165+
)
166+
});
160167

161168
// Update the buffer.
162169
let buffer = &mut computed.buffer;
@@ -186,7 +193,7 @@ impl TextPipeline {
186193
spans.clear();
187194
self.spans_buffer = spans
188195
.into_iter()
189-
.map(|_| -> (usize, &'static str, &'static TextStyle, FontFaceInfo) { unreachable!() })
196+
.map(|_| -> (usize, &'static str, &'static TextFont, FontFaceInfo) { unreachable!() })
190197
.collect();
191198

192199
Ok(())
@@ -201,7 +208,7 @@ impl TextPipeline {
201208
&mut self,
202209
layout_info: &mut TextLayoutInfo,
203210
fonts: &Assets<Font>,
204-
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>,
211+
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color)>,
205212
scale_factor: f64,
206213
layout: &TextLayout,
207214
bounds: TextBounds,
@@ -222,8 +229,8 @@ impl TextPipeline {
222229
// Extract font ids from the iterator while traversing it.
223230
let mut glyph_info = core::mem::take(&mut self.glyph_info);
224231
glyph_info.clear();
225-
let text_spans = text_spans.inspect(|(_, _, _, style)| {
226-
glyph_info.push((style.font.id(), style.font_smoothing));
232+
let text_spans = text_spans.inspect(|(_, _, _, text_font, _)| {
233+
glyph_info.push((text_font.font.id(), text_font.font_smoothing));
227234
});
228235

229236
let update_result = self.update_buffer(
@@ -335,7 +342,7 @@ impl TextPipeline {
335342
&mut self,
336343
entity: Entity,
337344
fonts: &Assets<Font>,
338-
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>,
345+
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color)>,
339346
scale_factor: f64,
340347
layout: &TextLayout,
341348
computed: &mut ComputedTextBlock,
@@ -427,12 +434,12 @@ impl TextMeasureInfo {
427434
}
428435

429436
fn load_font_to_fontdb(
430-
style: &TextStyle,
437+
text_font: &TextFont,
431438
font_system: &mut cosmic_text::FontSystem,
432439
map_handle_to_font_id: &mut HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, Arc<str>)>,
433440
fonts: &Assets<Font>,
434441
) -> FontFaceInfo {
435-
let font_handle = style.font.clone();
442+
let font_handle = text_font.font.clone();
436443
let (face_id, family_name) = map_handle_to_font_id
437444
.entry(font_handle.id())
438445
.or_insert_with(|| {
@@ -461,10 +468,11 @@ fn load_font_to_fontdb(
461468
}
462469
}
463470

464-
/// Translates [`TextStyle`] to [`Attrs`].
471+
/// Translates [`TextFont`] to [`Attrs`].
465472
fn get_attrs<'a>(
466473
span_index: usize,
467-
style: &TextStyle,
474+
text_font: &TextFont,
475+
color: Color,
468476
face_info: &'a FontFaceInfo,
469477
scale_factor: f64,
470478
) -> Attrs<'a> {
@@ -474,8 +482,8 @@ fn get_attrs<'a>(
474482
.stretch(face_info.stretch)
475483
.style(face_info.style)
476484
.weight(face_info.weight)
477-
.metrics(Metrics::relative(style.font_size, 1.2).scale(scale_factor as f32))
478-
.color(cosmic_text::Color(style.color.to_linear().as_u32()));
485+
.metrics(Metrics::relative(text_font.font_size, 1.2).scale(scale_factor as f32))
486+
.color(cosmic_text::Color(color.to_linear().as_u32()));
479487
attrs
480488
}
481489

0 commit comments

Comments
 (0)