mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 22:00:03 -04:00
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 
This commit is contained in:
@@ -62,10 +62,6 @@ mint = ["epaint/mint"]
|
||||
## Enable persistence of memory (window positions etc).
|
||||
persistence = ["serde", "epaint/serde", "ron"]
|
||||
|
||||
## Enable profiling with the [`puffin`](https://docs.rs/puffin) crate.
|
||||
##
|
||||
## Only enabled on native, because of the low resolution (1ms) of clocks in browsers.
|
||||
puffin = ["dep:puffin", "epaint/puffin"]
|
||||
|
||||
## Enable parallel tessellation using [`rayon`](https://docs.rs/rayon).
|
||||
##
|
||||
@@ -85,6 +81,7 @@ epaint = { workspace = true, default-features = false }
|
||||
|
||||
ahash.workspace = true
|
||||
nohash-hasher.workspace = true
|
||||
profiling.workspace = true
|
||||
|
||||
#! ### Optional dependencies
|
||||
accesskit = { version = "0.17.0", optional = true }
|
||||
@@ -95,7 +92,6 @@ backtrace = { workspace = true, optional = true }
|
||||
document-features = { workspace = true, optional = true }
|
||||
|
||||
log = { workspace = true, optional = true }
|
||||
puffin = { workspace = true, optional = true }
|
||||
ron = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true, features = ["derive", "rc"] }
|
||||
|
||||
|
||||
@@ -109,13 +109,13 @@ struct Plugins {
|
||||
|
||||
impl Plugins {
|
||||
fn call(ctx: &Context, _cb_name: &str, callbacks: &[NamedContextCallback]) {
|
||||
crate::profile_scope!("plugins", _cb_name);
|
||||
profiling::scope!("plugins", _cb_name);
|
||||
for NamedContextCallback {
|
||||
debug_name: _name,
|
||||
callback,
|
||||
} in callbacks
|
||||
{
|
||||
crate::profile_scope!("plugin", _name);
|
||||
profiling::scope!("plugin", _name);
|
||||
(callback)(ctx);
|
||||
}
|
||||
}
|
||||
@@ -549,7 +549,7 @@ impl ContextImpl {
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
if self.is_accesskit_enabled {
|
||||
crate::profile_scope!("accesskit");
|
||||
profiling::scope!("accesskit");
|
||||
use crate::pass_state::AccessKitPassState;
|
||||
let id = crate::accesskit_root_id();
|
||||
let mut root_node = accesskit::Node::new(accesskit::Role::Window);
|
||||
@@ -568,8 +568,7 @@ impl ContextImpl {
|
||||
|
||||
/// Load fonts unless already loaded.
|
||||
fn update_fonts_mut(&mut self) {
|
||||
crate::profile_function!();
|
||||
|
||||
profiling::function_scope!();
|
||||
let input = &self.viewport().input;
|
||||
let pixels_per_point = input.pixels_per_point();
|
||||
let max_texture_side = input.max_texture_side;
|
||||
@@ -616,7 +615,7 @@ impl ContextImpl {
|
||||
log::trace!("Creating new Fonts for pixels_per_point={pixels_per_point}");
|
||||
|
||||
is_new = true;
|
||||
crate::profile_scope!("Fonts::new");
|
||||
profiling::scope!("Fonts::new");
|
||||
Fonts::new(
|
||||
pixels_per_point,
|
||||
max_texture_side,
|
||||
@@ -625,12 +624,12 @@ impl ContextImpl {
|
||||
});
|
||||
|
||||
{
|
||||
crate::profile_scope!("Fonts::begin_pass");
|
||||
profiling::scope!("Fonts::begin_pass");
|
||||
fonts.begin_pass(pixels_per_point, max_texture_side);
|
||||
}
|
||||
|
||||
if is_new && self.memory.options.preload_font_glyphs {
|
||||
crate::profile_scope!("preload_font_glyphs");
|
||||
profiling::scope!("preload_font_glyphs");
|
||||
// Preload the most common characters for the most common fonts.
|
||||
// This is not very important to do, but may save a few GPU operations.
|
||||
for font_id in self.memory.options.style().text_styles.values() {
|
||||
@@ -812,8 +811,7 @@ impl Context {
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput {
|
||||
crate::profile_function!();
|
||||
|
||||
profiling::function_scope!();
|
||||
let viewport_id = new_input.viewport_id;
|
||||
let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get());
|
||||
|
||||
@@ -821,9 +819,13 @@ impl Context {
|
||||
debug_assert_eq!(output.platform_output.num_completed_passes, 0);
|
||||
|
||||
loop {
|
||||
crate::profile_scope!(
|
||||
profiling::scope!(
|
||||
"pass",
|
||||
output.platform_output.num_completed_passes.to_string()
|
||||
output
|
||||
.platform_output
|
||||
.num_completed_passes
|
||||
.to_string()
|
||||
.as_str()
|
||||
);
|
||||
|
||||
// We must move the `num_passes` (back) to the viewport output so that [`Self::will_discard`]
|
||||
@@ -886,7 +888,7 @@ impl Context {
|
||||
/// // handle full_output
|
||||
/// ```
|
||||
pub fn begin_pass(&self, new_input: RawInput) {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
self.write(|ctx| ctx.begin_pass(new_input));
|
||||
|
||||
@@ -1760,7 +1762,7 @@ impl Context {
|
||||
/// The new fonts will become active at the start of the next pass.
|
||||
/// This will overwrite the existing fonts.
|
||||
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
let pixels_per_point = self.pixels_per_point();
|
||||
|
||||
@@ -1788,7 +1790,7 @@ impl Context {
|
||||
/// The new font will become active at the start of the next pass.
|
||||
/// This will keep the existing fonts.
|
||||
pub fn add_font(&self, new_font: FontInsert) {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
let pixels_per_point = self.pixels_per_point();
|
||||
|
||||
@@ -2152,7 +2154,7 @@ impl Context {
|
||||
/// Call at the end of each frame if you called [`Context::begin_pass`].
|
||||
#[must_use]
|
||||
pub fn end_pass(&self) -> FullOutput {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
if self.options(|o| o.zoom_with_keyboard) {
|
||||
crate::gui_zoom::zoom_with_keyboard(self);
|
||||
@@ -2342,7 +2344,7 @@ impl ContextImpl {
|
||||
// https://github.com/emilk/egui/issues/3664
|
||||
// at the cost of a lot of performance.
|
||||
// (This will override any smaller delta that was uploaded above.)
|
||||
crate::profile_scope!("full_font_atlas_update");
|
||||
profiling::scope!("full_font_atlas_update");
|
||||
let full_delta = ImageDelta::full(fonts.image(), TextureAtlas::texture_options());
|
||||
tex_mngr.set(TextureId::default(), full_delta);
|
||||
}
|
||||
@@ -2356,7 +2358,7 @@ impl ContextImpl {
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
{
|
||||
crate::profile_scope!("accesskit");
|
||||
profiling::scope!("accesskit");
|
||||
let state = viewport.this_pass.accesskit_state.take();
|
||||
if let Some(state) = state {
|
||||
let root_id = crate::accesskit_root_id().accesskit_id();
|
||||
@@ -2386,7 +2388,7 @@ impl ContextImpl {
|
||||
let mut repaint_needed = false;
|
||||
|
||||
if self.memory.options.repaint_on_widget_change {
|
||||
crate::profile_function!("compare-widget-rects");
|
||||
profiling::scope!("compare-widget-rects");
|
||||
if viewport.prev_pass.widgets != viewport.this_pass.widgets {
|
||||
repaint_needed = true; // Some widget has moved
|
||||
}
|
||||
@@ -2525,7 +2527,7 @@ impl Context {
|
||||
shapes: Vec<ClippedShape>,
|
||||
pixels_per_point: f32,
|
||||
) -> Vec<ClippedPrimitive> {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
// A tempting optimization is to reuse the tessellation from last frame if the
|
||||
// shapes are the same, but just comparing the shapes takes about 50% of the time
|
||||
@@ -2552,7 +2554,7 @@ impl Context {
|
||||
|
||||
let paint_stats = PaintStats::from_shapes(&shapes);
|
||||
let clipped_primitives = {
|
||||
crate::profile_scope!("tessellator::tessellate_shapes");
|
||||
profiling::scope!("tessellator::tessellate_shapes");
|
||||
tessellator::Tessellator::new(
|
||||
pixels_per_point,
|
||||
tessellation_options,
|
||||
@@ -3368,7 +3370,7 @@ impl Context {
|
||||
pub fn forget_image(&self, uri: &str) {
|
||||
use load::BytesLoader as _;
|
||||
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
let loaders = self.loaders();
|
||||
|
||||
@@ -3390,7 +3392,7 @@ impl Context {
|
||||
pub fn forget_all_images(&self) {
|
||||
use load::BytesLoader as _;
|
||||
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
let loaders = self.loaders();
|
||||
|
||||
@@ -3425,7 +3427,7 @@ impl Context {
|
||||
/// [not_supported]: crate::load::LoadError::NotSupported
|
||||
/// [custom]: crate::load::LoadError::Loading
|
||||
pub fn try_load_bytes(&self, uri: &str) -> load::BytesLoadResult {
|
||||
crate::profile_function!(uri);
|
||||
profiling::function_scope!(uri);
|
||||
|
||||
let loaders = self.loaders();
|
||||
let bytes_loaders = loaders.bytes.lock();
|
||||
@@ -3462,7 +3464,7 @@ impl Context {
|
||||
/// [not_supported]: crate::load::LoadError::NotSupported
|
||||
/// [custom]: crate::load::LoadError::Loading
|
||||
pub fn try_load_image(&self, uri: &str, size_hint: load::SizeHint) -> load::ImageLoadResult {
|
||||
crate::profile_function!(uri);
|
||||
profiling::function_scope!(uri);
|
||||
|
||||
let loaders = self.loaders();
|
||||
let image_loaders = loaders.image.lock();
|
||||
@@ -3513,7 +3515,7 @@ impl Context {
|
||||
texture_options: TextureOptions,
|
||||
size_hint: load::SizeHint,
|
||||
) -> load::TextureLoadResult {
|
||||
crate::profile_function!(uri);
|
||||
profiling::function_scope!(uri);
|
||||
|
||||
let loaders = self.loaders();
|
||||
let texture_loaders = loaders.texture.lock();
|
||||
@@ -3531,7 +3533,7 @@ impl Context {
|
||||
|
||||
/// The loaders of bytes, images, and textures.
|
||||
pub fn loaders(&self) -> Arc<Loaders> {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
self.read(|this| this.loaders.clone())
|
||||
}
|
||||
}
|
||||
@@ -3663,7 +3665,7 @@ impl Context {
|
||||
viewport_builder: ViewportBuilder,
|
||||
viewport_ui_cb: impl Fn(&Self, ViewportClass) + Send + Sync + 'static,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
if self.embed_viewports() {
|
||||
viewport_ui_cb(self, ViewportClass::Embedded);
|
||||
@@ -3715,7 +3717,7 @@ impl Context {
|
||||
builder: ViewportBuilder,
|
||||
mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T,
|
||||
) -> T {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
if self.embed_viewports() {
|
||||
return viewport_ui_cb(self, ViewportClass::Embedded);
|
||||
|
||||
@@ -39,7 +39,7 @@ pub fn hit_test(
|
||||
pos: Pos2,
|
||||
search_radius: f32,
|
||||
) -> WidgetHits {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
let search_radius_sq = search_radius * search_radius;
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ impl InputState {
|
||||
pixels_per_point: f32,
|
||||
options: &crate::Options,
|
||||
) -> Self {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
let time = new.time.unwrap_or(self.time + new.predicted_dt as f64);
|
||||
let unstable_dt = (time - self.time) as f32;
|
||||
|
||||
@@ -113,7 +113,7 @@ pub(crate) fn interact(
|
||||
input: &InputState,
|
||||
interaction: &mut InteractionState,
|
||||
) -> InteractionSnapshot {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
if let Some(id) = interaction.potential_click_id {
|
||||
if !widgets.contains(id) {
|
||||
|
||||
@@ -222,7 +222,7 @@ impl GraphicLayers {
|
||||
area_order: &[LayerId],
|
||||
to_global: &ahash::HashMap<LayerId, TSTransform>,
|
||||
) -> Vec<ClippedShape> {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
let mut all_shapes: Vec<_> = Default::default();
|
||||
|
||||
|
||||
@@ -388,6 +388,18 @@
|
||||
//! ## Installing additional fonts
|
||||
//! The default egui fonts only support latin and cryllic characters, and some emojis.
|
||||
//! To use egui with e.g. asian characters you need to install your own font (`.ttf` or `.otf`) using [`Context::set_fonts`].
|
||||
//!
|
||||
//! ## Instrumentation
|
||||
//! This crate supports using the [profiling](https://crates.io/crates/profiling) crate for instrumentation.
|
||||
//! You can enable features on the profiling crates in your application to add instrumentation for all
|
||||
//! crates that support it, including egui. See the profiling crate docs for more information.
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! profiling = "1.0"
|
||||
//! [features]
|
||||
//! profile-with-puffin = ["profiling/profile-with-puffin"]
|
||||
//! ```
|
||||
//!
|
||||
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
@@ -691,33 +703,3 @@ pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) {
|
||||
pub fn accesskit_root_id() -> Id {
|
||||
Id::new("accesskit_root")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
mod profiling_scopes {
|
||||
#![allow(unused_macros)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
macro_rules! profile_function {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
||||
puffin::profile_function!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_function;
|
||||
|
||||
/// Profiling macro for feature "puffin"
|
||||
macro_rules! profile_scope {
|
||||
($($arg: tt)*) => {
|
||||
#[cfg(feature = "puffin")]
|
||||
#[cfg(not(target_arch = "wasm32"))] // Disabled on web because of the coarse 1ms clock resolution there.
|
||||
puffin::profile_scope!($($arg)*);
|
||||
};
|
||||
}
|
||||
pub(crate) use profile_scope;
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use profiling_scopes::{profile_function, profile_scope};
|
||||
|
||||
@@ -779,7 +779,7 @@ impl Focus {
|
||||
|
||||
impl Memory {
|
||||
pub(crate) fn begin_pass(&mut self, new_raw_input: &RawInput, viewports: &ViewportIdSet) {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
self.viewport_id = new_raw_input.viewport_id;
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ impl Default for PassState {
|
||||
|
||||
impl PassState {
|
||||
pub(crate) fn begin_pass(&mut self, screen_rect: Rect) {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
let Self {
|
||||
used_ids,
|
||||
widgets,
|
||||
|
||||
@@ -574,7 +574,7 @@ struct PersistedMap(Vec<(u64, SerializedElement)>);
|
||||
#[cfg(feature = "persistence")]
|
||||
impl PersistedMap {
|
||||
fn from_map(map: &IdTypeMap) -> Self {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@@ -593,7 +593,7 @@ impl PersistedMap {
|
||||
let max_bytes_per_type = map.max_bytes_per_type;
|
||||
|
||||
{
|
||||
crate::profile_scope!("gather");
|
||||
profiling::scope!("gather");
|
||||
for (hash, element) in &map.map {
|
||||
if let Some(element) = element.to_serialize() {
|
||||
let stats = types_map.entry(element.type_id).or_default();
|
||||
@@ -610,7 +610,7 @@ impl PersistedMap {
|
||||
let mut persisted = vec![];
|
||||
|
||||
{
|
||||
crate::profile_scope!("gc");
|
||||
profiling::scope!("gc");
|
||||
for stats in types_map.values() {
|
||||
let mut bytes_written = 0;
|
||||
|
||||
@@ -634,7 +634,7 @@ impl PersistedMap {
|
||||
}
|
||||
|
||||
fn into_map(self) -> IdTypeMap {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
let map = self
|
||||
.0
|
||||
.into_iter()
|
||||
@@ -671,7 +671,7 @@ impl serde::Serialize for IdTypeMap {
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
crate::profile_scope!("IdTypeMap::serialize");
|
||||
profiling::scope!("IdTypeMap::serialize");
|
||||
PersistedMap::from_map(self).serialize(serializer)
|
||||
}
|
||||
}
|
||||
@@ -682,7 +682,7 @@ impl<'de> serde::Deserialize<'de> for IdTypeMap {
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
crate::profile_scope!("IdTypeMap::deserialize");
|
||||
profiling::scope!("IdTypeMap::deserialize");
|
||||
<PersistedMap>::deserialize(deserializer).map(PersistedMap::into_map)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ impl std::fmt::Debug for IconData {
|
||||
|
||||
impl From<IconData> for epaint::ColorImage {
|
||||
fn from(icon: IconData) -> Self {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
let IconData {
|
||||
rgba,
|
||||
width,
|
||||
@@ -200,7 +200,7 @@ impl From<IconData> for epaint::ColorImage {
|
||||
|
||||
impl From<&IconData> for epaint::ColorImage {
|
||||
fn from(icon: &IconData) -> Self {
|
||||
crate::profile_function!();
|
||||
profiling::function_scope!();
|
||||
let IconData {
|
||||
rgba,
|
||||
width,
|
||||
|
||||
Reference in New Issue
Block a user