1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00
Files
egui/crates/egui_demo_lib/benches/benchmark.rs
Lucas Meurer 33c85bd62a Untangle logic-only frames: one frame entry point, one output type
Same fixes as the previous commits (run no egui pass at all when
nothing will be shown), but the mechanisms are shared instead of
duplicated:

* `Context::run_frame(input, show_ui, f)` is the one entry point for
  integrations: one `FramePhase::Logic` (always, outside any pass),
  then one `FramePhase::Ui` per pass (none when `show_ui` is false).
  `Context::run_logic` is now a thin wrapper around it, and the
  logic-outside-of-pass sequencing lives in egui, not in each backend.

* egui itself buffers the input of pass-less frames
  (`ViewportState::pending_raw_input`) and prepends it to the next
  pass. This replaces `EpiIntegration::pending_raw_input` and the web
  backend's `input.raw.append`, so an integration cannot lose input.

* `FullOutput` is now `{ platform_output, viewport_commands,
  pass_output: Option<PassOutput> }`, and `LogicOutput` is gone.
  One-shot viewport commands (imperative) are separated from
  `ViewportOutput` (which viewports should exist - declarative), so
  each backend has exactly one command-handling path, shared by frames
  with and without a pass. `pass_output: None` encodes "no pass ran:
  paint nothing, leave the viewports alone" in the type.

* The glow/wgpu `!show_ui` early-return blocks are gone: a hidden root
  viewport flows through the same tail as a visible one, with the
  paint and viewport-structure steps gated on `pass_output`.

Behavioral fixes that fall out:

* `App::logic` now sees the current window state in visible frames
  too (it was one frame stale outside the hidden path).
* Auto-save keeps working while a window is minimized or occluded.
* Commands sent to a freshly created viewport apply in the same frame.
* The Wayland resize workaround now also covers commands from
  pass-less frames.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 19:09:17 +02:00

263 lines
9.0 KiB
Rust

use std::fmt::Write as _;
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use egui::epaint::TextShape;
use egui::load::SizedTexture;
use egui::{Button, Id, RichText, TextureId, Ui, UiBuilder, Vec2};
use egui_demo_lib::LOREM_IPSUM_LONG;
use rand::RngExt as _;
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; // Much faster allocator
/// Each iteration should be called in their own `Ui` with an intentional id clash,
/// to prevent the Context from building a massive map of `WidgetRects` (which would slow the test,
/// causing unreliable results).
fn create_benchmark_ui(ctx: &egui::Context) -> Ui {
Ui::new(ctx.clone(), Id::new("clashing_id"), UiBuilder::new())
}
pub fn criterion_benchmark(c: &mut Criterion) {
use egui::RawInput;
{
let ctx = egui::Context::default();
let mut demo_windows = egui_demo_lib::DemoWindows::default();
// The most end-to-end benchmark.
c.bench_function("demo_with_tessellate__realistic", |b| {
b.iter(|| {
let full_output = ctx.run_ui(RawInput::default(), |ui| {
demo_windows.ui(ui);
});
let mut pass_output = full_output.pass_output.expect("run_ui always runs a pass");
ctx.tessellate(pass_output.shapes, pass_output.pixels_per_point);
pass_output.textures_delta.clear(); // Don't panic on drop with unapplied deltas
});
});
c.bench_function("demo_no_tessellate", |b| {
b.iter(|| {
let output = ctx.run_ui(RawInput::default(), |ui| {
demo_windows.ui(ui);
});
output.drop_without_applying_deltas();
});
});
let full_output = ctx.run_ui(RawInput::default(), |ui| {
demo_windows.ui(ui);
});
c.bench_function("demo_only_tessellate", |b| {
b.iter(|| {
let pass_output = full_output.expect_pass();
ctx.tessellate(pass_output.shapes.clone(), pass_output.pixels_per_point)
});
});
full_output.drop_without_applying_deltas();
}
if false {
let ctx = egui::Context::default();
ctx.memory_mut(|m| m.set_everything_is_visible(true)); // give us everything
let mut demo_windows = egui_demo_lib::DemoWindows::default();
c.bench_function("demo_full_no_tessellate", |b| {
b.iter(|| {
ctx.run_ui(RawInput::default(), |ui| {
demo_windows.ui(ui);
})
});
});
}
{
let ctx = egui::Context::default();
let output = ctx.run_ui(RawInput::default(), |ui| {
c.bench_function("label &str", |b| {
b.iter_batched_ref(
|| create_benchmark_ui(ui),
|ui| {
ui.label("the quick brown fox jumps over the lazy dog");
},
BatchSize::LargeInput,
);
});
c.bench_function("label format!", |b| {
b.iter_batched_ref(
|| create_benchmark_ui(ui),
|ui| {
ui.label("the quick brown fox jumps over the lazy dog".to_owned());
},
BatchSize::LargeInput,
);
});
});
output.drop_without_applying_deltas();
}
{
let ctx = egui::Context::default();
let output = ctx.run_ui(RawInput::default(), |ui| {
let mut group = c.benchmark_group("button");
// To ensure we have a valid image, let's use the font texture. The size
// shouldn't be important for this benchmark.
let image = SizedTexture::new(TextureId::default(), Vec2::splat(16.0));
group.bench_function("1_button_text", |b| {
b.iter_batched_ref(
|| create_benchmark_ui(ui),
|ui| {
ui.add(Button::new("Hello World"));
},
BatchSize::LargeInput,
);
});
group.bench_function("2_button_text_image", |b| {
b.iter_batched_ref(
|| create_benchmark_ui(ui),
|ui| {
ui.add(Button::image_and_text(image, "Hello World"));
},
BatchSize::LargeInput,
);
});
group.bench_function("3_button_text_image_right_text", |b| {
b.iter_batched_ref(
|| create_benchmark_ui(ui),
|ui| {
ui.add(Button::image_and_text(image, "Hello World").right_text(""));
},
BatchSize::LargeInput,
);
});
group.bench_function("4_button_italic", |b| {
b.iter_batched_ref(
|| create_benchmark_ui(ui),
|ui| {
ui.add(Button::new(RichText::new("Hello World").italics()));
},
BatchSize::LargeInput,
);
});
});
output.drop_without_applying_deltas();
}
{
let ctx = egui::Context::default();
ctx.begin_pass(RawInput::default());
let painter =
egui::Painter::new(ctx.clone(), egui::LayerId::background(), ctx.content_rect());
c.bench_function("Painter::rect", |b| {
let rect = painter.clip_rect();
b.iter(|| {
painter.rect(
rect,
2.0,
egui::Color32::RED,
(1.0, egui::Color32::WHITE),
egui::StrokeKind::Inside,
);
});
});
// Don't call `end_pass` to not have to drain the huge paint list
}
{
let pixels_per_point = 1.0;
let wrap_width = 512.0;
let font_id = egui::FontId::default();
let text_color = egui::Color32::WHITE;
let mut fonts =
egui::epaint::text::Fonts::new(Default::default(), egui::FontDefinitions::default());
{
c.bench_function("text_layout_uncached", |b| {
b.iter(|| {
use egui::epaint::text::{LayoutJob, layout};
let job = LayoutJob::simple(
LOREM_IPSUM_LONG.to_owned(),
font_id.clone(),
text_color,
wrap_width,
);
layout(&mut fonts.fonts, pixels_per_point, job.into())
});
});
}
c.bench_function("text_layout_cached", |b| {
b.iter(|| {
fonts.with_pixels_per_point(pixels_per_point).layout(
LOREM_IPSUM_LONG.to_owned(),
font_id.clone(),
text_color,
wrap_width,
)
});
});
c.bench_function("text_layout_cached_many_lines_modified", |b| {
const NUM_LINES: usize = 2_000;
let mut string = String::new();
for _ in 0..NUM_LINES {
for i in 0..30_u8 {
#[expect(clippy::unwrap_used)]
write!(string, "{i:02X} ").unwrap();
}
string.push('\n');
}
let mut rng = rand::rng();
b.iter(|| {
fonts.begin_pass(egui::epaint::TextOptions::default());
// Delete a random character, simulating a user making an edit in a long file:
let mut new_string = string.clone();
let idx = rng.random_range(0..string.len());
new_string.remove(idx);
fonts.with_pixels_per_point(pixels_per_point).layout(
new_string,
font_id.clone(),
text_color,
wrap_width,
);
});
});
let galley = fonts.with_pixels_per_point(pixels_per_point).layout(
LOREM_IPSUM_LONG.to_owned(),
font_id,
text_color,
wrap_width,
);
let font_image_size = fonts.font_image_size();
let prepared_discs = fonts.texture_atlas().prepared_discs();
let mut tessellator = egui::epaint::Tessellator::new(
1.0,
Default::default(),
font_image_size,
prepared_discs,
);
let mut mesh = egui::epaint::Mesh::default();
let text_shape = TextShape::new(egui::Pos2::ZERO, galley, text_color);
c.bench_function("tessellate_text", |b| {
b.iter(|| {
tessellator.tessellate_text(&text_shape, &mut mesh);
mesh.clear();
});
});
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);