bevy_dev_tools/frame_time_graph/
mod.rs1use bevy_app::{Plugin, Update};
4use bevy_asset::{load_internal_asset, uuid_handle, Asset, Assets, Handle};
5use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
6use bevy_ecs::{
7 schedule::IntoScheduleConfigs,
8 system::{Res, ResMut},
9};
10use bevy_math::ops::log2;
11use bevy_reflect::TypePath;
12use bevy_render::{
13 render_resource::{AsBindGroup, ShaderType},
14 storage::ShaderBuffer,
15};
16use bevy_shader::{Shader, ShaderRef};
17use bevy_ui_render::prelude::{UiMaterial, UiMaterialPlugin};
18
19use crate::fps_overlay::{FpsOverlayConfig, FpsOverlaySystems};
20
21const FRAME_TIME_GRAPH_SHADER_HANDLE: Handle<Shader> =
22 {
::bevy_asset::Handle::Uuid({
const OUTPUT: ::uuid::Uuid =
match ::uuid::Uuid::try_parse("4e38163a-5782-47a5-af52-d9161472ab59")
{
::uuid::__macro_support::Ok(u) => u,
::uuid::__macro_support::Err(_) => {
::core::panicking::panic_fmt(format_args!("invalid UUID"));
}
};
OUTPUT
}, core::marker::PhantomData)
}uuid_handle!("4e38163a-5782-47a5-af52-d9161472ab59");
23
24pub struct FrameTimeGraphPlugin;
26
27impl Plugin for FrameTimeGraphPlugin {
28 fn build(&self, app: &mut bevy_app::App) {
29 {
let mut assets =
app.world_mut().resource_mut::<::bevy_asset::Assets<_>>();
assets.insert(FRAME_TIME_GRAPH_SHADER_HANDLE.id(),
(Shader::from_wgsl)("#import bevy_ui::ui_vertex_output::UiVertexOutput\n\n@group(1) @binding(0) var<storage> values: array<f32>;\nstruct Config {\n dt_min: f32,\n dt_max: f32,\n dt_min_log2: f32,\n dt_max_log2: f32,\n proportional_width: u32,\n}\n@group(1) @binding(1) var<uniform> config: Config;\n\nconst RED: vec4<f32> = vec4(1.0, 0.0, 0.0, 1.0);\nconst GREEN: vec4<f32> = vec4(0.0, 1.0, 0.0, 1.0);\n\n// Gets a color based on the delta time\n// TODO use customizable gradient\nfn color_from_dt(dt: f32) -> vec4<f32> {\n return mix(GREEN, RED, dt / config.dt_max);\n}\n\n// Draw an SDF square\nfn sdf_square(pos: vec2<f32>, half_size: vec2<f32>, offset: vec2<f32>) -> f32 {\n let p = pos - offset;\n let dist = abs(p) - half_size;\n let outside_dist = length(max(dist, vec2<f32>(0.0, 0.0)));\n let inside_dist = min(max(dist.x, dist.y), 0.0);\n return outside_dist + inside_dist;\n}\n\n@fragment\nfn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {\n let dt_min = config.dt_min;\n let dt_max = config.dt_max;\n let dt_min_log2 = config.dt_min_log2;\n let dt_max_log2 = config.dt_max_log2;\n\n // The general algorithm is highly inspired by\n // <https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times>\n\n let len = arrayLength(&values);\n var graph_width = 0.0;\n for (var i = 0u; i <= len; i += 1u) {\n let dt = values[len - i];\n\n var frame_width: f32;\n if config.proportional_width == 1u {\n frame_width = (dt / dt_min) / f32(len);\n } else {\n frame_width = 0.015;\n }\n\n let frame_height_factor = (log2(dt) - dt_min_log2) / (dt_max_log2 - dt_min_log2);\n let frame_height_factor_norm = min(max(0.0, frame_height_factor), 1.0);\n let frame_height = mix(0.0, 1.0, frame_height_factor_norm);\n\n let size = vec2(frame_width, frame_height) / 2.0;\n let offset = vec2(1.0 - graph_width - size.x, 1. - size.y);\n if (sdf_square(in.uv, size, offset) < 0.0) {\n return color_from_dt(dt);\n }\n\n graph_width += frame_width;\n }\n\n return vec4(0.0, 0.0, 0.0, 0.5);\n}\n\n",
std::path::Path::new("src/frame_time_graph/mod.rs").parent().unwrap().join("frame_time_graph.wgsl").to_string_lossy())).unwrap();
};load_internal_asset!(
30 app,
31 FRAME_TIME_GRAPH_SHADER_HANDLE,
32 "frame_time_graph.wgsl",
33 Shader::from_wgsl
34 );
35
36 if !app.is_plugin_added::<FrameTimeDiagnosticsPlugin>() {
38 {
::core::panicking::panic_fmt(format_args!("Requires FrameTimeDiagnosticsPlugin"));
};panic!("Requires FrameTimeDiagnosticsPlugin");
39 }
41
42 app.add_plugins(UiMaterialPlugin::<FrametimeGraphMaterial>::default())
43 .add_systems(
44 Update,
45 update_frame_time_values.in_set(FpsOverlaySystems::UpdateText),
46 );
47 }
48}
49
50#[derive(#[automatically_derived]
impl ::core::fmt::Debug for FrameTimeGraphConfigUniform {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f,
"FrameTimeGraphConfigUniform", "dt_min", &self.dt_min, "dt_max",
&self.dt_max, "dt_min_log2", &self.dt_min_log2, "dt_max_log2",
&self.dt_max_log2, "proportional_width",
&&self.proportional_width)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for FrameTimeGraphConfigUniform {
#[inline]
fn clone(&self) -> FrameTimeGraphConfigUniform {
let _: ::core::clone::AssertParamIsClone<f32>;
let _: ::core::clone::AssertParamIsClone<u32>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for FrameTimeGraphConfigUniform { }Copy, impl bevy_render::render_resource::encase::private::ShaderSize for
FrameTimeGraphConfigUniform where
f32: bevy_render::render_resource::encase::private::ShaderSize,
f32: bevy_render::render_resource::encase::private::ShaderSize,
f32: bevy_render::render_resource::encase::private::ShaderSize,
f32: bevy_render::render_resource::encase::private::ShaderSize,
u32: bevy_render::render_resource::encase::private::ShaderSize {}ShaderType)]
52pub struct FrameTimeGraphConfigUniform {
53 dt_min: f32,
55 dt_max: f32,
57 dt_min_log2: f32,
58 dt_max_log2: f32,
59 proportional_width: u32,
61}
62
63impl FrameTimeGraphConfigUniform {
64 pub fn new(target_fps: f32, min_fps: f32, proportional_width: bool) -> Self {
66 let dt_min = 1. / (target_fps * 1.2);
68 let dt_max = 1. / min_fps;
69 Self {
70 dt_min,
71 dt_max,
72 dt_min_log2: log2(dt_min),
73 dt_max_log2: log2(dt_max),
74 proportional_width: u32::from(proportional_width),
75 }
76 }
77}
78
79#[derive(impl bevy_render::render_resource::AsBindGroup for FrametimeGraphMaterial {
type Data = ();
type Param =
(bevy_ecs::system::lifetimeless::SRes<bevy_render::render_asset::RenderAssets<bevy_render::texture::GpuImage>>,
bevy_ecs::system::lifetimeless::SRes<bevy_render::texture::FallbackImage>,
bevy_ecs::system::lifetimeless::SRes<bevy_render::render_asset::RenderAssets<bevy_render::storage::GpuShaderBuffer>>);
fn label() -> &'static str { "FrametimeGraphMaterial" }
fn unprepared_bind_group(&self,
layout: &bevy_render::render_resource::BindGroupLayout,
render_device: &bevy_render::renderer::RenderDevice,
(images, fallback_image, storage_buffers):
&mut bevy_ecs::system::SystemParamItem<'_, '_, Self::Param>,
force_no_bindless: bool)
->
::core::result::Result<bevy_render::render_resource::UnpreparedBindGroup,
bevy_render::render_resource::AsBindGroupError> {
let (uniform_binding_type, uniform_buffer_usages) =
(bevy_render::render_resource::BufferBindingType::Uniform,
bevy_render::render_resource::BufferUsages::UNIFORM);
let bindings =
bevy_render::render_resource::BindingResources(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(0u32,
bevy_render::render_resource::OwnedBindingResource::Buffer({
let handle:
&bevy_asset::Handle<bevy_render::storage::ShaderBuffer> =
(&self.values);
storage_buffers.get(handle).ok_or_else(||
bevy_render::render_resource::AsBindGroupError::RetryNextUpdate)?.buffer.clone()
})),
{
let mut buffer =
bevy_render::render_resource::encase::UniformBuffer::new(::std::vec::Vec::new());
buffer.write(&self.config).unwrap();
(1u32,
bevy_render::render_resource::OwnedBindingResource::Buffer(render_device.create_buffer_with_data(&bevy_render::render_resource::BufferInitDescriptor {
label: ::core::option::Option::None,
usage: uniform_buffer_usages,
contents: buffer.as_ref(),
})))
}])));
::core::result::Result::Ok(bevy_render::render_resource::UnpreparedBindGroup {
bindings,
})
}
#[allow(clippy :: unused_unit)]
fn bind_group_data(&self) -> Self::Data { () }
fn bind_group_layout_entries(render_device:
&bevy_render::renderer::RenderDevice, force_no_bindless: bool)
->
::std::vec::Vec<bevy_render::render_resource::BindGroupLayoutEntry> {
let actual_bindless_slot_count:
::core::option::Option<::core::num::NonZeroU32> =
::core::option::Option::None;
let (uniform_binding_type, uniform_buffer_usages) =
(bevy_render::render_resource::BufferBindingType::Uniform,
bevy_render::render_resource::BufferUsages::UNIFORM);
let mut bind_group_layout_entries = ::std::vec::Vec::new();
match actual_bindless_slot_count {
::core::option::Option::Some(bindless_slot_count) => {
let bindless_index_table_range =
bevy_render::render_resource::BindlessIndex(0)..bevy_render::render_resource::BindlessIndex(0u32);
let used_resource_types = &[];
bind_group_layout_entries.extend(bevy_render::render_resource::create_bindless_bind_group_layout_entries(bindless_index_table_range.end.0
- bindless_index_table_range.start.0,
bindless_slot_count.into(),
bevy_render::render_resource::BindingNumber(0),
used_resource_types).into_iter());
;
}
::core::option::Option::None => {
bind_group_layout_entries.push(bevy_render::render_resource::BindGroupLayoutEntry {
binding: 0u32,
visibility: bevy_render::render_resource::ShaderStages::VERTEX
| bevy_render::render_resource::ShaderStages::FRAGMENT,
ty: bevy_render::render_resource::BindingType::Buffer {
ty: bevy_render::render_resource::BufferBindingType::Storage {
read_only: true,
},
has_dynamic_offset: false,
min_binding_size: ::core::option::Option::None,
},
count: actual_bindless_slot_count,
});
bind_group_layout_entries.push(bevy_render::render_resource::BindGroupLayoutEntry {
binding: 1u32,
visibility: bevy_render::render_resource::ShaderStages::FRAGMENT
| bevy_render::render_resource::ShaderStages::VERTEX |
bevy_render::render_resource::ShaderStages::COMPUTE,
ty: bevy_render::render_resource::BindingType::Buffer {
ty: uniform_binding_type,
has_dynamic_offset: false,
min_binding_size: ::core::option::Option::Some(<FrameTimeGraphConfigUniform
as bevy_render::render_resource::ShaderType>::min_size()),
},
count: actual_bindless_slot_count,
});
;
}
};
bind_group_layout_entries
}
fn bindless_descriptor()
->
::core::option::Option<bevy_render::render_resource::BindlessDescriptor> {
::core::option::Option::None
}
}AsBindGroup, impl bevy_asset::VisitAssetDependencies for FrametimeGraphMaterial {
fn visit_dependencies(&self,
visit: &mut impl ::core::ops::FnMut(bevy_asset::UntypedAssetId)) {}
}Asset, const _: () =
{
#[allow(deprecated, reason =
"derives on a deprecated type shouldn't be considered a usage")]
impl bevy_reflect::TypePath for FrametimeGraphMaterial where {
fn type_path() -> &'static str {
"bevy_dev_tools::frame_time_graph::FrametimeGraphMaterial"
}
fn short_type_path() -> &'static str { "FrametimeGraphMaterial" }
fn type_ident() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("FrametimeGraphMaterial")
}
fn crate_name() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("bevy_dev_tools::frame_time_graph".split(':').next().unwrap())
}
fn module_path() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("bevy_dev_tools::frame_time_graph")
}
}
};TypePath, #[automatically_derived]
impl ::core::fmt::Debug for FrametimeGraphMaterial {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"FrametimeGraphMaterial", "values", &self.values, "config",
&&self.config)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for FrametimeGraphMaterial {
#[inline]
fn clone(&self) -> FrametimeGraphMaterial {
FrametimeGraphMaterial {
values: ::core::clone::Clone::clone(&self.values),
config: ::core::clone::Clone::clone(&self.config),
}
}
}Clone)]
81pub struct FrametimeGraphMaterial {
82 #[storage(0, read_only)]
86 pub values: Handle<ShaderBuffer>, #[uniform(1)]
89 pub config: FrameTimeGraphConfigUniform,
90}
91
92impl UiMaterial for FrametimeGraphMaterial {
93 fn fragment_shader() -> ShaderRef {
94 FRAME_TIME_GRAPH_SHADER_HANDLE.into()
95 }
96}
97
98fn update_frame_time_values(
100 mut frame_time_graph_materials: ResMut<Assets<FrametimeGraphMaterial>>,
101 mut buffers: ResMut<Assets<ShaderBuffer>>,
102 diagnostics_store: Res<DiagnosticsStore>,
103 config: Option<Res<FpsOverlayConfig>>,
104) {
105 if !config.is_none_or(|c| c.frame_time_graph_config.enabled) {
106 return;
107 }
108 let Some(frame_time) = diagnostics_store.get(&FrameTimeDiagnosticsPlugin::FRAME_TIME) else {
109 return;
110 };
111 let frame_times = frame_time
112 .values()
113 .map(|x| *x as f32 / 1000.0)
115 .collect::<Vec<_>>();
116 for (_, material) in frame_time_graph_materials.iter_mut() {
117 let mut buffer = buffers.get_mut(&material.values).unwrap();
118
119 buffer.set_data(frame_times.clone());
120 }
121}