Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_dev_tools/src/fps_overlay.rs
6595 views
1
//! Module containing logic for FPS overlay.
2
3
use bevy_app::{Plugin, Startup, Update};
4
use bevy_asset::{Assets, Handle};
5
use bevy_camera::visibility::Visibility;
6
use bevy_color::Color;
7
use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
8
use bevy_ecs::{
9
change_detection::DetectChangesMut,
10
component::Component,
11
entity::Entity,
12
prelude::Local,
13
query::With,
14
resource::Resource,
15
schedule::{common_conditions::resource_changed, IntoScheduleConfigs},
16
system::{Commands, Query, Res, ResMut},
17
};
18
use bevy_render::storage::ShaderStorageBuffer;
19
use bevy_text::{Font, TextColor, TextFont, TextSpan};
20
use bevy_time::Time;
21
use bevy_ui::{
22
widget::{Text, TextUiWriter},
23
FlexDirection, GlobalZIndex, Node, PositionType, Val,
24
};
25
use bevy_ui_render::prelude::MaterialNode;
26
use core::time::Duration;
27
28
use crate::frame_time_graph::{
29
FrameTimeGraphConfigUniform, FrameTimeGraphPlugin, FrametimeGraphMaterial,
30
};
31
32
/// [`GlobalZIndex`] used to render the fps overlay.
33
///
34
/// We use a number slightly under `i32::MAX` so you can render on top of it if you really need to.
35
pub const FPS_OVERLAY_ZINDEX: i32 = i32::MAX - 32;
36
37
// Used to scale the frame time graph based on the fps text size
38
const FRAME_TIME_GRAPH_WIDTH_SCALE: f32 = 6.0;
39
const FRAME_TIME_GRAPH_HEIGHT_SCALE: f32 = 2.0;
40
41
/// A plugin that adds an FPS overlay to the Bevy application.
42
///
43
/// This plugin will add the [`FrameTimeDiagnosticsPlugin`] if it wasn't added before.
44
///
45
/// Note: It is recommended to use native overlay of rendering statistics when possible for lower overhead and more accurate results.
46
/// The correct way to do this will vary by platform:
47
/// - **Metal**: setting env variable `MTL_HUD_ENABLED=1`
48
#[derive(Default)]
49
pub struct FpsOverlayPlugin {
50
/// Starting configuration of overlay, this can be later be changed through [`FpsOverlayConfig`] resource.
51
pub config: FpsOverlayConfig,
52
}
53
54
impl Plugin for FpsOverlayPlugin {
55
fn build(&self, app: &mut bevy_app::App) {
56
// TODO: Use plugin dependencies, see https://github.com/bevyengine/bevy/issues/69
57
if !app.is_plugin_added::<FrameTimeDiagnosticsPlugin>() {
58
app.add_plugins(FrameTimeDiagnosticsPlugin::default());
59
}
60
61
if !app.is_plugin_added::<FrameTimeGraphPlugin>() {
62
app.add_plugins(FrameTimeGraphPlugin);
63
}
64
65
app.insert_resource(self.config.clone())
66
.add_systems(Startup, setup)
67
.add_systems(
68
Update,
69
(
70
(toggle_display, customize_overlay)
71
.run_if(resource_changed::<FpsOverlayConfig>),
72
update_text,
73
),
74
);
75
}
76
}
77
78
/// Configuration options for the FPS overlay.
79
#[derive(Resource, Clone)]
80
pub struct FpsOverlayConfig {
81
/// Configuration of text in the overlay.
82
pub text_config: TextFont,
83
/// Color of text in the overlay.
84
pub text_color: Color,
85
/// Displays the FPS overlay if true.
86
pub enabled: bool,
87
/// The period after which the FPS overlay re-renders.
88
///
89
/// Defaults to once every 100 ms.
90
pub refresh_interval: Duration,
91
/// Configuration of the frame time graph
92
pub frame_time_graph_config: FrameTimeGraphConfig,
93
}
94
95
impl Default for FpsOverlayConfig {
96
fn default() -> Self {
97
FpsOverlayConfig {
98
text_config: TextFont {
99
font: Handle::<Font>::default(),
100
font_size: 32.0,
101
..Default::default()
102
},
103
text_color: Color::WHITE,
104
enabled: true,
105
refresh_interval: Duration::from_millis(100),
106
// TODO set this to display refresh rate if possible
107
frame_time_graph_config: FrameTimeGraphConfig::target_fps(60.0),
108
}
109
}
110
}
111
112
/// Configuration of the frame time graph
113
#[derive(Clone, Copy)]
114
pub struct FrameTimeGraphConfig {
115
/// Is the graph visible
116
pub enabled: bool,
117
/// The minimum acceptable FPS
118
///
119
/// Anything below this will show a red bar
120
pub min_fps: f32,
121
/// The target FPS
122
///
123
/// Anything above this will show a green bar
124
pub target_fps: f32,
125
}
126
127
impl FrameTimeGraphConfig {
128
/// Constructs a default config for a given target fps
129
pub fn target_fps(target_fps: f32) -> Self {
130
Self {
131
target_fps,
132
..Self::default()
133
}
134
}
135
}
136
137
impl Default for FrameTimeGraphConfig {
138
fn default() -> Self {
139
Self {
140
enabled: true,
141
min_fps: 30.0,
142
target_fps: 60.0,
143
}
144
}
145
}
146
147
#[derive(Component)]
148
struct FpsText;
149
150
#[derive(Component)]
151
struct FrameTimeGraph;
152
153
fn setup(
154
mut commands: Commands,
155
overlay_config: Res<FpsOverlayConfig>,
156
mut frame_time_graph_materials: ResMut<Assets<FrametimeGraphMaterial>>,
157
mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
158
) {
159
commands
160
.spawn((
161
Node {
162
// We need to make sure the overlay doesn't affect the position of other UI nodes
163
position_type: PositionType::Absolute,
164
flex_direction: FlexDirection::Column,
165
..Default::default()
166
},
167
// Render overlay on top of everything
168
GlobalZIndex(FPS_OVERLAY_ZINDEX),
169
))
170
.with_children(|p| {
171
p.spawn((
172
Text::new("FPS: "),
173
overlay_config.text_config.clone(),
174
TextColor(overlay_config.text_color),
175
FpsText,
176
))
177
.with_child((TextSpan::default(), overlay_config.text_config.clone()));
178
179
let font_size = overlay_config.text_config.font_size;
180
p.spawn((
181
Node {
182
width: Val::Px(font_size * FRAME_TIME_GRAPH_WIDTH_SCALE),
183
height: Val::Px(font_size * FRAME_TIME_GRAPH_HEIGHT_SCALE),
184
display: if overlay_config.frame_time_graph_config.enabled {
185
bevy_ui::Display::DEFAULT
186
} else {
187
bevy_ui::Display::None
188
},
189
..Default::default()
190
},
191
MaterialNode::from(frame_time_graph_materials.add(FrametimeGraphMaterial {
192
values: buffers.add(ShaderStorageBuffer {
193
// Initialize with dummy data because the default (`data: None`) will
194
// cause a panic in the shader if the frame time graph is constructed
195
// with `enabled: false`.
196
data: Some(vec![0, 0, 0, 0]),
197
..Default::default()
198
}),
199
config: FrameTimeGraphConfigUniform::new(
200
overlay_config.frame_time_graph_config.target_fps,
201
overlay_config.frame_time_graph_config.min_fps,
202
true,
203
),
204
})),
205
FrameTimeGraph,
206
));
207
});
208
}
209
210
fn update_text(
211
diagnostic: Res<DiagnosticsStore>,
212
query: Query<Entity, With<FpsText>>,
213
mut writer: TextUiWriter,
214
time: Res<Time>,
215
config: Res<FpsOverlayConfig>,
216
mut time_since_rerender: Local<Duration>,
217
) {
218
*time_since_rerender += time.delta();
219
if *time_since_rerender >= config.refresh_interval {
220
*time_since_rerender = Duration::ZERO;
221
for entity in &query {
222
if let Some(fps) = diagnostic.get(&FrameTimeDiagnosticsPlugin::FPS)
223
&& let Some(value) = fps.smoothed()
224
{
225
*writer.text(entity, 1) = format!("{value:.2}");
226
}
227
}
228
}
229
}
230
231
fn customize_overlay(
232
overlay_config: Res<FpsOverlayConfig>,
233
query: Query<Entity, With<FpsText>>,
234
mut writer: TextUiWriter,
235
) {
236
for entity in &query {
237
writer.for_each_font(entity, |mut font| {
238
*font = overlay_config.text_config.clone();
239
});
240
writer.for_each_color(entity, |mut color| color.0 = overlay_config.text_color);
241
}
242
}
243
244
fn toggle_display(
245
overlay_config: Res<FpsOverlayConfig>,
246
mut query: Query<&mut Visibility, With<FpsText>>,
247
mut graph_style: Query<&mut Node, With<FrameTimeGraph>>,
248
) {
249
for mut visibility in &mut query {
250
visibility.set_if_neq(match overlay_config.enabled {
251
true => Visibility::Visible,
252
false => Visibility::Hidden,
253
});
254
}
255
256
if let Ok(mut graph_style) = graph_style.single_mut() {
257
if overlay_config.frame_time_graph_config.enabled {
258
// Scale the frame time graph based on the font size of the overlay
259
let font_size = overlay_config.text_config.font_size;
260
graph_style.width = Val::Px(font_size * FRAME_TIME_GRAPH_WIDTH_SCALE);
261
graph_style.height = Val::Px(font_size * FRAME_TIME_GRAPH_HEIGHT_SCALE);
262
263
graph_style.display = bevy_ui::Display::DEFAULT;
264
} else {
265
graph_style.display = bevy_ui::Display::None;
266
}
267
}
268
}
269
270