Skip to main content

max / quasi

6.1 KB · 142 lines History Blame Raw
1 //! The egui harness, so the immediate renderer has a number.
2 //!
3 //! [`quasi_immediate::Immediate::screen`] takes a `&mut Ui`, and a `Ui` is not
4 //! constructible on its own: egui hands one out inside a frame it is running.
5 //! So measuring this renderer means standing up a frame rather than calling a
6 //! function, which is why the bench went two renderers wide for as long as it
7 //! did.
8 //!
9 //! # What a cell in this column is
10 //!
11 //! One frame: a `Context` run over a fixed viewport with no input, drawing the
12 //! fixture inside a central panel. That is the terminal column's unit and not
13 //! the webview's -- a webview emits every row into a string the browser
14 //! scrolls, and both of the other two draw one screenful and leave the rest to
15 //! scroll state. Expect it to be flatter across the row counts than the webview
16 //! is, and read the places where it is not: that is the description being
17 //! walked before the drawing is clipped.
18 //!
19 //! # Three things hoisted out of the loop, each for the buffer's reason
20 //!
21 //! The terminal column hoists its `Buffer` because `Buffer::empty` for a 160x50
22 //! viewport swamped the drawing when this bench first ran. The same argument
23 //! applies three times here, and it is stronger, because egui's per-frame state
24 //! is larger than a cell grid.
25 //!
26 //! - **The `Context`.** It owns the font atlas, the texture allocations and the
27 //! memory carried between frames. A fresh one per iteration measures egui
28 //! starting up, which is a cost a host pays once at launch.
29 //! - **The `View`.** A host owns one across frames; it is what remembers which
30 //! row is current and what is open.
31 //! - **The `Screen`.** Unlike the other columns, which rebuild the description
32 //! per iteration on purpose. See [`frame`]: this column cannot make that
33 //! choice honestly, and says so rather than quietly measuring something else.
34 //!
35 //! # The palette is the shipped theme, not an invented one
36 //!
37 //! [`palette`] resolves the same bundled `goingson` theme the terminal renderer
38 //! loads, through `makeover::resolve`'s own `rgb`/`rgba` accessors. A literal
39 //! would be sixteen colours this file made up, and the two renderers would then
40 //! be drawing different themes in the same table.
41
42 use egui::{Color32, Context, Pos2, RawInput, Rect, vec2};
43 use makeover_immediate::Palette;
44 use quasi_immediate::{Immediate, View};
45 use quasi_router::Screen;
46
47 /// The window the frame is drawn in.
48 ///
49 /// The terminal's `TERMINAL` and its reasoning, in points: a real window rather
50 /// than one sized to the content, because sizing it to the content measures a
51 /// window nobody has and charges the renderer for rows no reader can see.
52 const WINDOW: (f32, f32) = (1280.0, 900.0);
53
54 /// A renderer in the shipped theme.
55 #[must_use]
56 pub fn renderer() -> Immediate {
57 Immediate::new(palette())
58 }
59
60 /// A context sized to [`WINDOW`], with fonts already built.
61 ///
62 /// The caller holds it across every iteration. Building one costs the font
63 /// atlas, which is the single largest allocation in an egui program and is paid
64 /// once at launch by any real host.
65 #[must_use]
66 pub fn context() -> Context {
67 Context::default()
68 }
69
70 /// One frame.
71 ///
72 /// The screen is borrowed rather than built here, which is the one place this
73 /// column departs from the others and it is deliberate rather than convenient.
74 /// The webview and terminal columns rebuild the description per iteration
75 /// because a request pays for it; an immediate-mode host does not have
76 /// requests. It draws sixty frames a second over a description it built when
77 /// the screen changed, so charging every frame for the build would report a
78 /// cost the app does not have.
79 ///
80 /// The build cost is not lost: it is the `build tree` column, which is the same
81 /// number for all three renderers.
82 pub fn frame(ctx: &Context, immediate: &Immediate, screen: &Screen, view: &mut View) {
83 let input = RawInput {
84 screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(WINDOW.0, WINDOW.1))),
85 ..Default::default()
86 };
87 let output = ctx.run_ui(input, |ctx| {
88 egui::CentralPanel::default().show(ctx, |ui| {
89 immediate.screen(ui, screen, view);
90 });
91 });
92 // Consumed so the tessellation is not dead-code-eliminated, and dropped
93 // rather than returned: a host uploads these to the GPU, which is not this
94 // crate's cost and not measurable without one.
95 std::hint::black_box(&output.shapes);
96 }
97
98 /// The shipped `goingson` theme, as an immediate-mode palette.
99 ///
100 /// Every one of the sixteen fields comes from a resolved token. `overlay` and
101 /// `elevation` are the two intents makeover emits as `rgba(...)` rather than
102 /// hex, because they are scrims, so they are read through `rgba` and the rest
103 /// through `rgb`. Reading a scrim as an opaque colour is how a translucent
104 /// token becomes a near-black rectangle over the page.
105 fn palette() -> Palette {
106 let dir = makeover::bundled_themes_dir().expect("makeover ships themes");
107 let colours = makeover::load_theme(&[(dir, false)], "goingson").expect("a bundled theme loads");
108 let tokens = makeover::resolve(&colours);
109
110 let opaque = |key: &str| {
111 let (r, g, b) = tokens
112 .rgb(key)
113 .unwrap_or_else(|| panic!("the resolved theme carries `{key}`"));
114 Color32::from_rgb(r, g, b)
115 };
116 let scrim = |key: &str| {
117 let (r, g, b, a) = tokens
118 .rgba(key)
119 .unwrap_or_else(|| panic!("the resolved theme carries `{key}`"));
120 Color32::from_rgba_unmultiplied(r, g, b, a)
121 };
122
123 Palette {
124 page: opaque("surface-page"),
125 raised: opaque("surface-raised"),
126 overlay: scrim("overlay"),
127 well: opaque("surface-well"),
128 sunken: opaque("surface-sunken"),
129 bevel_light: opaque("bevel-light"),
130 bevel_dark: opaque("bevel-dark"),
131 elevation: scrim("elevation"),
132 content: opaque("content"),
133 content_secondary: opaque("content-secondary"),
134 content_muted: opaque("content-muted"),
135 action: opaque("action"),
136 danger: opaque("danger"),
137 success: opaque("success"),
138 warning: opaque("warning"),
139 info: opaque("info"),
140 }
141 }
142