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