use bevy_app::{Plugin, Update};
use bevy_asset::{load_internal_asset, uuid_handle, Asset, Assets, Handle};
use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy_ecs::{
schedule::IntoScheduleConfigs,
system::{Res, ResMut},
};
use bevy_math::ops::log2;
use bevy_reflect::TypePath;
use bevy_render::{
render_resource::{AsBindGroup, ShaderType},
storage::ShaderStorageBuffer,
};
use bevy_shader::{Shader, ShaderRef};
use bevy_ui_render::prelude::{UiMaterial, UiMaterialPlugin};
use crate::fps_overlay::{FpsOverlayConfig, FpsOverlaySystems};
const FRAME_TIME_GRAPH_SHADER_HANDLE: Handle<Shader> =
uuid_handle!("4e38163a-5782-47a5-af52-d9161472ab59");
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
);
if !app.is_plugin_added::<FrameTimeDiagnosticsPlugin>() {
panic!("Requires FrameTimeDiagnosticsPlugin");
}
app.add_plugins(UiMaterialPlugin::<FrametimeGraphMaterial>::default())
.add_systems(
Update,
update_frame_time_values.in_set(FpsOverlaySystems::UpdateText),
);
}
}
#[derive(Debug, Clone, Copy, ShaderType)]
pub struct FrameTimeGraphConfigUniform {
dt_min: f32,
dt_max: f32,
dt_min_log2: f32,
dt_max_log2: f32,
proportional_width: u32,
}
impl FrameTimeGraphConfigUniform {
pub fn new(target_fps: f32, min_fps: f32, proportional_width: bool) -> Self {
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),
}
}
}
#[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
pub struct FrametimeGraphMaterial {
#[storage(0, read_only)]
pub values: Handle<ShaderStorageBuffer>, #[uniform(1)]
pub config: FrameTimeGraphConfigUniform,
}
impl UiMaterial for FrametimeGraphMaterial {
fn fragment_shader() -> ShaderRef {
FRAME_TIME_GRAPH_SHADER_HANDLE.into()
}
}
fn update_frame_time_values(
mut frame_time_graph_materials: ResMut<Assets<FrametimeGraphMaterial>>,
mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
diagnostics_store: Res<DiagnosticsStore>,
config: Option<Res<FpsOverlayConfig>>,
) {
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()
.map(|x| *x as f32 / 1000.0)
.collect::<Vec<_>>();
for (_, material) in frame_time_graph_materials.iter_mut() {
let buffer = buffers.get_mut(&material.values).unwrap();
buffer.set_data(frame_times.clone());
}
}