Files
egui/crates/eframe/src/native/run.rs
T
Ted de Munnik 3af907919b Use profiling crate to support more profiler backends (#5150)
Hey! I am not sure if this is something that's been considered before
and decided against (I couldn't find any PR's or issues).

This change removes the internal profiling macros in library crates and
the `puffin` feature and replaces it with similar functions in the
[profiling](https://github.com/aclysma/profiling) crate. This crate
provides a layer of abstraction over various profiler instrumentation
crates and allows library users to pick their favorite (supported)
profiler.

An additional benefit for puffin users is that dependencies of egui are
included in the instrumentation output too (mainly wgpu which uses the
profiling crate), so more details might be available when profiling.

A breaking change is that instead of using the `puffin` feature on egui,
users that want to profile the crate with puffin instead have to enable
the `profile-with-puffin` feature on the profiling crate. Similarly they
could instead choose to use `profile-with-tracy` etc.

I tried to add a 'tracy' feature to egui_demo_app in order to showcase ,
however the /scripts/check.sh currently breaks on mutually exclusive
features (which this introduces), so I decided against including it for
the initial PR. I'm happy to iterate more on this if there is interest
in taking this PR though.

Screenshot showing the additional info for wgpu now available when using
puffin

![image](https://github.com/user-attachments/assets/49fc0e7e-8f88-40cb-a69e-74ca2e3f90f3)
2024-12-16 09:15:54 +01:00

376 lines
14 KiB
Rust

use std::time::Instant;
use winit::{
application::ApplicationHandler,
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::WindowId,
};
use ahash::HashMap;
use super::winit_integration::{UserEvent, WinitApp};
use crate::{
epi,
native::{event_loop_context, winit_integration::EventResult},
Result,
};
// ----------------------------------------------------------------------------
fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
#[cfg(target_os = "android")]
use winit::platform::android::EventLoopBuilderExtAndroid as _;
profiling::function_scope!();
let mut builder = winit::event_loop::EventLoop::with_user_event();
#[cfg(target_os = "android")]
let mut builder =
builder.with_android_app(native_options.android_app.take().ok_or_else(|| {
crate::Error::AppCreation(Box::from(
"`NativeOptions` is missing required `android_app`",
))
})?);
if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
hook(&mut builder);
}
profiling::scope!("EventLoopBuilder::build");
Ok(builder.build()?)
}
/// Access a thread-local event loop.
///
/// We reuse the event-loop so we can support closing and opening an eframe window
/// multiple times. This is just a limitation of winit.
#[cfg(not(target_os = "ios"))]
fn with_event_loop<R>(
mut native_options: epi::NativeOptions,
f: impl FnOnce(&mut EventLoop<UserEvent>, epi::NativeOptions) -> R,
) -> Result<R> {
thread_local!(static EVENT_LOOP: std::cell::RefCell<Option<EventLoop<UserEvent>>> = const { std::cell::RefCell::new(None) });
EVENT_LOOP.with(|event_loop| {
// Since we want to reference NativeOptions when creating the EventLoop we can't
// do that as part of the lazy thread local storage initialization and so we instead
// create the event loop lazily here
let mut event_loop_lock = event_loop.borrow_mut();
let event_loop = if let Some(event_loop) = &mut *event_loop_lock {
event_loop
} else {
event_loop_lock.insert(create_event_loop(&mut native_options)?)
};
Ok(f(event_loop, native_options))
})
}
/// Wraps a [`WinitApp`] to implement [`ApplicationHandler`]. This handles redrawing, exit states, and
/// some events, but otherwise forwards events to the [`WinitApp`].
struct WinitAppWrapper<T: WinitApp> {
windows_next_repaint_times: HashMap<WindowId, Instant>,
winit_app: T,
return_result: Result<(), crate::Error>,
run_and_return: bool,
}
impl<T: WinitApp> WinitAppWrapper<T> {
fn new(winit_app: T, run_and_return: bool) -> Self {
Self {
windows_next_repaint_times: HashMap::default(),
winit_app,
return_result: Ok(()),
run_and_return,
}
}
fn handle_event_result(
&mut self,
event_loop: &ActiveEventLoop,
event_result: Result<EventResult>,
) {
let mut exit = false;
log::trace!("event_result: {event_result:?}");
let combined_result = event_result.and_then(|event_result| {
match event_result {
EventResult::Wait => {
event_loop.set_control_flow(ControlFlow::Wait);
Ok(event_result)
}
EventResult::RepaintNow(window_id) => {
log::trace!("RepaintNow of {window_id:?}",);
if cfg!(target_os = "windows") {
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
self.winit_app.run_ui_and_paint(event_loop, window_id)
} else {
// Fix for https://github.com/emilk/egui/issues/2425
self.windows_next_repaint_times
.insert(window_id, Instant::now());
Ok(event_result)
}
}
EventResult::RepaintNext(window_id) => {
log::trace!("RepaintNext of {window_id:?}",);
self.windows_next_repaint_times
.insert(window_id, Instant::now());
Ok(event_result)
}
EventResult::RepaintAt(window_id, repaint_time) => {
self.windows_next_repaint_times.insert(
window_id,
self.windows_next_repaint_times
.get(&window_id)
.map_or(repaint_time, |last| (*last).min(repaint_time)),
);
Ok(event_result)
}
EventResult::Exit => {
exit = true;
Ok(event_result)
}
}
});
if let Err(err) = combined_result {
log::error!("Exiting because of error: {err}");
exit = true;
self.return_result = Err(err);
};
if exit {
if self.run_and_return {
log::debug!("Asking to exit event loop…");
event_loop.exit();
} else {
log::debug!("Quitting - saving app state…");
self.winit_app.save_and_destroy();
log::debug!("Exiting with return code 0");
#[allow(clippy::exit)]
std::process::exit(0);
}
}
self.check_redraw_requests(event_loop);
}
fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) {
let now = Instant::now();
self.windows_next_repaint_times
.retain(|window_id, repaint_time| {
if now < *repaint_time {
return true; // not yet ready
};
event_loop.set_control_flow(ControlFlow::Poll);
if let Some(window) = self.winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
window.request_redraw();
} else {
log::trace!("No window found for {window_id:?}");
}
false
});
let next_repaint_time = self.windows_next_repaint_times.values().min().copied();
if let Some(next_repaint_time) = next_repaint_time {
event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
};
}
}
impl<T: WinitApp> ApplicationHandler<UserEvent> for WinitAppWrapper<T> {
fn suspended(&mut self, event_loop: &ActiveEventLoop) {
profiling::scope!("Event::Suspended");
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = self.winit_app.suspended(event_loop);
self.handle_event_result(event_loop, event_result);
});
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
profiling::scope!("Event::Resumed");
// Nb: Make sure this guard is dropped after this function returns.
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = self.winit_app.resumed(event_loop);
self.handle_event_result(event_loop, event_result);
});
}
fn exiting(&mut self, event_loop: &ActiveEventLoop) {
// On Mac, Cmd-Q we get here and then `run_app_on_demand` doesn't return (despite its name),
// so we need to save state now:
log::debug!("Received Event::LoopExiting - saving app state…");
event_loop_context::with_event_loop_context(event_loop, move || {
self.winit_app.save_and_destroy();
});
}
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
profiling::function_scope!(egui_winit::short_device_event_description(&event));
// Nb: Make sure this guard is dropped after this function returns.
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = self.winit_app.device_event(event_loop, device_id, event);
self.handle_event_result(event_loop, event_result);
});
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
profiling::function_scope!(match &event {
UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
#[cfg(feature = "accesskit")]
UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",
});
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = match event {
UserEvent::RequestRepaint {
when,
cumulative_pass_nr,
viewport_id,
} => {
let current_pass_nr = self
.winit_app
.egui_ctx()
.map_or(0, |ctx| ctx.cumulative_pass_nr_for(viewport_id));
if current_pass_nr == cumulative_pass_nr
|| current_pass_nr == cumulative_pass_nr + 1
{
log::trace!("UserEvent::RequestRepaint scheduling repaint at {when:?}");
if let Some(window_id) =
self.winit_app.window_id_from_viewport_id(viewport_id)
{
Ok(EventResult::RepaintAt(window_id, when))
} else {
Ok(EventResult::Wait)
}
} else {
log::trace!("Got outdated UserEvent::RequestRepaint");
Ok(EventResult::Wait) // old request - we've already repainted
}
}
#[cfg(feature = "accesskit")]
UserEvent::AccessKitActionRequest(request) => {
self.winit_app.on_accesskit_event(request)
}
};
self.handle_event_result(event_loop, event_result);
});
}
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: winit::event::StartCause) {
if let winit::event::StartCause::ResumeTimeReached { .. } = cause {
log::trace!("Woke up to check next_repaint_time");
}
self.check_redraw_requests(event_loop);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: winit::event::WindowEvent,
) {
profiling::function_scope!(egui_winit::short_window_event_description(&event));
// Nb: Make sure this guard is dropped after this function returns.
event_loop_context::with_event_loop_context(event_loop, move || {
let event_result = match event {
winit::event::WindowEvent::RedrawRequested => {
self.winit_app.run_ui_and_paint(event_loop, window_id)
}
_ => self.winit_app.window_event(event_loop, window_id, event),
};
self.handle_event_result(event_loop, event_result);
});
}
}
#[cfg(not(target_os = "ios"))]
fn run_and_return(event_loop: &mut EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
use winit::platform::run_on_demand::EventLoopExtRunOnDemand;
log::trace!("Entering the winit event loop (run_app_on_demand)…");
let mut app = WinitAppWrapper::new(winit_app, true);
event_loop.run_app_on_demand(&mut app)?;
log::debug!("eframe window closed");
app.return_result
}
fn run_and_exit(event_loop: EventLoop<UserEvent>, winit_app: impl WinitApp) -> Result {
log::trace!("Entering the winit event loop (run_app)…");
// When to repaint what window
let mut app = WinitAppWrapper::new(winit_app, false);
event_loop.run_app(&mut app)?;
log::debug!("winit event loop unexpectedly returned");
Ok(())
}
// ----------------------------------------------------------------------------
#[cfg(feature = "glow")]
pub fn run_glow(
app_name: &str,
mut native_options: epi::NativeOptions,
app_creator: epi::AppCreator<'_>,
) -> Result {
#![allow(clippy::needless_return_with_question_mark)] // False positive
use super::glow_integration::GlowWinitApp;
#[cfg(not(target_os = "ios"))]
if native_options.run_and_return {
return with_event_loop(native_options, |event_loop, native_options| {
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
run_and_return(event_loop, glow_eframe)
})?;
}
let event_loop = create_event_loop(&mut native_options)?;
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
run_and_exit(event_loop, glow_eframe)
}
// ----------------------------------------------------------------------------
#[cfg(feature = "wgpu")]
pub fn run_wgpu(
app_name: &str,
mut native_options: epi::NativeOptions,
app_creator: epi::AppCreator<'_>,
) -> Result {
#![allow(clippy::needless_return_with_question_mark)] // False positive
use super::wgpu_integration::WgpuWinitApp;
#[cfg(not(target_os = "ios"))]
if native_options.run_and_return {
return with_event_loop(native_options, |event_loop, native_options| {
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
run_and_return(event_loop, wgpu_eframe)
})?;
}
let event_loop = create_event_loop(&mut native_options)?;
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
run_and_exit(event_loop, wgpu_eframe)
}