From 6fc4bc126d593b33859a172346be6838578266f5 Mon Sep 17 00:00:00 2001 From: atlv Date: Wed, 9 Jul 2025 02:05:45 -0400 Subject: [PATCH 1/3] Split out spot_light_world_from_view into a function in shadows.wgsl (#20050) # Objective - Improve readability ## Solution - Split a function out ## Testing - spotlight example works --- crates/bevy_pbr/src/render/shadows.wgsl | 28 +++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/bevy_pbr/src/render/shadows.wgsl b/crates/bevy_pbr/src/render/shadows.wgsl index 0e539f0009..ef2f73e968 100644 --- a/crates/bevy_pbr/src/render/shadows.wgsl +++ b/crates/bevy_pbr/src/render/shadows.wgsl @@ -62,6 +62,22 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: v return sample_shadow_cubemap(frag_ls * flip_z, distance_to_light, depth, light_id); } +// this method of constructing a basis from a vec3 is used by glam::Vec3::any_orthonormal_pair +// so we reproduce it here to avoid a mismatch if glam changes. we also switch the handedness +// the construction of the orthonormal basis up and right vectors needs to precisely mirror the code +// in bevy_light/spot_light.rs:spot_light_world_from_view +fn spot_light_world_from_view(fwd: vec3) -> mat3x3 { + var sign = -1.0; + if (fwd.z >= 0.0) { + sign = 1.0; + } + let a = -1.0 / (fwd.z + sign); + let b = fwd.x * fwd.y * a; + let up_dir = vec3(1.0 + sign * fwd.x * fwd.x * a, sign * b, -sign * fwd.x); + let right_dir = vec3(-b, -sign - fwd.y * fwd.y * a, fwd.y); + return mat3x3(right_dir, up_dir, fwd); +} + fn fetch_spot_shadow( light_id: u32, frag_position: vec4, @@ -88,17 +104,7 @@ fn fetch_spot_shadow( + ((*light).shadow_depth_bias * normalize(surface_to_light)) + (surface_normal.xyz * (*light).shadow_normal_bias) * distance_to_light; - // the construction of the up and right vectors needs to precisely mirror the code - // in render/light.rs:spot_light_view_matrix - var sign = -1.0; - if (fwd.z >= 0.0) { - sign = 1.0; - } - let a = -1.0 / (fwd.z + sign); - let b = fwd.x * fwd.y * a; - let up_dir = vec3(1.0 + sign * fwd.x * fwd.x * a, sign * b, -sign * fwd.x); - let right_dir = vec3(-b, -sign - fwd.y * fwd.y * a, fwd.y); - let light_inv_rot = mat3x3(right_dir, up_dir, fwd); + let light_inv_rot = spot_light_world_from_view(fwd); // because the matrix is a pure rotation matrix, the inverse is just the transpose, and to calculate // the product of the transpose with a vector we can just post-multiply instead of pre-multiplying. From d40c5b54ae3da86feea5070e9fc223b8f791345e Mon Sep 17 00:00:00 2001 From: Brian Reavis Date: Tue, 8 Jul 2025 23:23:44 -0700 Subject: [PATCH 2/3] Material, mesh, skin extraction optimization (#17976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective The extraction systems for materials, meshes, and skins previously iterated over `RemovedComponents` in addition to more specific variants like `RemovedComponents>`. This caused each system to loop through and check many irrelevant despawned entities—sometimes multiple times. With many material types, this overhead added up and became noticeable in frames with many despawns. Screenshot 2025-02-21 at 10 28 01 AM ## Solution This PR removes superfluous `RemovedComponents` iteration for `ViewVisibility` and `GlobalTransform`, ensuring that we only iterate over the most specific `RemovedComponents` relevant to the system (e.g., material components, mesh components). This is guaranteed to match what the system originally collected. ### Before (red) / After (yellow): Screenshot 2025-02-21 at 10 46 17 AM Log plot to highlight the long tail that this PR is addressing. --- crates/bevy_pbr/src/material.rs | 4 ++-- crates/bevy_pbr/src/render/mesh.rs | 8 +------- crates/bevy_pbr/src/render/skin.rs | 6 +----- crates/bevy_sprite/src/mesh2d/material.rs | 6 +----- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index cc3a69a0ad..2e642dba92 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -746,11 +746,11 @@ fn early_sweep_material_instances( /// preparation for a new frame. pub(crate) fn late_sweep_material_instances( mut material_instances: ResMut, - mut removed_visibilities_query: Extract>, + mut removed_meshes_query: Extract>, ) { let last_change_tick = material_instances.current_change_tick; - for entity in removed_visibilities_query.read() { + for entity in removed_meshes_query.read() { if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) { // Only sweep the entry if it wasn't updated this frame. It's // possible that a `ViewVisibility` component was removed and diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 2e9562c5f8..53b3b4129a 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1452,8 +1452,6 @@ pub fn extract_meshes_for_gpu_building( >, >, all_meshes_query: Extract>, - mut removed_visibilities_query: Extract>, - mut removed_global_transforms_query: Extract>, mut removed_meshes_query: Extract>, gpu_culling_query: Extract, Without)>>, meshes_to_reextract_next_frame: ResMut, @@ -1509,11 +1507,7 @@ pub fn extract_meshes_for_gpu_building( } // Also record info about each mesh that became invisible. - for entity in removed_visibilities_query - .read() - .chain(removed_global_transforms_query.read()) - .chain(removed_meshes_query.read()) - { + for entity in removed_meshes_query.read() { // Only queue a mesh for removal if we didn't pick it up above. // It's possible that a necessary component was removed and re-added in // the same frame. diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index f9ec672a66..ca035c980a 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -309,7 +309,6 @@ pub fn extract_skins( skinned_mesh_inverse_bindposes: Extract>>, changed_transforms: Extract>>, joints: Extract>, - mut removed_visibilities_query: Extract>, mut removed_skinned_meshes_query: Extract>, ) { let skin_uniforms = skin_uniforms.into_inner(); @@ -335,10 +334,7 @@ pub fn extract_skins( ); // Delete skins that became invisible. - for skinned_mesh_entity in removed_visibilities_query - .read() - .chain(removed_skinned_meshes_query.read()) - { + for skinned_mesh_entity in removed_skinned_meshes_query.read() { // Only remove a skin if we didn't pick it up in `add_or_delete_skins`. // It's possible that a necessary component was removed and re-added in // the same frame. diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 06914690ca..4117e39823 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -331,7 +331,6 @@ pub fn extract_mesh_materials_2d( Or<(Changed, Changed>)>, >, >, - mut removed_visibilities_query: Extract>, mut removed_materials_query: Extract>>, ) { for (entity, view_visibility, material) in &changed_meshes_query { @@ -342,10 +341,7 @@ pub fn extract_mesh_materials_2d( } } - for entity in removed_visibilities_query - .read() - .chain(removed_materials_query.read()) - { + for entity in removed_materials_query.read() { // Only queue a mesh for removal if we didn't pick it up above. // It's possible that a necessary component was removed and re-added in // the same frame. From d45ae7428607cb8d94d4e8ede1853f0d865c7bcb Mon Sep 17 00:00:00 2001 From: Daniel Skates Date: Thu, 10 Jul 2025 00:59:21 +0800 Subject: [PATCH 3/3] Add frame_time graph to fps_overlay v2 (#19277) # Objective - Rebase of https://github.com/bevyengine/bevy/pull/12561 , note that this is blocked on "up-streaming [iyes_perf_ui](https://crates.io/crates/iyes_perf_ui)" , but that work seems to also be stalled > Frame time is often more important to know than FPS but because of the temporal nature of it, just seeing a number is not enough. Seeing a graph that shows the history makes it easier to reason about performance. ## Solution > This PR adds a bar graph of the frame time history. > > Each bar is scaled based on the frame time where a bigger frame time will give a taller and wider bar. > > The color also scales with that frame time where red is at or bellow the minimum target fps and green is at or above the target maximum frame rate. Anything between those 2 values will be interpolated between green and red based on the frame time. > > The algorithm is highly inspired by this article: https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times ## Testing - Ran `cargo run --example fps_overlay --features="bevy_dev_tools"` --------- Co-authored-by: IceSentry Co-authored-by: Alice Cecile --- crates/bevy_dev_tools/Cargo.toml | 2 + crates/bevy_dev_tools/src/fps_overlay.rs | 114 ++++++++++++++++-- .../frame_time_graph/frame_time_graph.wgsl | 68 +++++++++++ .../src/frame_time_graph/mod.rs | 114 ++++++++++++++++++ crates/bevy_dev_tools/src/lib.rs | 1 + examples/dev_tools/fps_overlay.rs | 15 ++- .../release-notes/frame_time_graph.md | 18 +++ 7 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 crates/bevy_dev_tools/src/frame_time_graph/frame_time_graph.wgsl create mode 100644 crates/bevy_dev_tools/src/frame_time_graph/mod.rs create mode 100644 release-content/release-notes/frame_time_graph.md diff --git a/crates/bevy_dev_tools/Cargo.toml b/crates/bevy_dev_tools/Cargo.toml index 3f0efb1c21..d31fe7ef22 100644 --- a/crates/bevy_dev_tools/Cargo.toml +++ b/crates/bevy_dev_tools/Cargo.toml @@ -18,12 +18,14 @@ bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" } bevy_color = { path = "../bevy_color", version = "0.17.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" } bevy_render = { path = "../bevy_render", version = "0.17.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_time = { path = "../bevy_time", version = "0.17.0-dev" } bevy_text = { path = "../bevy_text", version = "0.17.0-dev" } bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" } +bevy_ui_render = { path = "../bevy_ui_render", version = "0.17.0-dev" } bevy_window = { path = "../bevy_window", version = "0.17.0-dev" } bevy_state = { path = "../bevy_state", version = "0.17.0-dev" } diff --git a/crates/bevy_dev_tools/src/fps_overlay.rs b/crates/bevy_dev_tools/src/fps_overlay.rs index 7c29ae3adc..435a0d2faf 100644 --- a/crates/bevy_dev_tools/src/fps_overlay.rs +++ b/crates/bevy_dev_tools/src/fps_overlay.rs @@ -1,7 +1,7 @@ //! Module containing logic for FPS overlay. use bevy_app::{Plugin, Startup, Update}; -use bevy_asset::Handle; +use bevy_asset::{Assets, Handle}; use bevy_color::Color; use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy_ecs::{ @@ -12,22 +12,31 @@ use bevy_ecs::{ query::With, resource::Resource, schedule::{common_conditions::resource_changed, IntoScheduleConfigs}, - system::{Commands, Query, Res}, + system::{Commands, Query, Res, ResMut}, }; -use bevy_render::view::Visibility; +use bevy_render::{storage::ShaderStorageBuffer, view::Visibility}; use bevy_text::{Font, TextColor, TextFont, TextSpan}; use bevy_time::Time; use bevy_ui::{ widget::{Text, TextUiWriter}, - GlobalZIndex, Node, PositionType, + FlexDirection, GlobalZIndex, Node, PositionType, Val, }; +use bevy_ui_render::prelude::MaterialNode; use core::time::Duration; +use crate::frame_time_graph::{ + FrameTimeGraphConfigUniform, FrameTimeGraphPlugin, FrametimeGraphMaterial, +}; + /// [`GlobalZIndex`] used to render the fps overlay. /// /// We use a number slightly under `i32::MAX` so you can render on top of it if you really need to. pub const FPS_OVERLAY_ZINDEX: i32 = i32::MAX - 32; +// Used to scale the frame time graph based on the fps text size +const FRAME_TIME_GRAPH_WIDTH_SCALE: f32 = 6.0; +const FRAME_TIME_GRAPH_HEIGHT_SCALE: f32 = 2.0; + /// A plugin that adds an FPS overlay to the Bevy application. /// /// This plugin will add the [`FrameTimeDiagnosticsPlugin`] if it wasn't added before. @@ -47,12 +56,18 @@ impl Plugin for FpsOverlayPlugin { if !app.is_plugin_added::() { app.add_plugins(FrameTimeDiagnosticsPlugin::default()); } + + if !app.is_plugin_added::() { + app.add_plugins(FrameTimeGraphPlugin); + } + app.insert_resource(self.config.clone()) .add_systems(Startup, setup) .add_systems( Update, ( - (customize_text, toggle_display).run_if(resource_changed::), + (toggle_display, customize_overlay) + .run_if(resource_changed::), update_text, ), ); @@ -72,6 +87,8 @@ pub struct FpsOverlayConfig { /// /// Defaults to once every 100 ms. pub refresh_interval: Duration, + /// Configuration of the frame time graph + pub frame_time_graph_config: FrameTimeGraphConfig, } impl Default for FpsOverlayConfig { @@ -85,6 +102,43 @@ impl Default for FpsOverlayConfig { text_color: Color::WHITE, 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), + } + } +} + +/// Configuration of the frame time graph +#[derive(Clone, Copy)] +pub struct FrameTimeGraphConfig { + /// Is the graph visible + pub enabled: bool, + /// The minimum acceptable FPS + /// + /// Anything below this will show a red bar + pub min_fps: f32, + /// The target FPS + /// + /// Anything above this will show a green bar + pub target_fps: f32, +} + +impl FrameTimeGraphConfig { + /// Constructs a default config for a given target fps + pub fn target_fps(target_fps: f32) -> Self { + Self { + target_fps, + ..Self::default() + } + } +} + +impl Default for FrameTimeGraphConfig { + fn default() -> Self { + Self { + enabled: true, + min_fps: 30.0, + target_fps: 60.0, } } } @@ -92,12 +146,21 @@ impl Default for FpsOverlayConfig { #[derive(Component)] struct FpsText; -fn setup(mut commands: Commands, overlay_config: Res) { +#[derive(Component)] +struct FrameTimeGraph; + +fn setup( + mut commands: Commands, + overlay_config: Res, + mut frame_time_graph_materials: ResMut>, + mut buffers: ResMut>, +) { commands .spawn(( Node { // We need to make sure the overlay doesn't affect the position of other UI nodes position_type: PositionType::Absolute, + flex_direction: FlexDirection::Column, ..Default::default() }, // Render overlay on top of everything @@ -111,6 +174,29 @@ fn setup(mut commands: Commands, overlay_config: Res) { FpsText, )) .with_child((TextSpan::default(), overlay_config.text_config.clone())); + + let font_size = overlay_config.text_config.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 { + bevy_ui::Display::DEFAULT + } else { + bevy_ui::Display::None + }, + ..Default::default() + }, + MaterialNode::from(frame_time_graph_materials.add(FrametimeGraphMaterial { + values: buffers.add(ShaderStorageBuffer::default()), + config: FrameTimeGraphConfigUniform::new( + overlay_config.frame_time_graph_config.target_fps, + overlay_config.frame_time_graph_config.min_fps, + true, + ), + })), + FrameTimeGraph, + )); }); } @@ -135,7 +221,7 @@ fn update_text( } } -fn customize_text( +fn customize_overlay( overlay_config: Res, query: Query>, mut writer: TextUiWriter, @@ -151,6 +237,7 @@ fn customize_text( fn toggle_display( overlay_config: Res, mut query: Query<&mut Visibility, With>, + mut graph_style: Query<&mut Node, With>, ) { for mut visibility in &mut query { visibility.set_if_neq(match overlay_config.enabled { @@ -158,4 +245,17 @@ fn toggle_display( false => Visibility::Hidden, }); } + + if let Ok(mut graph_style) = graph_style.single_mut() { + if overlay_config.frame_time_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; + graph_style.width = Val::Px(font_size * FRAME_TIME_GRAPH_WIDTH_SCALE); + graph_style.height = Val::Px(font_size * FRAME_TIME_GRAPH_HEIGHT_SCALE); + + graph_style.display = bevy_ui::Display::DEFAULT; + } else { + graph_style.display = bevy_ui::Display::None; + } + } } 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 new file mode 100644 index 0000000000..82b5a46cc7 --- /dev/null +++ b/crates/bevy_dev_tools/src/frame_time_graph/frame_time_graph.wgsl @@ -0,0 +1,68 @@ +#import bevy_ui::ui_vertex_output::UiVertexOutput + +@group(1) @binding(0) var values: array; +struct Config { + dt_min: f32, + dt_max: f32, + dt_min_log2: f32, + dt_max_log2: f32, + proportional_width: u32, +} +@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); +} + +// Draw an SDF square +fn sdf_square(pos: vec2, half_size: vec2, offset: vec2) -> f32 { + let p = pos - offset; + let dist = abs(p) - half_size; + let outside_dist = length(max(dist, vec2(0.0, 0.0))); + let inside_dist = min(max(dist.x, dist.y), 0.0); + return outside_dist + inside_dist; +} + +@fragment +fn fragment(in: UiVertexOutput) -> @location(0) vec4 { + let dt_min = config.dt_min; + let dt_max = config.dt_max; + let dt_min_log2 = config.dt_min_log2; + let dt_max_log2 = config.dt_max_log2; + + // The general algorithm is highly inspired by + // + + let len = arrayLength(&values); + var graph_width = 0.0; + for (var i = 0u; i <= len; i += 1u) { + let dt = values[len - i]; + + var frame_width: f32; + if config.proportional_width == 1u { + frame_width = (dt / dt_min) / f32(len); + } else { + frame_width = 0.015; + } + + let frame_height_factor = (log2(dt) - dt_min_log2) / (dt_max_log2 - dt_min_log2); + let frame_height_factor_norm = min(max(0.0, frame_height_factor), 1.0); + let frame_height = mix(0.0, 1.0, frame_height_factor_norm); + + let size = vec2(frame_width, frame_height) / 2.0; + let offset = vec2(1.0 - graph_width - size.x, 1. - size.y); + if (sdf_square(in.uv, size, offset) < 0.0) { + return color_from_dt(dt); + } + + graph_width += frame_width; + } + + return vec4(0.0, 0.0, 0.0, 0.5); +} + diff --git a/crates/bevy_dev_tools/src/frame_time_graph/mod.rs b/crates/bevy_dev_tools/src/frame_time_graph/mod.rs new file mode 100644 index 0000000000..32f6c20006 --- /dev/null +++ b/crates/bevy_dev_tools/src/frame_time_graph/mod.rs @@ -0,0 +1,114 @@ +//! Module containing logic for the frame time graph + +use bevy_app::{Plugin, Update}; +use bevy_asset::{load_internal_asset, uuid_handle, Asset, Assets, Handle}; +use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; +use bevy_ecs::system::{Res, ResMut}; +use bevy_math::ops::log2; +use bevy_reflect::TypePath; +use bevy_render::{ + render_resource::{AsBindGroup, Shader, ShaderRef, ShaderType}, + storage::ShaderStorageBuffer, +}; +use bevy_ui_render::prelude::{UiMaterial, UiMaterialPlugin}; + +use crate::fps_overlay::FpsOverlayConfig; + +const FRAME_TIME_GRAPH_SHADER_HANDLE: Handle = + uuid_handle!("4e38163a-5782-47a5-af52-d9161472ab59"); + +/// Plugin that sets up everything to render the frame time graph material +pub struct FrameTimeGraphPlugin; + +impl Plugin for FrameTimeGraphPlugin { + fn build(&self, app: &mut bevy_app::App) { + load_internal_asset!( + app, + FRAME_TIME_GRAPH_SHADER_HANDLE, + "frame_time_graph.wgsl", + Shader::from_wgsl + ); + + // TODO: Use plugin dependencies, see https://github.com/bevyengine/bevy/issues/69 + if !app.is_plugin_added::() { + panic!("Requires FrameTimeDiagnosticsPlugin"); + // app.add_plugins(FrameTimeDiagnosticsPlugin); + } + + app.add_plugins(UiMaterialPlugin::::default()) + .add_systems(Update, update_frame_time_values); + } +} + +/// The config values sent to the frame time graph shader +#[derive(Debug, Clone, Copy, ShaderType)] +pub struct FrameTimeGraphConfigUniform { + // minimum expected delta time + dt_min: f32, + // maximum expected delta time + dt_max: f32, + dt_min_log2: f32, + dt_max_log2: f32, + // controls whether or not the bars width are proportional to their delta time + proportional_width: u32, +} + +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 { + // 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; + Self { + dt_min, + dt_max, + dt_min_log2: log2(dt_min), + dt_max_log2: log2(dt_max), + proportional_width: u32::from(proportional_width), + } + } +} + +/// The material used to render the frame time graph ui node +#[derive(AsBindGroup, Asset, TypePath, Debug, Clone)] +pub struct FrametimeGraphMaterial { + /// The history of the previous frame times value. + /// + /// This should be updated every frame to match the frame time history from the [`DiagnosticsStore`] + #[storage(0, read_only)] + pub values: Handle, // Vec, + /// The configuration values used by the shader to control how the graph is rendered + #[uniform(1)] + pub config: FrameTimeGraphConfigUniform, +} + +impl UiMaterial for FrametimeGraphMaterial { + fn fragment_shader() -> ShaderRef { + FRAME_TIME_GRAPH_SHADER_HANDLE.into() + } +} + +/// A system that updates the frame time values sent to the frame time graph +fn update_frame_time_values( + mut frame_time_graph_materials: ResMut>, + mut buffers: ResMut>, + diagnostics_store: Res, + config: Option>, +) { + if !config.is_none_or(|c| c.frame_time_graph_config.enabled) { + return; + } + let Some(frame_time) = diagnostics_store.get(&FrameTimeDiagnosticsPlugin::FRAME_TIME) else { + return; + }; + let frame_times = frame_time + .values() + // convert to millis + .map(|x| *x as f32 / 1000.0) + .collect::>(); + for (_, material) in frame_time_graph_materials.iter_mut() { + let buffer = buffers.get_mut(&material.values).unwrap(); + + buffer.set_data(frame_times.clone().as_slice()); + } +} diff --git a/crates/bevy_dev_tools/src/lib.rs b/crates/bevy_dev_tools/src/lib.rs index 5e826e3f9c..8efea87f00 100644 --- a/crates/bevy_dev_tools/src/lib.rs +++ b/crates/bevy_dev_tools/src/lib.rs @@ -14,6 +14,7 @@ use bevy_app::prelude::*; pub mod ci_testing; pub mod fps_overlay; +pub mod frame_time_graph; pub mod picking_debug; diff --git a/examples/dev_tools/fps_overlay.rs b/examples/dev_tools/fps_overlay.rs index d79bd46e9d..5287df9962 100644 --- a/examples/dev_tools/fps_overlay.rs +++ b/examples/dev_tools/fps_overlay.rs @@ -1,7 +1,7 @@ //! Showcase how to use and configure FPS overlay. use bevy::{ - dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin}, + dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin, FrameTimeGraphConfig}, prelude::*, text::FontSmoothing, }; @@ -33,6 +33,13 @@ fn main() { // We can also set the refresh interval for the FPS counter refresh_interval: core::time::Duration::from_millis(100), enabled: true, + frame_time_graph_config: FrameTimeGraphConfig { + enabled: true, + // The minimum acceptable fps + min_fps: 30.0, + // The target fps + target_fps: 144.0, + }, }, }, )) @@ -52,7 +59,8 @@ 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 overlay visibility." + "Press 4 to toggle the text visibility.\n", + "Press 5 to toggle the frame time graph." )), Node { position_type: PositionType::Absolute, @@ -81,4 +89,7 @@ fn customize_config(input: Res>, mut overlay: ResMut