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
@@ -90,11 +90,13 @@ class Renderer {
|
||||
|
||||
/// Draws `plot`, refreshing its layout, ticks and grid first.
|
||||
///
|
||||
/// The renderer owns the presentation style: `draw()` copies `style()` into the
|
||||
/// plot, 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.
|
||||
/// The plot also borrows this renderer's font (when it has no backend of its
|
||||
/// own), so the renderer must outlive the plot's use of it.
|
||||
/// 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
|
||||
/// 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.
|
||||
///
|
||||
/// `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
|
||||
@@ -112,6 +114,13 @@ class Renderer {
|
||||
/// vertex array 0, program 0, `GL_ARRAY_BUFFER` 0, texture unit 0 active and
|
||||
/// nothing bound to `GL_TEXTURE_2D` on it, blending off, scissoring off, and the
|
||||
/// viewport left at the frame's rect (the caller sets its own for the next pass).
|
||||
/// Two pieces of global state the frame *changed* are handed back rather than
|
||||
/// left where the frame wanted them: `GL_UNPACK_ALIGNMENT`, which `init()` set
|
||||
/// to 1 for the glyph atlas' single-channel rows, and the clear colour, which is
|
||||
/// `style().background` only for the duration of the frame. GL keeps no history
|
||||
/// for the clear colour, so the host's value is queried on the first
|
||||
/// `begin_frame()`; that first value is what comes back here, so a colour the
|
||||
/// host changes after the first frame is restored over by it.
|
||||
/// The framebuffer binding is never touched -- it is the caller's, and so is the
|
||||
/// pixel-pack alignment if `dump_frame_ppm()` ran.
|
||||
void end_frame() noexcept;
|
||||
@@ -152,7 +161,13 @@ class Renderer {
|
||||
/// window. Reads the framebuffer that is bound now, which is the one the frame
|
||||
/// was drawn into. Returns false, with `st` filled in, on a bad argument or an
|
||||
/// unreadable pixel.
|
||||
bool dump_frame_ppm(const char* path, Status* st = nullptr) const noexcept;
|
||||
///
|
||||
/// The one entry point here that is not `noexcept`: it sizes its readback hole
|
||||
/// and its output row, so an allocation failure propagates `std::bad_alloc`
|
||||
/// rather than terminating a process that could have carried on without a
|
||||
/// screenshot. A dump is a diagnostic, and no `Status` code could say more than
|
||||
/// the exception already does.
|
||||
bool dump_frame_ppm(const char* path, Status* st = nullptr) const;
|
||||
|
||||
private:
|
||||
/// The bitmap font as the plot layer sees it, at the size the text pipeline
|
||||
@@ -250,6 +265,15 @@ class Renderer {
|
||||
std::vector<f32> gather_; ///< scratch for one gathered window, re-used
|
||||
mutable std::vector<u8> readback_; ///< dump_frame_ppm's row buffer
|
||||
|
||||
/// The host's `GL_UNPACK_ALIGNMENT`, saved by `init()` before the atlas upload
|
||||
/// set it to 1 and handed back by `end_frame()`. GL's default is 4.
|
||||
gl::GLint host_unpack_alignment_ = 4;
|
||||
/// The host's `GL_COLOR_CLEAR_VALUE`, queried on the first `begin_frame()` (GL
|
||||
/// has no other way to recover it once the frame has set it) and handed back by
|
||||
/// `end_frame()`.
|
||||
Color host_clear_{};
|
||||
bool host_clear_saved_ = false;
|
||||
|
||||
Rect viewport_; ///< the frame's region, top-left origin, framebuffer px
|
||||
Rect gl_viewport_; ///< the same rect flipped for GL, and for readback
|
||||
u32 fb_w_ = 0;
|
||||
|
||||
@@ -17,8 +17,12 @@ struct Style {
|
||||
f32 axis_font_px = 12.0f;
|
||||
f32 grid_label_pad = 4.0f;
|
||||
f32 line_width_px = 1.5f;
|
||||
f32 dense_threshold_spp = 1.5f; ///< spec §4.3: samples per pixel above which
|
||||
///< a trace is drawn from the LOD pyramid.
|
||||
f32 dense_threshold_spp = 1.5f; ///< spec §4.3: samples per pixel at or above
|
||||
///< which a trace is gathered from the LOD
|
||||
///< pyramid instead of from its raw samples.
|
||||
///< It selects the gather level, never a
|
||||
///< primitive: both sides draw through the one
|
||||
///< instanced anti-aliased quad path.
|
||||
f32 tick_min_px = 60.0f; ///< minimum spacing between major ticks.
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user