mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
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>
263 lines
9.0 KiB
Rust
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);
|