feat(render): logical style metrics with a UI scale, so chrome has a consistent physical size

This commit is contained in:
Martino Ferrari committed 2026-09-28 12:17:53 +02:00
1 parent 1f03631e98
commit 7500fc90d2
21 files changed
+708 -82

No files matched your search

+22 -10
View File
@@ -91,13 +91,20 @@ class Renderer {
/// Draws `plot`, refreshing its layout, ticks and grid first.
///
/// The renderer owns the presentation, style and text both: `draw()` copies
/// `style()` into the plot and installs its own font backend on it, so the strips
/// `style()` into the plot, hands it the style's `ui_scale` *before* it lays out
/// (`Plot::set_ui_scale()`) and installs its own font backend on it, so the strips
/// and tick densities the plot lays out with are the values the renderer draws
/// with -- set colours and metrics here, not on the plot, and expect a backend
/// set with `Plot::set_text_backend()` to be replaced on the first frame (a
/// debug build names the one it dropped). The renderer must therefore outlive the
/// plot's use of it.
///
/// Every metric in `style()` is a logical unit, so this is also where the scale
/// becomes pixels: the font is reset at `style().device(axis_font_px)` and each
/// stroke and padding is converted through the same call. A host therefore states
/// its display once, in `style().ui_scale`, and the frame it gets back is the same
/// physical size on a 1x panel and a 2x one.
///
/// `Plot::layout()` is computed exactly once, here, and handed to every helper:
/// it runs the auto scale, the strip fixed-point loop and the grid build, so
/// re-deriving it per helper would rebuild the ticks and the grid per helper.
@@ -136,10 +143,13 @@ class Renderer {
/// pixel-pack alignment if `dump_frame_ppm()` ran.
void end_frame() noexcept;
/// The frame's colours and metrics; `draw()` hands them to the plot as well, so
/// the layout it computes matches what is drawn. `line_width_px` is the width of
/// the strokes framing the data (axis lines, cursor markers) -- grid lines stay
/// hairlines and every trace carries its own width.
/// The frame's colours and metrics; `draw()` hands them to the plot as well --
/// style and `ui_scale` both -- so the layout it computes matches what is drawn.
/// Every metric here is a logical unit (`Style`'s unit rule): the host writes
/// `ui_scale` from the display it is on and this converts each metric with
/// `device()`. `line_width_px` is the width of the strokes framing the data (axis
/// lines, cursor markers) -- grid lines stay hairlines and every trace carries its
/// own width.
render::Style& style() noexcept { return style_; }
const render::Style& style() const noexcept { return style_; }
@@ -162,11 +172,13 @@ class Renderer {
bool using_persistent_upload() const noexcept {
return line_vao_.persistent() && text_vao_.persistent();
}
/// The axis text size the renderer actually draws with: the on-screen cell
/// height of the backend it draws with. `Style::axis_font_px` is an em size, so
/// with an ascent-to-descent cell it is this that a host reads back as the line
/// height it got -- and it is the backend's own `height()` scaled, so it always
/// agrees with the metrics the plot laid the labels out with.
/// The axis text size the renderer actually draws with, in **device** pixels: the
/// on-screen cell height of the backend it draws with, which is
/// `style().device(Style::axis_font_px)`'s own cell rather than the logical request
/// itself. `Style::axis_font_px` is an em size, so with an ascent-to-descent cell
/// it is this that a host reads back as the line height it got -- and it is the
/// backend's own `height()` scaled, so it always agrees with the metrics the plot
/// laid the labels out with.
f32 axis_font_px() const noexcept { return font_backend_.cell_px(); }
/// Writes the framebuffer's viewport region as a binary PPM (P6, 8 bits per