//! The egui harness, so the immediate renderer has a number. //! //! [`quasi_immediate::Immediate::screen`] takes a `&mut Ui`, and a `Ui` is not //! constructible on its own: egui hands one out inside a frame it is running. //! So measuring this renderer means standing up a frame rather than calling a //! function, which is why the bench went two renderers wide for as long as it //! did. //! //! # What a cell in this column is //! //! One frame: a `Context` run over a fixed viewport with no input, drawing the //! fixture inside a central panel. That is the terminal column's unit and not //! the webview's -- a webview emits every row into a string the browser //! scrolls, and both of the other two draw one screenful and leave the rest to //! scroll state. Expect it to be flatter across the row counts than the webview //! is, and read the places where it is not: that is the description being //! walked before the drawing is clipped. //! //! # Three things hoisted out of the loop, each for the buffer's reason //! //! The terminal column hoists its `Buffer` because `Buffer::empty` for a 160x50 //! viewport swamped the drawing when this bench first ran. The same argument //! applies three times here, and it is stronger, because egui's per-frame state //! is larger than a cell grid. //! //! - **The `Context`.** It owns the font atlas, the texture allocations and the //! memory carried between frames. A fresh one per iteration measures egui //! starting up, which is a cost a host pays once at launch. //! - **The `View`.** A host owns one across frames; it is what remembers which //! row is current and what is open. //! - **The `Screen`.** Unlike the other columns, which rebuild the description //! per iteration on purpose. See [`frame`]: this column cannot make that //! choice honestly, and says so rather than quietly measuring something else. //! //! # The palette is the shipped theme, not an invented one //! //! [`palette`] resolves the same bundled `goingson` theme the terminal renderer //! loads, through `makeover::resolve`'s own `rgb`/`rgba` accessors. A literal //! would be sixteen colours this file made up, and the two renderers would then //! be drawing different themes in the same table. use egui::{Color32, Context, Pos2, RawInput, Rect, vec2}; use makeover_immediate::Palette; use quasi_immediate::{Immediate, View}; use quasi_router::Screen; /// The window the frame is drawn in. /// /// The terminal's `TERMINAL` and its reasoning, in points: a real window rather /// than one sized to the content, because sizing it to the content measures a /// window nobody has and charges the renderer for rows no reader can see. const WINDOW: (f32, f32) = (1280.0, 900.0); /// A renderer in the shipped theme. #[must_use] pub fn renderer() -> Immediate { Immediate::new(palette()) } /// A context sized to [`WINDOW`], with fonts already built. /// /// The caller holds it across every iteration. Building one costs the font /// atlas, which is the single largest allocation in an egui program and is paid /// once at launch by any real host. #[must_use] pub fn context() -> Context { Context::default() } /// One frame. /// /// The screen is borrowed rather than built here, which is the one place this /// column departs from the others and it is deliberate rather than convenient. /// The webview and terminal columns rebuild the description per iteration /// because a request pays for it; an immediate-mode host does not have /// requests. It draws sixty frames a second over a description it built when /// the screen changed, so charging every frame for the build would report a /// cost the app does not have. /// /// The build cost is not lost: it is the `build tree` column, which is the same /// number for all three renderers. pub fn frame(ctx: &Context, immediate: &Immediate, screen: &Screen, view: &mut View) { let input = RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(WINDOW.0, WINDOW.1))), ..Default::default() }; let output = ctx.run_ui(input, |ctx| { egui::CentralPanel::default().show(ctx, |ui| { immediate.screen(ui, screen, view); }); }); // Consumed so the tessellation is not dead-code-eliminated, and dropped // rather than returned: a host uploads these to the GPU, which is not this // crate's cost and not measurable without one. std::hint::black_box(&output.shapes); } /// The shipped `goingson` theme, as an immediate-mode palette. /// /// Every one of the sixteen fields comes from a resolved token. `overlay` and /// `elevation` are the two intents makeover emits as `rgba(...)` rather than /// hex, because they are scrims, so they are read through `rgba` and the rest /// through `rgb`. Reading a scrim as an opaque colour is how a translucent /// token becomes a near-black rectangle over the page. fn palette() -> Palette { let dir = makeover::bundled_themes_dir().expect("makeover ships themes"); let colours = makeover::load_theme(&[(dir, false)], "goingson").expect("a bundled theme loads"); let tokens = makeover::resolve(&colours); let opaque = |key: &str| { let (r, g, b) = tokens .rgb(key) .unwrap_or_else(|| panic!("the resolved theme carries `{key}`")); Color32::from_rgb(r, g, b) }; let scrim = |key: &str| { let (r, g, b, a) = tokens .rgba(key) .unwrap_or_else(|| panic!("the resolved theme carries `{key}`")); Color32::from_rgba_unmultiplied(r, g, b, a) }; Palette { page: opaque("surface-page"), raised: opaque("surface-raised"), overlay: scrim("overlay"), well: opaque("surface-well"), sunken: opaque("surface-sunken"), bevel_light: opaque("bevel-light"), bevel_dark: opaque("bevel-dark"), elevation: scrim("elevation"), content: opaque("content"), content_secondary: opaque("content-secondary"), content_muted: opaque("content-muted"), action: opaque("action"), danger: opaque("danger"), success: opaque("success"), warning: opaque("warning"), info: opaque("info"), } }