mirror of
https://github.com/bevyengine/bevy
synced 2024-11-14 00:47:32 +00:00
Trim cosmic-text's shape run cache (#15037)
# Objective - Fixes https://github.com/bevyengine/bevy/pull/14991. The `cosmic-text` shape run cache requires manual cleanup for old text that no longer needs to be cached. ## Solution - Add a system to trim the cache. - Add an `average fps` indicator to the `text_debug` example. ## Testing Tested with `cargo run --example text_debug`. - **No shape run cache**: 82fps with ~1fps variance. - **Shape run cache no trim**: 90-100fps with ~2-4fps variance - **Shape run cache trim age = 1**: 90-100fps with ~2-8fps variance - **Shape run cache trim age = 2**: 90-100fps with ~2-4fps variance - **Shape run cache trim age = 2000**: 80-120fps with ~2-6fps variance The shape run cache seems to increase average FPS but also increases frame time variance (when there is dynamic text).
This commit is contained in:
parent
cacf3929db
commit
fa51e26052
3 changed files with 78 additions and 7 deletions
|
@ -123,7 +123,8 @@ impl Plugin for TextPlugin {
|
||||||
.ambiguous_with(CameraUpdateSystem),
|
.ambiguous_with(CameraUpdateSystem),
|
||||||
remove_dropped_font_atlas_sets,
|
remove_dropped_font_atlas_sets,
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
|
.add_systems(Last, trim_cosmic_cache);
|
||||||
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
render_app.add_systems(
|
render_app.add_systems(
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use bevy_asset::{AssetId, Assets};
|
use bevy_asset::{AssetId, Assets};
|
||||||
use bevy_ecs::{component::Component, entity::Entity, reflect::ReflectComponent, system::Resource};
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::Entity,
|
||||||
|
reflect::ReflectComponent,
|
||||||
|
system::{ResMut, Resource},
|
||||||
|
};
|
||||||
use bevy_math::{UVec2, Vec2};
|
use bevy_math::{UVec2, Vec2};
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::texture::Image;
|
use bevy_render::texture::Image;
|
||||||
|
@ -407,3 +412,13 @@ fn buffer_dimensions(buffer: &Buffer) -> Vec2 {
|
||||||
|
|
||||||
Vec2::new(width.ceil(), height).ceil()
|
Vec2::new(width.ceil(), height).ceil()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Discards stale data cached in `FontSystem`.
|
||||||
|
pub(crate) fn trim_cosmic_cache(mut pipeline: ResMut<TextPipeline>) {
|
||||||
|
// A trim age of 2 was found to reduce frame time variance vs age of 1 when tested with dynamic text.
|
||||||
|
// See https://github.com/bevyengine/bevy/pull/15037
|
||||||
|
//
|
||||||
|
// We assume only text updated frequently benefits from the shape cache (e.g. animated text, or
|
||||||
|
// text that is dynamically measured for UI).
|
||||||
|
pipeline.font_system_mut().shape_run_cache.trim(2);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! Shows various text layout options.
|
//! Shows various text layout options.
|
||||||
|
|
||||||
|
use std::{collections::VecDeque, time::Duration};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
color::palettes::css::*,
|
color::palettes::css::*,
|
||||||
diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin},
|
diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin},
|
||||||
|
@ -154,7 +156,15 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
builder.spawn((
|
builder.spawn((
|
||||||
TextBundle::from_sections([
|
TextBundle::from_sections([
|
||||||
TextSection::new(
|
TextSection::new(
|
||||||
"This text changes in the bottom right",
|
"",
|
||||||
|
TextStyle {
|
||||||
|
font: font.clone(),
|
||||||
|
font_size: 25.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextSection::new(
|
||||||
|
"\nThis text changes in the bottom right",
|
||||||
TextStyle {
|
TextStyle {
|
||||||
font: font.clone(),
|
font: font.clone(),
|
||||||
font_size: 25.0,
|
font_size: 25.0,
|
||||||
|
@ -223,10 +233,23 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_text_system(
|
fn change_text_system(
|
||||||
|
mut fps_history: Local<VecDeque<f64>>,
|
||||||
|
mut time_history: Local<VecDeque<Duration>>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
diagnostics: Res<DiagnosticsStore>,
|
diagnostics: Res<DiagnosticsStore>,
|
||||||
mut query: Query<&mut Text, With<TextChanges>>,
|
mut query: Query<&mut Text, With<TextChanges>>,
|
||||||
) {
|
) {
|
||||||
|
time_history.push_front(time.elapsed());
|
||||||
|
time_history.truncate(120);
|
||||||
|
let avg_fps = (time_history.len() as f64)
|
||||||
|
/ (time_history.front().copied().unwrap_or_default()
|
||||||
|
- time_history.back().copied().unwrap_or_default())
|
||||||
|
.as_secs_f64()
|
||||||
|
.max(0.0001);
|
||||||
|
fps_history.push_front(avg_fps);
|
||||||
|
fps_history.truncate(120);
|
||||||
|
let fps_variance = std_deviation(fps_history.make_contiguous()).unwrap_or_default();
|
||||||
|
|
||||||
for mut text in &mut query {
|
for mut text in &mut query {
|
||||||
let mut fps = 0.0;
|
let mut fps = 0.0;
|
||||||
if let Some(fps_diagnostic) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
|
if let Some(fps_diagnostic) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
|
||||||
|
@ -244,12 +267,44 @@ fn change_text_system(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
text.sections[0].value = format!(
|
text.sections[0].value =
|
||||||
"This text changes in the bottom right - {fps:.1} fps, {frame_time:.3} ms/frame",
|
format!("{avg_fps:.1} avg fps, {fps_variance:.1} frametime variance",);
|
||||||
|
|
||||||
|
text.sections[1].value = format!(
|
||||||
|
"\nThis text changes in the bottom right - {fps:.1} fps, {frame_time:.3} ms/frame",
|
||||||
);
|
);
|
||||||
|
|
||||||
text.sections[3].value = format!("{fps:.1}");
|
text.sections[4].value = format!("{fps:.1}");
|
||||||
|
|
||||||
text.sections[5].value = format!("{frame_time:.3}");
|
text.sections[6].value = format!("{frame_time:.3}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mean(data: &[f64]) -> Option<f64> {
|
||||||
|
let sum = data.iter().sum::<f64>();
|
||||||
|
let count = data.len();
|
||||||
|
|
||||||
|
match count {
|
||||||
|
positive if positive > 0 => Some(sum / count as f64),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn std_deviation(data: &[f64]) -> Option<f64> {
|
||||||
|
match (mean(data), data.len()) {
|
||||||
|
(Some(data_mean), count) if count > 0 => {
|
||||||
|
let variance = data
|
||||||
|
.iter()
|
||||||
|
.map(|value| {
|
||||||
|
let diff = data_mean - *value;
|
||||||
|
|
||||||
|
diff * diff
|
||||||
|
})
|
||||||
|
.sum::<f64>()
|
||||||
|
/ count as f64;
|
||||||
|
|
||||||
|
Some(variance.sqrt())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue