Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit da2ffb7

Browse files
committedJan 29, 2024
Much more accurate cpu_usage
1 parent 6a94f4f commit da2ffb7

File tree

12 files changed

+124
-64
lines changed

12 files changed

+124
-64
lines changed
 

‎Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/eframe/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ parking_lot = "0.12"
130130
raw-window-handle.workspace = true
131131
static_assertions = "1.1.0"
132132
thiserror.workspace = true
133+
web-time.workspace = true
133134

134135
#! ### Optional dependencies
135136
## Enable this when generating docs.

‎crates/eframe/src/epi.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,13 @@ pub struct IntegrationInfo {
757757
/// `None` means "don't know".
758758
pub system_theme: Option<Theme>,
759759

760-
/// Seconds of cpu usage (in seconds) of UI code on the previous frame.
760+
/// Seconds of cpu usage (in seconds) on the previous frame.
761+
///
762+
/// This includes [`App::update`] as well as rendering (except for vsync waiting).
763+
///
764+
/// For a more detailed view of cpu usage, use the [`puffin`](https://crates.io/crates/puffin)
765+
/// profiler together with the `puffin` feature of `eframe`.
766+
///
761767
/// `None` if this is the first frame.
762768
pub cpu_usage: Option<f32>,
763769
}

‎crates/eframe/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ mod epi;
150150
// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
151151
pub use epi::*;
152152

153+
pub(crate) mod stopwatch;
154+
153155
// ----------------------------------------------------------------------------
154156
// When compiling for web
155157

‎crates/eframe/src/native/epi_integration.rs

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
//! Common tools used by [`super::glow_integration`] and [`super::wgpu_integration`].
22
3-
use std::time::Instant;
4-
3+
use web_time::Instant;
54
use winit::event_loop::EventLoopWindowTarget;
65

76
use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _};
@@ -259,7 +258,6 @@ impl EpiIntegration {
259258
}
260259

261260
pub fn pre_update(&mut self) {
262-
self.frame_start = Instant::now();
263261
self.app_icon_setter.update();
264262
}
265263

@@ -304,9 +302,8 @@ impl EpiIntegration {
304302
std::mem::take(&mut self.pending_full_output)
305303
}
306304

307-
pub fn post_update(&mut self) {
308-
let frame_time = self.frame_start.elapsed().as_secs_f64() as f32;
309-
self.frame.info.cpu_usage = Some(frame_time);
305+
pub fn report_frame_time(&mut self, seconds: f32) {
306+
self.frame.info.cpu_usage = Some(seconds);
310307
}
311308

312309
pub fn post_rendering(&mut self, window: &winit::window::Window) {

‎crates/eframe/src/native/glow_integration.rs

+25-10
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,9 @@ impl GlowWinitRunning {
493493
#[cfg(feature = "puffin")]
494494
puffin::GlobalProfiler::lock().new_frame();
495495

496+
let mut frame_timer = crate::stopwatch::Stopwatch::new();
497+
frame_timer.start();
498+
496499
{
497500
let glutin = self.glutin.borrow();
498501
let viewport = &glutin.viewports[&viewport_id];
@@ -556,7 +559,11 @@ impl GlowWinitRunning {
556559

557560
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
558561

559-
change_gl_context(current_gl_context, gl_surface);
562+
{
563+
frame_timer.pause();
564+
change_gl_context(current_gl_context, gl_surface);
565+
frame_timer.resume();
566+
}
560567

561568
self.painter
562569
.borrow()
@@ -600,17 +607,20 @@ impl GlowWinitRunning {
600607

601608
let viewport = viewports.get_mut(&viewport_id).unwrap();
602609
viewport.info.events.clear(); // they should have been processed
603-
let window = viewport.window.as_ref().unwrap();
610+
let window = viewport.window.clone().unwrap();
604611
let gl_surface = viewport.gl_surface.as_ref().unwrap();
605612
let egui_winit = viewport.egui_winit.as_mut().unwrap();
606613

607-
integration.post_update();
608-
egui_winit.handle_platform_output(window, platform_output);
614+
egui_winit.handle_platform_output(&window, platform_output);
609615

610616
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
611617

612-
// We may need to switch contexts again, because of immediate viewports:
613-
change_gl_context(current_gl_context, gl_surface);
618+
{
619+
// We may need to switch contexts again, because of immediate viewports:
620+
frame_timer.pause();
621+
change_gl_context(current_gl_context, gl_surface);
622+
frame_timer.resume();
623+
}
614624

615625
let screen_size_in_pixels: [u32; 2] = window.inner_size().into();
616626

@@ -637,10 +647,12 @@ impl GlowWinitRunning {
637647
image: screenshot.into(),
638648
});
639649
}
640-
integration.post_rendering(window);
650+
integration.post_rendering(&window);
641651
}
642652

643653
{
654+
// vsync - don't count as frame-time:
655+
frame_timer.pause();
644656
crate::profile_scope!("swap_buffers");
645657
if let Err(err) = gl_surface.swap_buffers(
646658
current_gl_context
@@ -649,6 +661,7 @@ impl GlowWinitRunning {
649661
) {
650662
log::error!("swap_buffers failed: {err}");
651663
}
664+
frame_timer.resume();
652665
}
653666

654667
// give it time to settle:
@@ -659,7 +672,11 @@ impl GlowWinitRunning {
659672
}
660673
}
661674

662-
integration.maybe_autosave(app.as_mut(), Some(window));
675+
glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output);
676+
677+
integration.report_frame_time(frame_timer.total_time_sec()); // don't count auto-save time as part of regular frame time
678+
679+
integration.maybe_autosave(app.as_mut(), Some(&window));
663680

664681
if window.is_minimized() == Some(true) {
665682
// On Mac, a minimized Window uses up all CPU:
@@ -668,8 +685,6 @@ impl GlowWinitRunning {
668685
std::thread::sleep(std::time::Duration::from_millis(10));
669686
}
670687

671-
glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output);
672-
673688
if integration.should_close() {
674689
EventResult::Exit
675690
} else {

‎crates/eframe/src/native/wgpu_integration.rs

+24-23
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,9 @@ impl WgpuWinitRunning {
528528
shared,
529529
} = self;
530530

531+
let mut frame_timer = crate::stopwatch::Stopwatch::new();
532+
frame_timer.start();
533+
531534
let (viewport_ui_cb, raw_input) = {
532535
crate::profile_scope!("Prepare");
533536
let mut shared_lock = shared.borrow_mut();
@@ -628,8 +631,6 @@ impl WgpuWinitRunning {
628631
return EventResult::Wait;
629632
};
630633

631-
integration.post_update();
632-
633634
let FullOutput {
634635
platform_output,
635636
textures_delta,
@@ -640,27 +641,25 @@ impl WgpuWinitRunning {
640641

641642
egui_winit.handle_platform_output(window, platform_output);
642643

643-
{
644-
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
645-
646-
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
647-
let (_vsync_secs, screenshot) = painter.paint_and_update_textures(
648-
viewport_id,
649-
pixels_per_point,
650-
app.clear_color(&egui_ctx.style().visuals),
651-
&clipped_primitives,
652-
&textures_delta,
653-
screenshot_requested,
654-
);
655-
if let Some(screenshot) = screenshot {
656-
egui_winit
657-
.egui_input_mut()
658-
.events
659-
.push(egui::Event::Screenshot {
660-
viewport_id,
661-
image: screenshot.into(),
662-
});
663-
}
644+
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
645+
646+
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
647+
let (vsync_secs, screenshot) = painter.paint_and_update_textures(
648+
viewport_id,
649+
pixels_per_point,
650+
app.clear_color(&egui_ctx.style().visuals),
651+
&clipped_primitives,
652+
&textures_delta,
653+
screenshot_requested,
654+
);
655+
if let Some(screenshot) = screenshot {
656+
egui_winit
657+
.egui_input_mut()
658+
.events
659+
.push(egui::Event::Screenshot {
660+
viewport_id,
661+
image: screenshot.into(),
662+
});
664663
}
665664

666665
integration.post_rendering(window);
@@ -684,6 +683,8 @@ impl WgpuWinitRunning {
684683
.and_then(|id| viewports.get(id))
685684
.and_then(|vp| vp.window.as_ref());
686685

686+
integration.report_frame_time(frame_timer.total_time_sec() - vsync_secs); // don't count auto-save time as part of regular frame time
687+
687688
integration.maybe_autosave(app.as_mut(), window.map(|w| w.as_ref()));
688689

689690
if let Some(window) = window {

‎crates/eframe/src/stopwatch.rs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#![allow(dead_code)] // not everything is used on wasm
2+
3+
use web_time::Instant;
4+
5+
pub struct Stopwatch {
6+
total_time_ns: u128,
7+
8+
/// None = not running
9+
start: Option<Instant>,
10+
}
11+
12+
impl Stopwatch {
13+
pub fn new() -> Self {
14+
Self {
15+
total_time_ns: 0,
16+
start: None,
17+
}
18+
}
19+
20+
pub fn start(&mut self) {
21+
assert!(self.start.is_none());
22+
self.start = Some(Instant::now());
23+
}
24+
25+
pub fn pause(&mut self) {
26+
let start = self.start.take().unwrap();
27+
let duration = start.elapsed();
28+
self.total_time_ns += duration.as_nanos();
29+
}
30+
31+
pub fn resume(&mut self) {
32+
assert!(self.start.is_none());
33+
self.start = Some(Instant::now());
34+
}
35+
36+
pub fn total_time_ns(&self) -> u128 {
37+
if let Some(start) = self.start {
38+
// Running
39+
let duration = start.elapsed();
40+
self.total_time_ns + duration.as_nanos()
41+
} else {
42+
// Paused
43+
self.total_time_ns
44+
}
45+
}
46+
47+
pub fn total_time_sec(&self) -> f32 {
48+
self.total_time_ns() as f32 * 1e-9
49+
}
50+
}

‎crates/eframe/src/web/app_runner.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,6 @@ impl AppRunner {
179179
///
180180
/// The result can be painted later with a call to [`Self::run_and_paint`] or [`Self::paint`].
181181
pub fn logic(&mut self) {
182-
let frame_start = now_sec();
183-
184182
super::resize_canvas_to_screen_size(self.canvas_id(), self.web_options.max_size_points);
185183
let canvas_size = super::canvas_size_in_points(self.canvas_id());
186184
let raw_input = self.input.new_frame(canvas_size);
@@ -211,8 +209,6 @@ impl AppRunner {
211209
self.handle_platform_output(platform_output);
212210
self.textures_delta.append(textures_delta);
213211
self.clipped_primitives = Some(self.egui_ctx.tessellate(shapes, pixels_per_point));
214-
215-
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
216212
}
217213

218214
/// Paint the results of the last call to [`Self::logic`].
@@ -232,6 +228,10 @@ impl AppRunner {
232228
}
233229
}
234230

231+
pub fn report_frame_time(&mut self, cpu_usage_seconds: f32) {
232+
self.frame.info.cpu_usage = Some(cpu_usage_seconds);
233+
}
234+
235235
fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) {
236236
#[cfg(feature = "web_screen_reader")]
237237
if self.egui_ctx.options(|o| o.screen_reader) {

‎crates/eframe/src/web/events.rs

+5
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,16 @@ fn paint_if_needed(runner: &mut AppRunner) {
3030
// running the logic, as the logic could cause it to be set again.
3131
runner.needs_repaint.clear();
3232

33+
let mut stopwatch = crate::stopwatch::Stopwatch::new();
34+
stopwatch.start();
35+
3336
// Run user code…
3437
runner.logic();
3538

3639
// …and paint the result.
3740
runner.paint();
41+
42+
runner.report_frame_time(stopwatch.total_time_sec());
3843
}
3944
}
4045
runner.auto_save_if_needed();

‎crates/egui_demo_app/src/frame_history.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ impl FrameHistory {
3838
1e3 * self.mean_frame_time()
3939
))
4040
.on_hover_text(
41-
"Includes egui layout and tessellation time.\n\
42-
Does not include GPU usage, nor overhead for sending data to GPU.",
41+
"Includes all app logic, egui layout, tessellation, and rendering.\n\
42+
Does not include waiting for vsync.",
4343
);
4444
egui::warn_if_debug_build(ui);
4545

‎examples/puffin_profiler/src/main.rs

-18
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,6 @@ impl eframe::App for MyApp {
9393
.store(show_deferred_viewport, Ordering::Relaxed);
9494
});
9595

96-
{
97-
// Sleep a bit to emulate some work:
98-
puffin::profile_scope!("small_sleep");
99-
std::thread::sleep(std::time::Duration::from_millis(10));
100-
}
101-
10296
if self.show_immediate_viewport {
10397
ctx.show_viewport_immediate(
10498
egui::ViewportId::from_hash_of("immediate_viewport"),
@@ -121,12 +115,6 @@ impl eframe::App for MyApp {
121115
// Tell parent viewport that we should not show next frame:
122116
self.show_immediate_viewport = false;
123117
}
124-
125-
{
126-
// Sleep a bit to emulate some work:
127-
puffin::profile_scope!("small_sleep");
128-
std::thread::sleep(std::time::Duration::from_millis(10));
129-
}
130118
},
131119
);
132120
}
@@ -153,12 +141,6 @@ impl eframe::App for MyApp {
153141
// Tell parent to close us.
154142
show_deferred_viewport.store(false, Ordering::Relaxed);
155143
}
156-
157-
{
158-
// Sleep a bit to emulate some work:
159-
puffin::profile_scope!("small_sleep");
160-
std::thread::sleep(std::time::Duration::from_millis(10));
161-
}
162144
},
163145
);
164146
}

0 commit comments

Comments
 (0)
Please sign in to comment.