Much more accurate `cpu_usage` timing (#3913)
`frame.info.cpu_usage` now includes time for tessellation and rendering, but excludes vsync and context switching.
This commit is contained in:
parent
6a94f4f5f0
commit
ab39420c29
|
|
@ -1206,6 +1206,7 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
"web-time",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winapi",
|
"winapi",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ parking_lot = "0.12"
|
||||||
raw-window-handle.workspace = true
|
raw-window-handle.workspace = true
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
|
web-time.workspace = true
|
||||||
|
|
||||||
#! ### Optional dependencies
|
#! ### Optional dependencies
|
||||||
## Enable this when generating docs.
|
## Enable this when generating docs.
|
||||||
|
|
|
||||||
|
|
@ -757,7 +757,13 @@ pub struct IntegrationInfo {
|
||||||
/// `None` means "don't know".
|
/// `None` means "don't know".
|
||||||
pub system_theme: Option<Theme>,
|
pub system_theme: Option<Theme>,
|
||||||
|
|
||||||
/// Seconds of cpu usage (in seconds) of UI code on the previous frame.
|
/// Seconds of cpu usage (in seconds) on the previous frame.
|
||||||
|
///
|
||||||
|
/// This includes [`App::update`] as well as rendering (except for vsync waiting).
|
||||||
|
///
|
||||||
|
/// For a more detailed view of cpu usage, use the [`puffin`](https://crates.io/crates/puffin)
|
||||||
|
/// profiler together with the `puffin` feature of `eframe`.
|
||||||
|
///
|
||||||
/// `None` if this is the first frame.
|
/// `None` if this is the first frame.
|
||||||
pub cpu_usage: Option<f32>,
|
pub cpu_usage: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,8 @@ mod epi;
|
||||||
// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
|
// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
|
||||||
pub use epi::*;
|
pub use epi::*;
|
||||||
|
|
||||||
|
pub(crate) mod stopwatch;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// When compiling for web
|
// When compiling for web
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//! Common tools used by [`super::glow_integration`] and [`super::wgpu_integration`].
|
//! Common tools used by [`super::glow_integration`] and [`super::wgpu_integration`].
|
||||||
|
|
||||||
use std::time::Instant;
|
use web_time::Instant;
|
||||||
|
|
||||||
use winit::event_loop::EventLoopWindowTarget;
|
use winit::event_loop::EventLoopWindowTarget;
|
||||||
|
|
||||||
use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
|
use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
|
||||||
|
|
@ -259,7 +258,6 @@ impl EpiIntegration {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pre_update(&mut self) {
|
pub fn pre_update(&mut self) {
|
||||||
self.frame_start = Instant::now();
|
|
||||||
self.app_icon_setter.update();
|
self.app_icon_setter.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,9 +302,8 @@ impl EpiIntegration {
|
||||||
std::mem::take(&mut self.pending_full_output)
|
std::mem::take(&mut self.pending_full_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_update(&mut self) {
|
pub fn report_frame_time(&mut self, seconds: f32) {
|
||||||
let frame_time = self.frame_start.elapsed().as_secs_f64() as f32;
|
self.frame.info.cpu_usage = Some(seconds);
|
||||||
self.frame.info.cpu_usage = Some(frame_time);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn post_rendering(&mut self, window: &winit::window::Window) {
|
pub fn post_rendering(&mut self, window: &winit::window::Window) {
|
||||||
|
|
|
||||||
|
|
@ -493,6 +493,9 @@ impl GlowWinitRunning {
|
||||||
#[cfg(feature = "puffin")]
|
#[cfg(feature = "puffin")]
|
||||||
puffin::GlobalProfiler::lock().new_frame();
|
puffin::GlobalProfiler::lock().new_frame();
|
||||||
|
|
||||||
|
let mut frame_timer = crate::stopwatch::Stopwatch::new();
|
||||||
|
frame_timer.start();
|
||||||
|
|
||||||
{
|
{
|
||||||
let glutin = self.glutin.borrow();
|
let glutin = self.glutin.borrow();
|
||||||
let viewport = &glutin.viewports[&viewport_id];
|
let viewport = &glutin.viewports[&viewport_id];
|
||||||
|
|
@ -556,7 +559,11 @@ impl GlowWinitRunning {
|
||||||
|
|
||||||
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
|
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
|
||||||
|
|
||||||
|
{
|
||||||
|
frame_timer.pause();
|
||||||
change_gl_context(current_gl_context, gl_surface);
|
change_gl_context(current_gl_context, gl_surface);
|
||||||
|
frame_timer.resume();
|
||||||
|
}
|
||||||
|
|
||||||
self.painter
|
self.painter
|
||||||
.borrow()
|
.borrow()
|
||||||
|
|
@ -600,17 +607,20 @@ impl GlowWinitRunning {
|
||||||
|
|
||||||
let viewport = viewports.get_mut(&viewport_id).unwrap();
|
let viewport = viewports.get_mut(&viewport_id).unwrap();
|
||||||
viewport.info.events.clear(); // they should have been processed
|
viewport.info.events.clear(); // they should have been processed
|
||||||
let window = viewport.window.as_ref().unwrap();
|
let window = viewport.window.clone().unwrap();
|
||||||
let gl_surface = viewport.gl_surface.as_ref().unwrap();
|
let gl_surface = viewport.gl_surface.as_ref().unwrap();
|
||||||
let egui_winit = viewport.egui_winit.as_mut().unwrap();
|
let egui_winit = viewport.egui_winit.as_mut().unwrap();
|
||||||
|
|
||||||
integration.post_update();
|
egui_winit.handle_platform_output(&window, platform_output);
|
||||||
egui_winit.handle_platform_output(window, platform_output);
|
|
||||||
|
|
||||||
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
|
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
|
||||||
|
|
||||||
|
{
|
||||||
// We may need to switch contexts again, because of immediate viewports:
|
// We may need to switch contexts again, because of immediate viewports:
|
||||||
|
frame_timer.pause();
|
||||||
change_gl_context(current_gl_context, gl_surface);
|
change_gl_context(current_gl_context, gl_surface);
|
||||||
|
frame_timer.resume();
|
||||||
|
}
|
||||||
|
|
||||||
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
|
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
|
||||||
|
|
||||||
|
|
@ -637,10 +647,12 @@ impl GlowWinitRunning {
|
||||||
image: screenshot.into(),
|
image: screenshot.into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
integration.post_rendering(window);
|
integration.post_rendering(&window);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// vsync - don't count as frame-time:
|
||||||
|
frame_timer.pause();
|
||||||
crate::profile_scope!("swap_buffers");
|
crate::profile_scope!("swap_buffers");
|
||||||
if let Err(err) = gl_surface.swap_buffers(
|
if let Err(err) = gl_surface.swap_buffers(
|
||||||
current_gl_context
|
current_gl_context
|
||||||
|
|
@ -649,6 +661,7 @@ impl GlowWinitRunning {
|
||||||
) {
|
) {
|
||||||
log::error!("swap_buffers failed: {err}");
|
log::error!("swap_buffers failed: {err}");
|
||||||
}
|
}
|
||||||
|
frame_timer.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
// give it time to settle:
|
// give it time to settle:
|
||||||
|
|
@ -659,7 +672,11 @@ impl GlowWinitRunning {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
integration.maybe_autosave(app.as_mut(), Some(window));
|
glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output);
|
||||||
|
|
||||||
|
integration.report_frame_time(frame_timer.total_time_sec()); // don't count auto-save time as part of regular frame time
|
||||||
|
|
||||||
|
integration.maybe_autosave(app.as_mut(), Some(&window));
|
||||||
|
|
||||||
if window.is_minimized() == Some(true) {
|
if window.is_minimized() == Some(true) {
|
||||||
// On Mac, a minimized Window uses up all CPU:
|
// On Mac, a minimized Window uses up all CPU:
|
||||||
|
|
@ -668,8 +685,6 @@ impl GlowWinitRunning {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output);
|
|
||||||
|
|
||||||
if integration.should_close() {
|
if integration.should_close() {
|
||||||
EventResult::Exit
|
EventResult::Exit
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -528,6 +528,9 @@ impl WgpuWinitRunning {
|
||||||
shared,
|
shared,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
let mut frame_timer = crate::stopwatch::Stopwatch::new();
|
||||||
|
frame_timer.start();
|
||||||
|
|
||||||
let (viewport_ui_cb, raw_input) = {
|
let (viewport_ui_cb, raw_input) = {
|
||||||
crate::profile_scope!("Prepare");
|
crate::profile_scope!("Prepare");
|
||||||
let mut shared_lock = shared.borrow_mut();
|
let mut shared_lock = shared.borrow_mut();
|
||||||
|
|
@ -628,8 +631,6 @@ impl WgpuWinitRunning {
|
||||||
return EventResult::Wait;
|
return EventResult::Wait;
|
||||||
};
|
};
|
||||||
|
|
||||||
integration.post_update();
|
|
||||||
|
|
||||||
let FullOutput {
|
let FullOutput {
|
||||||
platform_output,
|
platform_output,
|
||||||
textures_delta,
|
textures_delta,
|
||||||
|
|
@ -640,11 +641,10 @@ impl WgpuWinitRunning {
|
||||||
|
|
||||||
egui_winit.handle_platform_output(window, platform_output);
|
egui_winit.handle_platform_output(window, platform_output);
|
||||||
|
|
||||||
{
|
|
||||||
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
|
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
|
||||||
|
|
||||||
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
|
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
|
||||||
let (_vsync_secs, screenshot) = painter.paint_and_update_textures(
|
let (vsync_secs, screenshot) = painter.paint_and_update_textures(
|
||||||
viewport_id,
|
viewport_id,
|
||||||
pixels_per_point,
|
pixels_per_point,
|
||||||
app.clear_color(&egui_ctx.style().visuals),
|
app.clear_color(&egui_ctx.style().visuals),
|
||||||
|
|
@ -661,7 +661,6 @@ impl WgpuWinitRunning {
|
||||||
image: screenshot.into(),
|
image: screenshot.into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
integration.post_rendering(window);
|
integration.post_rendering(window);
|
||||||
|
|
||||||
|
|
@ -684,6 +683,8 @@ impl WgpuWinitRunning {
|
||||||
.and_then(|id| viewports.get(id))
|
.and_then(|id| viewports.get(id))
|
||||||
.and_then(|vp| vp.window.as_ref());
|
.and_then(|vp| vp.window.as_ref());
|
||||||
|
|
||||||
|
integration.report_frame_time(frame_timer.total_time_sec() - vsync_secs); // don't count auto-save time as part of regular frame time
|
||||||
|
|
||||||
integration.maybe_autosave(app.as_mut(), window.map(|w| w.as_ref()));
|
integration.maybe_autosave(app.as_mut(), window.map(|w| w.as_ref()));
|
||||||
|
|
||||||
if let Some(window) = window {
|
if let Some(window) = window {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
#![allow(dead_code)] // not everything is used on wasm
|
||||||
|
|
||||||
|
use web_time::Instant;
|
||||||
|
|
||||||
|
pub struct Stopwatch {
|
||||||
|
total_time_ns: u128,
|
||||||
|
|
||||||
|
/// None = not running
|
||||||
|
start: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stopwatch {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
total_time_ns: 0,
|
||||||
|
start: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
assert!(self.start.is_none());
|
||||||
|
self.start = Some(Instant::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
let start = self.start.take().unwrap();
|
||||||
|
let duration = start.elapsed();
|
||||||
|
self.total_time_ns += duration.as_nanos();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume(&mut self) {
|
||||||
|
assert!(self.start.is_none());
|
||||||
|
self.start = Some(Instant::now());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_time_ns(&self) -> u128 {
|
||||||
|
if let Some(start) = self.start {
|
||||||
|
// Running
|
||||||
|
let duration = start.elapsed();
|
||||||
|
self.total_time_ns + duration.as_nanos()
|
||||||
|
} else {
|
||||||
|
// Paused
|
||||||
|
self.total_time_ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_time_sec(&self) -> f32 {
|
||||||
|
self.total_time_ns() as f32 * 1e-9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -179,8 +179,6 @@ impl AppRunner {
|
||||||
///
|
///
|
||||||
/// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`].
|
/// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`].
|
||||||
pub fn logic(&mut self) {
|
pub fn logic(&mut self) {
|
||||||
let frame_start = now_sec();
|
|
||||||
|
|
||||||
super::resize_canvas_to_screen_size(self.canvas_id(), self.web_options.max_size_points);
|
super::resize_canvas_to_screen_size(self.canvas_id(), self.web_options.max_size_points);
|
||||||
let canvas_size = super::canvas_size_in_points(self.canvas_id());
|
let canvas_size = super::canvas_size_in_points(self.canvas_id());
|
||||||
let raw_input = self.input.new_frame(canvas_size);
|
let raw_input = self.input.new_frame(canvas_size);
|
||||||
|
|
@ -211,8 +209,6 @@ impl AppRunner {
|
||||||
self.handle_platform_output(platform_output);
|
self.handle_platform_output(platform_output);
|
||||||
self.textures_delta.append(textures_delta);
|
self.textures_delta.append(textures_delta);
|
||||||
self.clipped_primitives = Some(self.egui_ctx.tessellate(shapes, pixels_per_point));
|
self.clipped_primitives = Some(self.egui_ctx.tessellate(shapes, pixels_per_point));
|
||||||
|
|
||||||
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paint the results of the last call to [`Self::logic`].
|
/// Paint the results of the last call to [`Self::logic`].
|
||||||
|
|
@ -232,6 +228,10 @@ impl AppRunner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn report_frame_time(&mut self, cpu_usage_seconds: f32) {
|
||||||
|
self.frame.info.cpu_usage = Some(cpu_usage_seconds);
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
|
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
|
||||||
#[cfg(feature = "web_screen_reader")]
|
#[cfg(feature = "web_screen_reader")]
|
||||||
if self.egui_ctx.options(|o| o.screen_reader) {
|
if self.egui_ctx.options(|o| o.screen_reader) {
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,16 @@ fn paint_if_needed(runner: &mut AppRunner) {
|
||||||
// running the logic, as the logic could cause it to be set again.
|
// running the logic, as the logic could cause it to be set again.
|
||||||
runner.needs_repaint.clear();
|
runner.needs_repaint.clear();
|
||||||
|
|
||||||
|
let mut stopwatch = crate::stopwatch::Stopwatch::new();
|
||||||
|
stopwatch.start();
|
||||||
|
|
||||||
// Run user code…
|
// Run user code…
|
||||||
runner.logic();
|
runner.logic();
|
||||||
|
|
||||||
// …and paint the result.
|
// …and paint the result.
|
||||||
runner.paint();
|
runner.paint();
|
||||||
|
|
||||||
|
runner.report_frame_time(stopwatch.total_time_sec());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
runner.auto_save_if_needed();
|
runner.auto_save_if_needed();
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ impl FrameHistory {
|
||||||
1e3 * self.mean_frame_time()
|
1e3 * self.mean_frame_time()
|
||||||
))
|
))
|
||||||
.on_hover_text(
|
.on_hover_text(
|
||||||
"Includes egui layout and tessellation time.\n\
|
"Includes all app logic, egui layout, tessellation, and rendering.\n\
|
||||||
Does not include GPU usage, nor overhead for sending data to GPU.",
|
Does not include waiting for vsync.",
|
||||||
);
|
);
|
||||||
egui::warn_if_debug_build(ui);
|
egui::warn_if_debug_build(ui);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,12 +93,6 @@ impl eframe::App for MyApp {
|
||||||
.store(show_deferred_viewport, Ordering::Relaxed);
|
.store(show_deferred_viewport, Ordering::Relaxed);
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
|
||||||
// Sleep a bit to emulate some work:
|
|
||||||
puffin::profile_scope!("small_sleep");
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.show_immediate_viewport {
|
if self.show_immediate_viewport {
|
||||||
ctx.show_viewport_immediate(
|
ctx.show_viewport_immediate(
|
||||||
egui::ViewportId::from_hash_of("immediate_viewport"),
|
egui::ViewportId::from_hash_of("immediate_viewport"),
|
||||||
|
|
@ -121,12 +115,6 @@ impl eframe::App for MyApp {
|
||||||
// Tell parent viewport that we should not show next frame:
|
// Tell parent viewport that we should not show next frame:
|
||||||
self.show_immediate_viewport = false;
|
self.show_immediate_viewport = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
// Sleep a bit to emulate some work:
|
|
||||||
puffin::profile_scope!("small_sleep");
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -153,12 +141,6 @@ impl eframe::App for MyApp {
|
||||||
// Tell parent to close us.
|
// Tell parent to close us.
|
||||||
show_deferred_viewport.store(false, Ordering::Relaxed);
|
show_deferred_viewport.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
// Sleep a bit to emulate some work:
|
|
||||||
puffin::profile_scope!("small_sleep");
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue