diff --git a/crates/bevy_dev_tools/src/fps_overlay.rs b/crates/bevy_dev_tools/src/fps_overlay.rs index ea5c0fde4318b..120f10d3a94b1 100644 --- a/crates/bevy_dev_tools/src/fps_overlay.rs +++ b/crates/bevy_dev_tools/src/fps_overlay.rs @@ -2,7 +2,7 @@ use bevy_app::{Plugin, Startup, Update}; use bevy_asset::{Assets, Handle}; -use bevy_color::Color; +use bevy_color::{Color, LinearRgba}; use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy_ecs::{ component::Component, @@ -74,36 +74,70 @@ impl Plugin for FpsOverlayPlugin { } } +/// Configuration options for the FPS overlay position on the screen. +#[derive(Default, Copy, Clone)] +pub struct FpsOverlayPositionConfig { + /// The horizontal position of the left edge of the overlay. + pub left: Val, + /// The horizontal position of the right edge of the overlay. + pub right: Val, + /// The vertical position of the top edge of the overlay. + pub top: Val, + /// The vertical position of the bottom edge of the overlay. + pub bottom: Val, +} + /// Configuration options for the FPS overlay. #[derive(Resource, Clone)] -pub struct FpsOverlayConfig { +pub struct FpsOverlayTextConfig { /// Configuration of text in the overlay. - pub text_config: TextFont, + pub font: TextFont, /// Color of text in the overlay. - pub text_color: Color, + pub color: Color, + /// Displays the FPS text overlay if true. + pub enabled: bool, +} + +impl Default for FpsOverlayTextConfig { + fn default() -> Self { + Self { + font: TextFont { + font: Handle::::default(), + font_size: 32.0, + ..Default::default() + }, + color: Color::WHITE, + enabled: true, + } + } +} + +/// Configuration options for the FPS overlay. +#[derive(Resource, Clone)] +pub struct FpsOverlayConfig { /// Displays the FPS overlay if true. pub enabled: bool, + /// Configuration of text in the overlay. + pub text_config: FpsOverlayTextConfig, /// The period after which the FPS overlay re-renders. /// /// Defaults to once every 100 ms. pub refresh_interval: Duration, /// Configuration of the frame time graph - pub frame_time_graph_config: FrameTimeGraphConfig, + pub graph_config: FrameTimeGraphConfig, + /// Configuration of the overlay position. + pub position: FpsOverlayPositionConfig, } impl Default for FpsOverlayConfig { fn default() -> Self { FpsOverlayConfig { - text_config: TextFont { - font: Handle::::default(), - font_size: 32.0, - ..Default::default() - }, - text_color: Color::WHITE, + text_config: FpsOverlayTextConfig::default(), enabled: true, refresh_interval: Duration::from_millis(100), // TODO set this to display refresh rate if possible - frame_time_graph_config: FrameTimeGraphConfig::target_fps(60.0), + graph_config: FrameTimeGraphConfig::target_fps(60.0), + position: FpsOverlayPositionConfig::default(), } } } @@ -115,12 +149,16 @@ pub struct FrameTimeGraphConfig { pub enabled: bool, /// The minimum acceptable FPS /// - /// Anything below this will show a red bar + /// Anything below this will show a `min_color` bar pub min_fps: f32, /// The target FPS /// - /// Anything above this will show a green bar + /// Anything above this will show a `max_color` bar pub target_fps: f32, + /// The color of the bar when having lower values + pub min_color: LinearRgba, + /// The color of the bar when having higher values + pub max_color: LinearRgba, } impl FrameTimeGraphConfig { @@ -139,6 +177,8 @@ impl Default for FrameTimeGraphConfig { enabled: true, min_fps: 30.0, target_fps: 60.0, + min_color: LinearRgba::GREEN, + max_color: LinearRgba::RED, } } } @@ -161,6 +201,10 @@ fn setup( // We need to make sure the overlay doesn't affect the position of other UI nodes position_type: PositionType::Absolute, flex_direction: FlexDirection::Column, + top: overlay_config.position.top, + right: overlay_config.position.right, + bottom: overlay_config.position.bottom, + left: overlay_config.position.left, ..Default::default() }, // Render overlay on top of everything @@ -170,19 +214,19 @@ fn setup( .with_children(|p| { p.spawn(( Text::new("FPS: "), - overlay_config.text_config.clone(), - TextColor(overlay_config.text_color), + overlay_config.text_config.font.clone(), + TextColor(overlay_config.text_config.color), FpsText, Pickable::IGNORE, )) - .with_child((TextSpan::default(), overlay_config.text_config.clone())); + .with_child((TextSpan::default(), overlay_config.text_config.font.clone())); - let font_size = overlay_config.text_config.font_size; + let font_size = overlay_config.text_config.font.font_size; p.spawn(( Node { width: Val::Px(font_size * FRAME_TIME_GRAPH_WIDTH_SCALE), height: Val::Px(font_size * FRAME_TIME_GRAPH_HEIGHT_SCALE), - display: if overlay_config.frame_time_graph_config.enabled { + display: if overlay_config.graph_config.enabled { bevy_ui::Display::DEFAULT } else { bevy_ui::Display::None @@ -199,9 +243,11 @@ fn setup( ..Default::default() }), config: FrameTimeGraphConfigUniform::new( - overlay_config.frame_time_graph_config.target_fps, - overlay_config.frame_time_graph_config.min_fps, + overlay_config.graph_config.target_fps, + overlay_config.graph_config.min_fps, true, + overlay_config.graph_config.min_color, + overlay_config.graph_config.max_color, ), })), FrameTimeGraph, @@ -237,9 +283,11 @@ fn customize_overlay( ) { for entity in &query { writer.for_each_font(entity, |mut font| { - *font = overlay_config.text_config.clone(); + *font = overlay_config.text_config.font.clone(); + }); + writer.for_each_color(entity, |mut color| { + color.0 = overlay_config.text_config.color; }); - writer.for_each_color(entity, |mut color| color.0 = overlay_config.text_color); } } @@ -248,15 +296,15 @@ fn toggle_display( mut text_node: Single<&mut Node, (With, Without)>, mut graph_node: Single<&mut Node, (With, Without)>, ) { - if overlay_config.enabled { + if overlay_config.enabled && overlay_config.text_config.enabled { text_node.display = bevy_ui::Display::DEFAULT; } else { text_node.display = bevy_ui::Display::None; } - if overlay_config.frame_time_graph_config.enabled { + if overlay_config.enabled && overlay_config.graph_config.enabled { // Scale the frame time graph based on the font size of the overlay - let font_size = overlay_config.text_config.font_size; + let font_size = overlay_config.text_config.font.font_size; graph_node.width = Val::Px(font_size * FRAME_TIME_GRAPH_WIDTH_SCALE); graph_node.height = Val::Px(font_size * FRAME_TIME_GRAPH_HEIGHT_SCALE); diff --git a/crates/bevy_dev_tools/src/frame_time_graph/frame_time_graph.wgsl b/crates/bevy_dev_tools/src/frame_time_graph/frame_time_graph.wgsl index 82b5a46cc71d6..af73b656181bd 100644 --- a/crates/bevy_dev_tools/src/frame_time_graph/frame_time_graph.wgsl +++ b/crates/bevy_dev_tools/src/frame_time_graph/frame_time_graph.wgsl @@ -7,16 +7,15 @@ struct Config { dt_min_log2: f32, dt_max_log2: f32, proportional_width: u32, + min_color: vec4, + max_color: vec4, } @group(1) @binding(1) var config: Config; -const RED: vec4 = vec4(1.0, 0.0, 0.0, 1.0); -const GREEN: vec4 = vec4(0.0, 1.0, 0.0, 1.0); - // Gets a color based on the delta time // TODO use customizable gradient fn color_from_dt(dt: f32) -> vec4 { - return mix(GREEN, RED, dt / config.dt_max); + return mix(config.min_color, config.max_color, dt / config.dt_max); } // Draw an SDF square diff --git a/crates/bevy_dev_tools/src/frame_time_graph/mod.rs b/crates/bevy_dev_tools/src/frame_time_graph/mod.rs index 8412b186531d9..97883d1bb5686 100644 --- a/crates/bevy_dev_tools/src/frame_time_graph/mod.rs +++ b/crates/bevy_dev_tools/src/frame_time_graph/mod.rs @@ -2,6 +2,7 @@ use bevy_app::{Plugin, Update}; use bevy_asset::{load_internal_asset, uuid_handle, Asset, Assets, Handle}; +use bevy_color::LinearRgba; use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy_ecs::system::{Res, ResMut}; use bevy_math::ops::log2; @@ -52,11 +53,19 @@ pub struct FrameTimeGraphConfigUniform { dt_max_log2: f32, // controls whether or not the bars width are proportional to their delta time proportional_width: u32, + min_color: LinearRgba, + max_color: LinearRgba, } impl FrameTimeGraphConfigUniform { /// `proportional_width`: controls whether or not the bars width are proportional to their delta time - pub fn new(target_fps: f32, min_fps: f32, proportional_width: bool) -> Self { + pub fn new( + target_fps: f32, + min_fps: f32, + proportional_width: bool, + min_color: LinearRgba, + max_color: LinearRgba, + ) -> Self { // we want an upper limit that is above the target otherwise the bars will disappear let dt_min = 1. / (target_fps * 1.2); let dt_max = 1. / min_fps; @@ -66,6 +75,8 @@ impl FrameTimeGraphConfigUniform { dt_min_log2: log2(dt_min), dt_max_log2: log2(dt_max), proportional_width: u32::from(proportional_width), + min_color, + max_color, } } } @@ -96,7 +107,7 @@ fn update_frame_time_values( diagnostics_store: Res, config: Option>, ) { - if !config.is_none_or(|c| c.frame_time_graph_config.enabled) { + if !config.is_none_or(|c| c.graph_config.enabled) { return; } let Some(frame_time) = diagnostics_store.get(&FrameTimeDiagnosticsPlugin::FRAME_TIME) else { diff --git a/examples/dev_tools/fps_overlay.rs b/examples/dev_tools/fps_overlay.rs index 5637706c756ed..e2bc70777e42b 100644 --- a/examples/dev_tools/fps_overlay.rs +++ b/examples/dev_tools/fps_overlay.rs @@ -1,7 +1,10 @@ //! Showcase how to use and configure FPS overlay. use bevy::{ - dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin, FrameTimeGraphConfig}, + dev_tools::fps_overlay::{ + FpsOverlayConfig, FpsOverlayPlugin, FpsOverlayPositionConfig, FpsOverlayTextConfig, + FrameTimeGraphConfig, + }, prelude::*, text::FontSmoothing, }; @@ -19,26 +22,42 @@ fn main() { DefaultPlugins, FpsOverlayPlugin { config: FpsOverlayConfig { - text_config: TextFont { - // Here we define size of our overlay - font_size: 42.0, - // If we want, we can use a custom font - font: default(), - // We could also disable font smoothing, - font_smoothing: FontSmoothing::default(), - ..default() + // Configure the fps text on the overlay + text_config: FpsOverlayTextConfig { + // Enable or disable only the fps text + enabled: true, + font: TextFont { + // Here we define size of our overlay + font_size: 42.0, + // If we want, we can use a custom font + font: default(), + // We could also disable font smoothing, + font_smoothing: FontSmoothing::default(), + ..default() + }, + // We can also change color of the overlay + color: OverlayColor::GREEN, }, - // We can also change color of the overlay - text_color: OverlayColor::GREEN, // We can also set the refresh interval for the FPS counter refresh_interval: core::time::Duration::from_millis(100), + // Enable or disable the entire fps overlay enabled: true, - frame_time_graph_config: FrameTimeGraphConfig { + graph_config: FrameTimeGraphConfig { enabled: true, // The minimum acceptable fps min_fps: 30.0, // The target fps target_fps: 144.0, + // When the bar is low, this color will be used + min_color: LinearRgba::GREEN, + // When the bar is high, this color will be used + max_color: LinearRgba::RED, + }, + // Set custom positioning of the overlay. Defaults to top-left on the screen. + position: FpsOverlayPositionConfig { + top: px(1.0), + left: px(1.0), + ..default() }, }, }, @@ -59,8 +78,9 @@ fn setup(mut commands: Commands) { "Press 1 to toggle the overlay color.\n", "Press 2 to decrease the overlay size.\n", "Press 3 to increase the overlay size.\n", - "Press 4 to toggle the text visibility.\n", - "Press 5 to toggle the frame time graph." + "Press 4 to toggle the overlay visibility.\n", + "Press 5 to toggle the frame time graph.\n", + "Press 6 to toggle the text visibility.", )), Node { position_type: PositionType::Absolute, @@ -74,22 +94,25 @@ fn setup(mut commands: Commands) { fn customize_config(input: Res>, mut overlay: ResMut) { if input.just_pressed(KeyCode::Digit1) { // Changing resource will affect overlay - if overlay.text_color == OverlayColor::GREEN { - overlay.text_color = OverlayColor::RED; + if overlay.text_config.color == OverlayColor::GREEN { + overlay.text_config.color = OverlayColor::RED; } else { - overlay.text_color = OverlayColor::GREEN; + overlay.text_config.color = OverlayColor::GREEN; } } if input.just_pressed(KeyCode::Digit2) { - overlay.text_config.font_size -= 2.0; + overlay.text_config.font.font_size -= 2.0; } if input.just_pressed(KeyCode::Digit3) { - overlay.text_config.font_size += 2.0; + overlay.text_config.font.font_size += 2.0; } if input.just_pressed(KeyCode::Digit4) { overlay.enabled = !overlay.enabled; } if input.just_released(KeyCode::Digit5) { - overlay.frame_time_graph_config.enabled = !overlay.frame_time_graph_config.enabled; + overlay.graph_config.enabled = !overlay.graph_config.enabled; + } + if input.just_released(KeyCode::Digit6) { + overlay.text_config.enabled = !overlay.text_config.enabled; } } diff --git a/tests/window/desktop_request_redraw.rs b/tests/window/desktop_request_redraw.rs index ccaafd86579db..4a75928e73052 100644 --- a/tests/window/desktop_request_redraw.rs +++ b/tests/window/desktop_request_redraw.rs @@ -1,6 +1,6 @@ //! Desktop request redraw use bevy::{ - dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin}, + dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin, FpsOverlayTextConfig}, prelude::*, window::RequestRedraw, winit::WinitSettings, @@ -18,11 +18,14 @@ fn main() { // Left and Right clicking the cube should roggle rotation on/off. .add_plugins(FpsOverlayPlugin { config: FpsOverlayConfig { - text_config: TextFont { - font_size: 12.0, + text_config: FpsOverlayTextConfig { + font: TextFont { + font_size: 12.0, + ..default() + }, + color: Color::srgb(0.0, 1.0, 0.0), ..default() }, - text_color: Color::srgb(0.0, 1.0, 0.0), refresh_interval: core::time::Duration::from_millis(16), ..default() },