fix(plot,render,gl): live streaming measurements, handed-back GL state
Final whole-branch review fixes. 1 (Important). Plot::measurement() cached on (series pointer, sample count) only, so a saturated StreamSeries -- whose size() stays pinned while x0() advances every acquire() -- froze its readout on the first saturated frame until a cursor moved. The cache key now carries the series' x0() (new MeasureSlot::last_x0), so a sliding window recomputes and a static series still does not: asserted with a new macro-gated counter hook (plot/detail/measure_count.hpp, PLOT_COUNT_MEASURE_RECOMPUTES, wired like the pyramid and ring counters, absent from the release library). New case saturated_stream_measurement_tracks_the_sliding_window: saturated ring, stationary cursors, ramp in absolute x -- zero recomputes for repeated reads with nothing moving, one per acquire while it slides. 2-3, 9. Spec: 4.3 described a GL_LINE_STRIP path that does not exist (there is one rasteriser -- instanced anti-aliased quads -- and dense_threshold_spp selects the gather level, not a primitive); 8 named a PLOT_GL_CHECK() macro that exists nowhere, and stated no exception policy. The table, the field's comment and 8 now describe the real gather-level threshold, the real glGetError() drain plus the stores' drop-to-orphan fallbacks, and the real policy: library failures return Status/bool, allocating entry points propagate std::bad_alloc. 4. The frame handed back neither GL_UNPACK_ALIGNMENT (init() sets 1 for the atlas) nor the clear colour (queried once with a new glGetFloatv entry). end_frame() restores both and renderer.hpp lists them in the residue contract. 5. draw() kept a host TextBackend for layout while drawing with its own font. The renderer now installs its font unconditionally -- strips and labels measure and draw with one backend -- with a debug-build diagnostic if a different one was set, and Plot::set_text_backend() is documented as the no-renderer path. New case renderer_measures_the_labels_it_draws asserts layout, measuring and the drawn label's ink agree. 6-10. draw()'s style overwrite stated at the call site; the load-sensitive wall-clock bound in test_measure.cpp replaced by a counter assertion (same interval, same fold count over a 512x length ratio); dump_frame_ppm drops noexcept and says why it may allocate; error.txt ignored (not this project's, not generated here) rather than deleted. Verified: debug and release build clean (-Wall -Wextra -Wpedantic -Wconversion -Wshadow), 4/4 ctest entries pass, 130 cases pass (was 127), umbrella and header self-containment checks pass, and no counter symbol in the release archive.
This commit is contained in:
1 parent
f363523195
commit
a08b184481
15 files changed
+478
-54
No files matched your search
+31
-13
@@ -68,17 +68,28 @@ class Plot {
|
||||
void set_viewport(Rect px) noexcept { viewport_ = px; }
|
||||
const Rect& viewport() const noexcept { return viewport_; }
|
||||
|
||||
/// Set from the renderer, which owns presentation: `Renderer::draw()` copies
|
||||
/// its own `style()` in here every frame, so a style set on the plot is what a
|
||||
/// plot drawn *without* a renderer lays out with -- a frame drawn by one always
|
||||
/// uses the renderer's values.
|
||||
void set_style(const render::Style& s) noexcept { style_ = s; }
|
||||
const render::Style& style() const noexcept { return style_; }
|
||||
|
||||
/// The font layout() measures tick labels with. Borrowed, not owned, and may
|
||||
/// be null -- then the label areas fall back to PlotLayout's defaults and no
|
||||
/// string is measured. The caller (the renderer) owns the backend; Plot never
|
||||
/// creates one and never touches GL.
|
||||
/// The font layout() measures tick labels with. Borrowed, not owned, and may be
|
||||
/// null -- then the label areas fall back to PlotLayout's defaults and no string
|
||||
/// is measured: a plot with no renderer lays out with whatever the host sets
|
||||
/// here, which is for consumers that draw the plot themselves.
|
||||
///
|
||||
/// A plot drawn by a `Renderer` is the resolution the other way round: the
|
||||
/// renderer draws the labels with its own bitmap font, so it installs that font
|
||||
/// here on every `draw()` and the strips are measured with the metrics the
|
||||
/// labels are actually drawn with. A backend installed before the first draw is
|
||||
/// therefore replaced, with a debug-build diagnostic naming the mismatch.
|
||||
void set_text_backend(const render::TextBackend* text) noexcept { text_ = text; }
|
||||
|
||||
/// The backend layout() measures labels with, or null when none is set. The
|
||||
/// renderer reads it to install its own font only when nobody else has.
|
||||
/// The backend layout() measures labels with, or null when none is set. A
|
||||
/// renderer replaces whatever it finds here with its own font (see
|
||||
/// set_text_backend()); a plot nobody draws keeps it.
|
||||
const render::TextBackend* text_backend() const noexcept { return text_; }
|
||||
|
||||
// --- construction -----------------------------------------------------
|
||||
@@ -157,10 +168,13 @@ class Plot {
|
||||
void set_cursor_snap(u32 id, SnapMode mode);
|
||||
|
||||
/// The measurement for `id`. Re-evaluated only when a cursor moved, when the
|
||||
/// measure was added, or when its series changed (its pointer or its sample
|
||||
/// count) -- never once per frame. An unknown id, a null-series trace, an
|
||||
/// inverted cursor pair or an empty series yields `valid == false`. const
|
||||
/// because recomputing fills a cache: nothing a caller can observe changes.
|
||||
/// measure was added, or when the series underneath it changed: a different
|
||||
/// series, a longer one, or a sliding window whose sample count stayed the same
|
||||
/// (`x0()` is the third key -- see MeasureSlot). Never once per frame. A
|
||||
/// saturated stream therefore keeps reporting live values with a stationary
|
||||
/// cursor pair. An unknown id, a null-series trace, an inverted cursor pair or
|
||||
/// an empty series yields `valid == false`. const because recomputing fills a
|
||||
/// cache: nothing a caller can observe changes.
|
||||
const Measurement& measurement(u32 id) const;
|
||||
|
||||
/// The cursor pair `id` was created over. The renderer needs it to place the
|
||||
@@ -203,9 +217,12 @@ class Plot {
|
||||
const Grid& grid() const noexcept { return grid_; }
|
||||
|
||||
private:
|
||||
/// One measure plus its cache. The dirty flag is what keeps measurement()
|
||||
/// from re-evaluating every frame; `last_series`/`last_size` catch a trace
|
||||
/// being pointed at a different (or grown) series without anyone telling us.
|
||||
/// One measure plus its cache. The dirty flag is what keeps measurement() from
|
||||
/// re-evaluating every frame; `last_series`, `last_size` and `last_x0` catch a
|
||||
/// trace being pointed at a different series, a series that grew, and a ring
|
||||
/// whose window slid under a constant sample count -- the last of which neither
|
||||
/// of the other two sees, since a saturated stream keeps its size while every
|
||||
/// sample it holds changes.
|
||||
struct MeasureSlot {
|
||||
u32 cursor_a = 0;
|
||||
u32 cursor_b = 0;
|
||||
@@ -214,6 +231,7 @@ class Plot {
|
||||
mutable bool dirty = true;
|
||||
mutable const data::Series* last_series = nullptr;
|
||||
mutable u64 last_size = 0;
|
||||
mutable f64 last_x0 = 0.0;
|
||||
};
|
||||
|
||||
/// Fit one axis to its traces' bounds, leaving auto_scale alone. False when the
|
||||
|
||||
Reference in new issue
Block a user