bevy_dev_tools/frame_time_graph/
mod.rs

1//! Module containing logic for the frame time graph
2
3use bevy_app::{Plugin, Update};
4use bevy_asset::{load_internal_asset, uuid_handle, Asset, Assets, Handle};
5use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
6use bevy_ecs::system::{Res, ResMut};
7use bevy_math::ops::log2;
8use bevy_reflect::TypePath;
9use bevy_render::{
10    render_resource::{AsBindGroup, ShaderType},
11    storage::ShaderStorageBuffer,
12};
13use bevy_shader::{Shader, ShaderRef};
14use bevy_ui_render::prelude::{UiMaterial, UiMaterialPlugin};
15
16use crate::fps_overlay::FpsOverlayConfig;
17
18const FRAME_TIME_GRAPH_SHADER_HANDLE: Handle<Shader> =
19    uuid_handle!("4e38163a-5782-47a5-af52-d9161472ab59");
20
21/// Plugin that sets up everything to render the frame time graph material
22pub struct FrameTimeGraphPlugin;
23
24impl Plugin for FrameTimeGraphPlugin {
25    fn build(&self, app: &mut bevy_app::App) {
26        load_internal_asset!(
27            app,
28            FRAME_TIME_GRAPH_SHADER_HANDLE,
29            "frame_time_graph.wgsl",
30            Shader::from_wgsl
31        );
32
33        // TODO: Use plugin dependencies, see https://github.com/bevyengine/bevy/issues/69
34        if !app.is_plugin_added::<FrameTimeDiagnosticsPlugin>() {
35            panic!("Requires FrameTimeDiagnosticsPlugin");
36            // app.add_plugins(FrameTimeDiagnosticsPlugin);
37        }
38
39        app.add_plugins(UiMaterialPlugin::<FrametimeGraphMaterial>::default())
40            .add_systems(Update, update_frame_time_values);
41    }
42}
43
44/// The config values sent to the frame time graph shader
45#[derive(Debug, Clone, Copy, ShaderType)]
46pub struct FrameTimeGraphConfigUniform {
47    // minimum expected delta time
48    dt_min: f32,
49    // maximum expected delta time
50    dt_max: f32,
51    dt_min_log2: f32,
52    dt_max_log2: f32,
53    // controls whether or not the bars width are proportional to their delta time
54    proportional_width: u32,
55}
56
57impl FrameTimeGraphConfigUniform {
58    /// `proportional_width`: controls whether or not the bars width are proportional to their delta time
59    pub fn new(target_fps: f32, min_fps: f32, proportional_width: bool) -> Self {
60        // we want an upper limit that is above the target otherwise the bars will disappear
61        let dt_min = 1. / (target_fps * 1.2);
62        let dt_max = 1. / min_fps;
63        Self {
64            dt_min,
65            dt_max,
66            dt_min_log2: log2(dt_min),
67            dt_max_log2: log2(dt_max),
68            proportional_width: u32::from(proportional_width),
69        }
70    }
71}
72
73/// The material used to render the frame time graph ui node
74#[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
75pub struct FrametimeGraphMaterial {
76    /// The history of the previous frame times value.
77    ///
78    /// This should be updated every frame to match the frame time history from the [`DiagnosticsStore`]
79    #[storage(0, read_only)]
80    pub values: Handle<ShaderStorageBuffer>, // Vec<f32>,
81    /// The configuration values used by the shader to control how the graph is rendered
82    #[uniform(1)]
83    pub config: FrameTimeGraphConfigUniform,
84}
85
86impl UiMaterial for FrametimeGraphMaterial {
87    fn fragment_shader() -> ShaderRef {
88        FRAME_TIME_GRAPH_SHADER_HANDLE.into()
89    }
90}
91
92/// A system that updates the frame time values sent to the frame time graph
93fn update_frame_time_values(
94    mut frame_time_graph_materials: ResMut<Assets<FrametimeGraphMaterial>>,
95    mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
96    diagnostics_store: Res<DiagnosticsStore>,
97    config: Option<Res<FpsOverlayConfig>>,
98) {
99    if !config.is_none_or(|c| c.frame_time_graph_config.enabled) {
100        return;
101    }
102    let Some(frame_time) = diagnostics_store.get(&FrameTimeDiagnosticsPlugin::FRAME_TIME) else {
103        return;
104    };
105    let frame_times = frame_time
106        .values()
107        // convert to millis
108        .map(|x| *x as f32 / 1000.0)
109        .collect::<Vec<_>>();
110    for (_, material) in frame_time_graph_materials.iter_mut() {
111        let buffer = buffers.get_mut(&material.values).unwrap();
112
113        buffer.set_data(frame_times.clone());
114    }
115}