trigger: a phosphor persistence, driven by the plotter's density image
The trigger pane gains the display the user asked for and the library now provides, and the
persistence it already had is renamed to what it is.
**Persistence is one image; the last-N-traces display is "multiple events".** Both live in a new
`Advanced` expander in the trigger panel, together with the settings that are set up once and left
alone. `persistence` (a switch) turns on the plotter's `Plot::set_density()` for every row:
every triggered acquisition is accumulated into a screen-sized intensity grid drawn behind the
traces, brightness being how many acquisitions covered a cell -- no depth to choose, no memory
that grows with the acquisitions (2.2 MB over four 573x412 rows in the probe's run). `fades by`
picks the fade the user asked for: **acquisitions** (each keeps a factor of what was before it,
0.5 by default -- the phosphor afterglow) or **time** (a time constant in stream seconds, so the
display fades while you watch whatever the trigger rate).
- **Trigger mode only, as asked.** `apply_density()` (the tick) states the display on for `Held`
and `Capturing` and off for `Live`, `Waiting`, `Paused` and a file, and turning it off drops the
grid at once rather than at an acquisition a quiet trigger never sends. It is unpickable and
reserves no layout space, exactly as the invalid-span bands are: a density can never be in the
way of a cursor or a drop, and it cannot move the framing.
- **One deposit per acquisition, one frame after the frame that frames it.** `Plot::accumulate_density()`
deposits what the plot holds through the transforms of its *last* layout -- the frame's own is
computed inside the paint that follows the tick -- so `density_pending` is set beside
`record_pending` and consumed by the next tick. In the same tick it was right on a Held-to-Held
transition and wrong on the first latch, whose axis was still the live ring's. Switching the
display on under an acquisition already on screen deposits it too, so the switch does not appear
to do nothing until the next latch (`single` mode may never send one).
- **The session carries it**: `trigger_density`, `trigger_density_decay` ("acquisitions"/"time",
the words the panel shows), `trigger_density_tau` and `trigger_density_factor`, written whether
or not it is on -- and `trigger_persistence` still means the multiple-event depth, so an older
file is unchanged.
- **The library changed twice under this**: the deposit now counts a cell **once per trace per
sweep** (counting the hits put one dense sweep at the ceiling of the brightness scale and hid the
sweeps behind it -- the probe's `on` leg read `peak=18` for a single sweep and reads `peak=1`
now), and a frame's upload is every row's rather than the last row's. Both are in the plotter's
own commit.
`UDPSCOPE_PROBE_DENSITY_MS` reads it all back off the plots -- the plotter's own `ink()`/`peak()` --
with the panel's own widgets driving the switch and the fade, in four legs: **off** (`rows=0
ink=0.0 peak=0.0000`), **on** (`rows=4`, `peak=1.0000`), **step-acquisitions** (`peak=1.0000 ->
1.5000`, the factor then the new sweep) and **step-time** (`ratio=0.367879`, `1/e` for `dt = tau`).
It also reads the frame cost as an A/B **inside one run** (the accumulators at the switch against
the frames after it): `tick 0.74 -> 9.99 ms`, `draw 2.09 -> 20.91 ms` at four rows and 2.2 M cells
on llvmpipe.
Acceptance: clean build 0 errors and exactly the three documented warnings (12352-byte GResource
blob, two `MathNode::source`), `test-ui` 13/13, `test-help` green, a plain run with **0
Gtk-CRITICAL / Gtk-WARNING** and `gtk warnings: 0`, and the panel's own frame reading
`boxes=16 controls_inside=15/15 advanced_widgets=5/5 advanced_inside=5/5` (the block's controls
counted from the expander's child, which is not in the widget-parent chain -- and the search
block's walk now names *its* expander instead of "the first one", which the second expander would
have stolen).
Docs: the trigger/pinning invariants carry the rule and its five consequences, `probe-readings.md`
the four legs and the dump line, `verification.md` the probe's sequence and assertions, and the
manual's trigger page explains both displays to a user.
This commit is contained in:
1 parent
e14e1ee069
commit
974d6aa2d9
15 files changed
+993
-41
No files matched your search
@@ -256,6 +256,66 @@ struct App {
|
||||
std::size_t dbg_persist_last_kept = 0;
|
||||
int dbg_persist_stable_polls = 0;
|
||||
bool dbg_persist_settled = false;
|
||||
/// The phosphor density's controls -- the trigger pane's *Advanced* block: the switch, the
|
||||
/// dropdown that chooses what the image fades by, and the two parameter spins, only one of which
|
||||
/// is on screen at a time (`sync_trigger_rows()` states that, as it does for the condition rows).
|
||||
/// The rows and the expander are held too, so the probe can read what the `.ui` holds and the dump
|
||||
/// can walk into the block -- an expander's child is *not* in the widget-parent chain.
|
||||
GtkWidget* trigger_density_switch = nullptr;
|
||||
GtkWidget* trigger_density_decay_drop = nullptr;
|
||||
GtkWidget* trigger_density_tau_spin = nullptr;
|
||||
GtkWidget* trigger_density_factor_spin = nullptr;
|
||||
GtkWidget* trigger_density_row = nullptr;
|
||||
GtkWidget* trigger_density_decay_row = nullptr;
|
||||
GtkWidget* trigger_density_tau_row = nullptr;
|
||||
GtkWidget* trigger_density_factor_row = nullptr;
|
||||
GtkWidget* trigger_advanced = nullptr;
|
||||
/// A new acquisition has reached the screen and has not been deposited into the density grid yet.
|
||||
/// Set where a snapshot is *framed* -- the held-event branch of the tick -- and when the display
|
||||
/// is switched on under an acquisition already on screen, and consumed by the next tick's
|
||||
/// `apply_density()`: the deposit needs the plot laid out for the acquisition it deposits, and
|
||||
/// that layout is the frame's, one frame after the bind.
|
||||
bool density_pending = false;
|
||||
/// The previous tick's clock (`g_get_monotonic_time()`), for the density's per-frame fade; 0
|
||||
/// before the first frame, which is what makes the first frame's `dt` zero rather than since-boot.
|
||||
gint64 density_last_us = 0;
|
||||
/// Whether the density was on the last time the tick looked, so switching it on under an
|
||||
/// acquisition already on screen deposits that one at once instead of waiting for a latch that
|
||||
/// `single` mode may never send.
|
||||
bool density_was_on = false;
|
||||
/// What the last tick left on screen, for the dump: the rows the density was on, the total ink
|
||||
/// and the peak over them, and the bytes the last *frame* uploaded. Captured on the tick because
|
||||
/// the dump runs after the window is gone.
|
||||
/// The **last tick the density was on**, captured there and not read back from the plots at
|
||||
/// exit: the dump runs after the window is gone and after the exit probe has switched the trigger
|
||||
/// off, which turns the display off with it -- a dump that asked the plots would say `rows=0`
|
||||
/// about a run whose whole tail had the image up.
|
||||
int dbg_density_rows = 0;
|
||||
unsigned long long dbg_density_cells = 0;
|
||||
unsigned long long dbg_density_grid_bytes = 0;
|
||||
double dbg_density_ink = 0.0;
|
||||
double dbg_density_peak = 0.0;
|
||||
/// The most any one frame **uploaded** of the grid: one byte a cell, so this is the resolution
|
||||
/// read back as the frame's own cost. A maximum rather than the last value, because the exit
|
||||
/// probe changes the display before the dump runs and the last painted frame is then not a frame
|
||||
/// of this display.
|
||||
long long dbg_density_upload_bytes = 0;
|
||||
/// How many ticks it was on for: 0 is "no image was ever drawn", which is what separates a real
|
||||
/// reading from a default.
|
||||
int dbg_density_frames = 0;
|
||||
/// The frame-cost accumulators as of the tick the probe switched the display on, so the cost can
|
||||
/// be read **before and after** it inside one run -- the same stream, the same layout, the same
|
||||
/// mock. Two runs would differ in everything but the density.
|
||||
double dbg_density_tick_sum0 = 0.0;
|
||||
int dbg_density_tick_n0 = 0;
|
||||
double dbg_density_draw_sum0 = 0.0;
|
||||
int dbg_density_draw_n0 = 0;
|
||||
/// The density probe's own state: the time constant its second half uses, and how many polls it
|
||||
/// has spent waiting for an event to be on screen.
|
||||
double dbg_density_tau = 1.0;
|
||||
int dbg_density_polls = 0;
|
||||
/// The density probe's step, so its sequence is a state machine rather than nested timers.
|
||||
int dbg_density_stage = 0;
|
||||
GtkWidget* signal_list = nullptr;
|
||||
GtkWidget* pause_button = nullptr;
|
||||
/// When the pause began, on the **monotonic wall clock**, or 0 while running. The elapsed time
|
||||
@@ -1817,6 +1877,26 @@ void persist_probe_report(App& a, const char* what);
|
||||
void persist_probe_set_depth(App& a, int depth);
|
||||
gboolean persist_probe_wait(gpointer user);
|
||||
gboolean persist_probe(gpointer user);
|
||||
|
||||
/// The phosphor density's one step for a frame: state every row's `plot::Plot` with the
|
||||
/// configuration the trigger holds, advance its fade by the frame's own `dt`, and deposit the
|
||||
/// acquisition on screen if one has arrived since the last step. `display` and `live_display` are
|
||||
/// passed in rather than re-read, so the step, the bind and the status line all describe one frame.
|
||||
///
|
||||
/// The density is on while an acquisition is on screen -- `Held`, and `Capturing` as it fills --
|
||||
/// and off on a live view, a paused one, or a file: the display the user asked for is a *trigger*
|
||||
/// display. Off is not "paused": switching it off (and leaving trigger mode, and opening a file)
|
||||
/// drops the image at once, so a fresh presentation starts empty rather than showing a stale image
|
||||
/// at another scale.
|
||||
void apply_density(App& a, DisplayMode display, bool live_display);
|
||||
|
||||
/// The density probe (`UDPSCOPE_PROBE_DENSITY_MS`): the display's own numbers read back off the
|
||||
/// plots -- total ink and peak with it off, on, and after one decay step of each kind.
|
||||
void density_probe_report(App& a, const char* what);
|
||||
/// States the switch and/or the fade through the panel's own widgets, the way a user would.
|
||||
void density_probe_set(App& a, bool on, bool by_time);
|
||||
gboolean density_probe(gpointer user);
|
||||
gboolean density_probe_step(gpointer user);
|
||||
void force_probe_report(App& a, const char* leg);
|
||||
gboolean force_probe_press(gpointer user);
|
||||
gboolean force_probe_poll(gpointer user);
|
||||
|
||||
Reference in new issue
Block a user