Feature/source plugins #1
No files matched your search
@@ -1269,14 +1269,21 @@ pinned: the axis, the framing and every measurement still describe the event on
|
|||||||
`N = 1` retains nothing at all — no copy, no layer — and lowering the depth frees the retained
|
`N = 1` retains nothing at all — no copy, no layer — and lowering the depth frees the retained
|
||||||
snapshots there and then, because a trigger that has stopped firing never reaches another publish.
|
snapshots there and then, because a trigger that has stopped firing never reaches another publish.
|
||||||
|
|
||||||
**Persistence: one image, every acquisition.** The trigger panel's `Advanced` block carries the
|
**Persistence: one image per signal, every acquisition.** The trigger panel's `Advanced` block
|
||||||
*other* display of the same acquisitions, and it is not a count at all: `persistence` turns on a
|
carries the *other* display of the same acquisitions, and it is not a count at all: `persistence`
|
||||||
**phosphor density** — the plotter's screen-sized intensity image (`Plot::set_density()`, one `f32`
|
turns on a **phosphor density** — the plotter's screen-sized intensity image (`Plot::set_density()`,
|
||||||
cell per device pixel of the axes rect) into which every triggered acquisition is deposited and
|
one `f32` cell per device pixel of the axes rect **per trace, in that trace's own signal colour**)
|
||||||
which is drawn behind the traces. A cell's brightness is how many acquisitions passed through it,
|
into which every triggered acquisition is deposited, the newest included, and which is drawn *in
|
||||||
so the sweep on screen is the brightest, the older ones add on top **with no depth to choose and no
|
place of* the traces. The current acquisition is therefore a sweep *inside* the image and fades with
|
||||||
memory that grows with them** — the image is 2.2 MB over four 573x412 rows in the probe's run, and
|
the rest; no solid polyline is drawn over it, which is what a report of "the persistent image plus
|
||||||
a thousand acquisitions cost the same. `fades by` picks how it decays: `acquisitions` (each
|
the last acquisition, and the last one never fades in time mode" turned out to be. A cell's
|
||||||
|
brightness is how many acquisitions passed through it — one sweep's ink is the trace's own colour,
|
||||||
|
and where several agree it clamps there rather than going white — so the display brightens where the
|
||||||
|
signal is steady and dims where it only passed, **with no depth to choose and no memory that grows
|
||||||
|
with the acquisitions**: the cost is the screen's times the traces on it (2.2 MB over the probe's
|
||||||
|
four one-signal rows; the default four signals in one 800x480 row are 6 MB) and a thousand
|
||||||
|
acquisitions cost the same. Each signal is a separate image, so two signals agreeing in a cell read
|
||||||
|
as two colours and not one blended grey. `fades by` picks how it decays: `acquisitions` (each
|
||||||
acquisition keeps the factor of what was before it, 0.5 by default — the phosphor afterglow
|
acquisition keeps the factor of what was before it, 0.5 by default — the phosphor afterglow
|
||||||
schedule) or `time` (a time constant in stream seconds, so the display fades while you watch
|
schedule) or `time` (a time constant in stream seconds, so the display fades while you watch
|
||||||
whatever the trigger rate). It applies in **trigger mode only** — while a captured acquisition is
|
whatever the trigger rate). It applies in **trigger mode only** — while a captured acquisition is
|
||||||
@@ -1285,7 +1292,8 @@ is an image *of* a stream of acquisitions. Like the invalid-span bands it reserv
|
|||||||
and nothing on it can be picked. The `[density-probe]` run that measured it is in
|
and nothing on it can be picked. The `[density-probe]` run that measured it is in
|
||||||
`docs/claude/probe-readings.md`; what it costs is in the same place (`[density-probe] frame cost:`:
|
`docs/claude/probe-readings.md`; what it costs is in the same place (`[density-probe] frame cost:`:
|
||||||
tick 0.74 → 9.99 ms and draw 2.09 → 20.91 ms over the same run, at four rows and 2.2 M cells a
|
tick 0.74 → 9.99 ms and draw 2.09 → 20.91 ms over the same run, at four rows and 2.2 M cells a
|
||||||
frame, on llvmpipe).
|
frame, on llvmpipe — measured before the per-trace split, when the one image was still re-filled
|
||||||
|
per row).
|
||||||
|
|
||||||
**And it says what it costs.** A retained acquisition is a whole window copied out of the ring, so
|
**And it says what it costs.** A retained acquisition is a whole window copied out of the ring, so
|
||||||
the retention is bounded by two things: the depth, and **`--buffer-mb`** — persistence may hold at
|
the retention is bounded by two things: the depth, and **`--buffer-mb`** — persistence may hold at
|
||||||
|
|||||||
Vendored
+1
-1
Submodule deps/plotter updated: 738205293e...1b8b55d3ee.
@@ -376,21 +376,26 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
trigger that has stopped firing never reaches another publish. A depth change is therefore also
|
trigger that has stopped firing never reaches another publish. A depth change is therefore also
|
||||||
how the memory comes back.
|
how the memory comes back.
|
||||||
|
|
||||||
- **The phosphor density is one *image*, and that is a different display from the layers above, not a
|
- **The phosphor density is the *trace*, and that is a different display from the layers above, not a
|
||||||
mode of them.** The trigger panel's `Advanced` block offers both, and the words matter: *multiple
|
mode of them.** The trigger panel's `Advanced` block offers both, and the words matter: *multiple
|
||||||
events* is `persist_depth` (N retained acquisitions, N traces, dimmer with age) while *persistence*
|
events* is `persist_depth` (N retained acquisitions, N traces, dimmer with age) while *persistence*
|
||||||
is `TriggerConfig::density` -- the plotter's `Plot::set_density()`, a screen-sized intensity grid
|
is `TriggerConfig::density` -- the plotter's `Plot::set_density()`, a screen-sized intensity grid
|
||||||
every acquisition is deposited into and which fades either by a time constant or by a
|
**per trace, drawn in that trace's own colour**, every acquisition deposited into it, which fades
|
||||||
per-acquisition factor. There is **no depth** on the density and no per-acquisition memory: the
|
either by a time constant or by a per-acquisition factor. There is **no depth** on the density and
|
||||||
image is `columns * rows * 4` bytes, one cell per device pixel of the row's axes (2.2 MB over the
|
no per-acquisition memory: the image is `traces * columns * rows * 4` bytes, one cell per device
|
||||||
four rows of the probe's run), so a thousand acquisitions cost what one costs. A cell's value is a
|
pixel of the row's axes for every trace the row draws (the app's default four signals in one row
|
||||||
number of *sweeps*: the library deposits a cell once per trace per sweep however many of that
|
are four grids -- 6 MB at 800x480; the probe's one-signal-per-row run is 2.2 MB over four rows), so
|
||||||
trace's own samples crossed it (`[density-probe] on` reads `peak=1.0000` for one sweep; a per-hit
|
a thousand acquisitions cost what one costs. A cell's value is a number of *sweeps*: the library
|
||||||
deposit read 18) -- dwell is `Style::intensity_grading`'s question, not this one's.
|
deposits a cell once per trace per sweep however many of that trace's own samples crossed it
|
||||||
Four things hold it together, each of which is a rule:
|
(`[density-probe] on` reads `peak=1.0000` for one sweep; a per-hit deposit read 18) -- dwell is
|
||||||
|
`Style::intensity_grading`'s question, not this one's. The colour is the trace's own
|
||||||
|
(`RowSet::signal_color()`, the same source the trace was built with and never a second one), so a
|
||||||
|
saturated cell is that signal's colour at full strength and two signals agreeing in a cell are two
|
||||||
|
quads of two colours rather than a blend.
|
||||||
|
Five things hold it together, each of which is a rule:
|
||||||
* **It is a trigger-mode display.** `apply_density()` (the tick) states `Held` and `Capturing` as
|
* **It is a trigger-mode display.** `apply_density()` (the tick) states `Held` and `Capturing` as
|
||||||
the states it is on in; `Live`, `Waiting`, `Paused` and a file are not, and turning it off drops
|
the states it is on in; `Live`, `Waiting`, `Paused` and a file are not, and turning it off drops
|
||||||
the grid **at once** rather than at an acquisition a quiet trigger never sends (the rule
|
the grids **at once** rather than at an acquisition a quiet trigger never sends (the rule
|
||||||
`set_persist_depth()` follows). So it goes away with the trigger, and a re-enable starts from an
|
`set_persist_depth()` follows). So it goes away with the trigger, and a re-enable starts from an
|
||||||
empty image rather than a stale one at another scale.
|
empty image rather than a stale one at another scale.
|
||||||
* **One deposit per acquisition, one frame *after* the frame that frames it.** `Plot::accumulate_density()`
|
* **One deposit per acquisition, one frame *after* the frame that frames it.** `Plot::accumulate_density()`
|
||||||
@@ -403,21 +408,26 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
|||||||
* **The switch deposits what is already on screen.** `on && !density_was_on` sets the same pending
|
* **The switch deposits what is already on screen.** `on && !density_was_on` sets the same pending
|
||||||
flag, so a user who turns persistence on while an event is held sees it instead of waiting for a
|
flag, so a user who turns persistence on while an event is held sees it instead of waiting for a
|
||||||
latch that `single` mode may never send.
|
latch that `single` mode may never send.
|
||||||
* **The brightness ceiling is the fade's steady state, and the ramp is linear in sweeps.** The app
|
* **No trace is drawn over it, and the newest acquisition is inside it.** `Renderer::draw_traces()`
|
||||||
states `DensityConfig::full_scale` from the fade it configured -- `1/(1 - factor)` for the
|
returns at once while a density is on: the trace ink *is* the image, so the acquisition just
|
||||||
acquisition fade, the library's default two sweeps for the time one, which has no fixed steady
|
deposited is a sweep in it and fades with the rest. Drawing the polyline too put a solid,
|
||||||
state (it settles where the rate and the constant meet) -- and leaves `gamma` at 1.0. A ceiling
|
unfading stroke over its own faint ghost -- the reported "persistent image + last acquisition",
|
||||||
at *one* sweep is the defect a user reported as "the colour fading is not applied, it looks
|
where the last acquisition never faded in time mode -- and the assertion is a **framebuffer** one:
|
||||||
white": a single sweep was already full ink, everything above it clamped to the same pixel, and
|
`a_phosphor_density_is_the_trace_in_its_own_colour` reads the same pixel before and after one
|
||||||
a display of one sweep and a display of five were the same white. It is also the one half that
|
time constant and requires it dimmer (`full=224 -> faded=115`, the trace's own `0xE0` green),
|
||||||
the *grid's* own numbers cannot see -- `ink`/`peak` were right while the picture was flat -- so
|
which a stroke over the density could not do.
|
||||||
the assertion is a **framebuffer** one: `a_phosphor_density_is_drawn_under_the_traces` measures a
|
* **The brightness ceiling is the trace's own brightness, and the ramp is linear in sweeps.** The
|
||||||
cell at a quarter of the scale against one at the ceiling and against the background
|
app leaves `DensityConfig::full_scale` at the library's default of **one sweep** and `gamma` at
|
||||||
(`empty=21 full=83 quarter=54`), which is the reading the eye asked for.
|
1.0, so the acquisition just deposited reads as the trace it is a picture of and a signal crossing
|
||||||
* **It is the plot's, so a figure rebuild resets it** -- the grid lives on each `Plot`, and
|
the same place every acquisition settles *at* the trace's colour (the ceiling clamps), rather than
|
||||||
|
a fraction of a brighter steady state. The old ceiling -- the fade's steady state, `1/(1-factor)`
|
||||||
|
for the acquisition fade -- is the gain offset a user reported between the latest acquisition and
|
||||||
|
the persistent image, and the "dimmer by design" headroom that made the ramp visible is reversed
|
||||||
|
here.
|
||||||
|
* **It is the plot's, so a figure rebuild resets it** -- the grids live on each `Plot`, and
|
||||||
`rebuild_all()` builds new ones. That is the one state a structural change (a row added, a signal
|
`rebuild_all()` builds new ones. That is the one state a structural change (a row added, a signal
|
||||||
switched off, a trace recoloured) does not carry: it costs the accumulated image and nothing else,
|
switched off, a trace recoloured) does not carry: it costs the accumulated image and nothing else,
|
||||||
and the alternative -- keeping the grid in the `RowSet` -- is what would make it not a plot
|
and the alternative -- keeping the grids in the `RowSet` -- is what would make it not a plot
|
||||||
feature. The layout is unaffected either way: the density reserves no space and nothing on it can
|
feature. The layout is unaffected either way: the density reserves no space and nothing on it can
|
||||||
be picked, exactly as `set_invalid_spans()` does.
|
be picked, exactly as `set_invalid_spans()` does.
|
||||||
* **The session carries it.** `trigger_density`, `trigger_density_decay`, `trigger_density_tau` and
|
* **The session carries it.** `trigger_density`, `trigger_density_decay`, `trigger_density_tau` and
|
||||||
|
|||||||
@@ -1980,53 +1980,61 @@ ring's bindings, and a reading taken there says nothing about either.
|
|||||||
|
|
||||||
```
|
```
|
||||||
[density-probe] arming on 'square', window 0.5 s, display off
|
[density-probe] arming on 'square', window 0.5 s, display off
|
||||||
[density-probe] off enabled=0 fade=acquisitions tau=1.000 factor=0.500 rows=0 cells=0 ink=0.0 peak=0.0000 ratio=1.000000
|
[density-probe] off enabled=0 fade=acquisitions tau=1.000 factor=0.500 rows=0 traces=0 cells=0 ink=0.0 peak=0.0000 ratio=1.000000
|
||||||
[density-probe] on enabled=1 fade=acquisitions tau=1.000 factor=0.500 rows=4 cells=2226150 ink=226596.0 peak=1.0000 ratio=1.000000
|
[density-probe] on enabled=1 fade=acquisitions tau=1.000 factor=0.500 rows=4 traces=4 cells=2226150 ink=226786.0 peak=1.0000 ratio=1.000000
|
||||||
[density-probe] step-acquisitions enabled=1 fade=acquisitions tau=1.000 factor=0.500 rows=4 cells=2226150 ink=262722.0 peak=1.5000 ratio=1.159429
|
[density-probe] step-acquisitions enabled=1 fade=acquisitions tau=1.000 factor=0.500 rows=4 traces=4 cells=2226150 ink=313971.0 peak=1.5000 ratio=1.384437
|
||||||
[density-probe] time-before enabled=1 fade=time tau=1.000 factor=0.500 rows=4 cells=2226150 ink=213362.2 peak=1.2182 ratio=1.000000
|
[density-probe] time-before enabled=1 fade=time tau=1.000 factor=0.500 rows=4 traces=4 cells=2226150 ink=458610.1 peak=2.1359 ratio=1.000000
|
||||||
[density-probe] step-time enabled=1 fade=time tau=1.000 factor=0.500 rows=4 cells=2226150 ink=78491.6 peak=0.4481 ratio=0.367879
|
[density-probe] step-time enabled=1 fade=time tau=1.000 factor=0.500 rows=4 traces=4 cells=2226150 ink=168713.2 peak=0.7858 ratio=0.367879
|
||||||
[density-probe] frame cost: tick before=0.74 ms (163 frames) after=9.99 ms (12 frames) draw before=2.09 ms (157) after=20.91 ms (7)
|
[density-probe] frame cost: tick before=0.62 ms (184 frames) after=6.96 ms (23 frames) draw before=2.02 ms (177) after=6.16 ms (18)
|
||||||
[udpscope] density: frames=504 rows=4 cells=2226150 grid_bytes=8904600 ink=632845.4 peak=2.8 upload_bytes=1081350
|
[udpscope] density: frames=748 rows=4 traces=4 cells=2226150 grid_bytes=8904600 ink=489400.2 peak=2.2 upload_bytes=2226150
|
||||||
```
|
```
|
||||||
|
|
||||||
The **phosphor density** (`Plot::set_density()`, the trigger pane's `persistence` switch): one
|
The **phosphor density** (`Plot::set_density()`, the trigger pane's `persistence` switch): one
|
||||||
screen-sized intensity image per row, every acquisition accumulated into it, drawn behind the traces and
|
screen-sized intensity image **per trace**, in that trace's own signal colour, every acquisition
|
||||||
fading by a count or by the clock. The numbers are the plotter's own -- `DensityGrid::ink()`/`peak()` summed
|
accumulated into it (the newest included), drawn *in place of* the traces and fading by a count or by
|
||||||
and maxed over the rows the display is on, which is the same source the library's tests read -- and the
|
the clock. The numbers are the plotter's own -- `DensityGrid::ink()`/`peak()` summed and maxed over the
|
||||||
|
traces the display is on, which is the same source the library's tests read -- and the
|
||||||
probe drives the panel's **own widgets** for the switch and the fade, so what it measures is this app's
|
probe drives the panel's **own widgets** for the switch and the fade, so what it measures is this app's
|
||||||
wiring.
|
wiring.
|
||||||
|
|
||||||
- **`off` is the display without it**: `rows=0`, so there is no grid at all and both numbers are zero.
|
- **`off` is the display without it**: `rows=0 traces=0`, so there is no grid at all and both numbers
|
||||||
That is also the run the frame cost is read against, and it is a plain run -- the density is off by
|
are zero. That is also the run the frame cost is read against, and it is a plain run -- the density
|
||||||
default and nothing draws it.
|
is off by default and nothing draws it.
|
||||||
- **`on` is one sweep**: `peak=1.0000` exactly, and `ink=226596` is the cells that sweep covered over
|
- **`on` is one sweep**: `traces=4` -- one grid per signal, four rows of one signal each -- and
|
||||||
four rows. A cell takes **1.0 once per trace per sweep** however many of the trace's own samples crossed
|
`peak=1.0000` exactly, with `ink=226786` the cells that sweep covered. A cell takes **1.0 once per
|
||||||
it, which is what makes a sweep's peak *one* rather than the eighteen a per-hit deposit read on the same
|
trace per sweep** however many of the trace's own samples crossed it, which is what makes a sweep's
|
||||||
run -- and what makes the sweeps behind it visible at all.
|
peak *one* rather than the eighteen a per-hit deposit read on the same run -- and what makes the
|
||||||
|
sweeps behind it visible at all.
|
||||||
- **`step-acquisitions`** deposits one more sweep: `peak=1.0000 -> 1.5000`, which is `1 + 0.5 x 1` -- the
|
- **`step-acquisitions`** deposits one more sweep: `peak=1.0000 -> 1.5000`, which is `1 + 0.5 x 1` -- the
|
||||||
panel's factor 0.5 applied to what was there, then the new sweep's own pass on top. `ink` rises from
|
panel's factor 0.5 applied to what was there, then the new sweep's own pass on top. The ratio is
|
||||||
`226596` to `262722` for the same reason: the old image halved and one sweep landed on it. The ratio is
|
|
||||||
**not** a constant to expect -- the acquisition on screen can move between the two reads, so what it says
|
**not** a constant to expect -- the acquisition on screen can move between the two reads, so what it says
|
||||||
is "the old image halved and a pass landed on it"; the exact relation is the *library's* case
|
is "the old image halved and a pass landed on it"; the exact relation is the *library's* case
|
||||||
(`a_density_halves_per_acquisition_and_keeps_the_newest_brightest`), where the same frame is deposited
|
(`a_density_halves_per_acquisition_and_keeps_the_newest_brightest`), where the same frame is deposited
|
||||||
twice.
|
twice.
|
||||||
- **`step-time` is `1/e` by construction**: one step of `dt = tau` through the plot's own `decay_density()`,
|
- **`step-time` is `1/e` by construction**: one step of `dt = tau` through the plot's own `decay_density()`,
|
||||||
so `ratio=0.367879` is `exp(-1)`. The leg before it (`time-before`) reads the same image under the time
|
so `ratio=0.367879` is `exp(-1)`. The leg before it (`time-before`) reads the same image under the time
|
||||||
fade, and the pair is the reading that says the *panel's* constant reaches the grid.
|
fade (`peak=2.1359`), and the pair is the reading that says the *panel's* constant reaches the grid.
|
||||||
- **`frame cost:` is the A/B inside one run**: the accumulators as of the tick the display was switched on
|
- **`frame cost:` is the A/B inside one run**: the accumulators as of the tick the display was switched on
|
||||||
against the frames after it, so both means are the same stream, the same layout and the same mock. At four
|
against the frames after it, so both means are the same stream, the same layout and the same mock. At four
|
||||||
rows of 573x412 cells it reads **tick 0.74 -> 9.99 ms** and **draw 2.09 -> 20.91 ms**: the per-frame
|
rows of 573x412 cells it reads **tick 0.62 -> 6.96 ms** and **draw 2.02 -> 6.16 ms**: the per-frame
|
||||||
conversion, upload and deposit. It is llvmpipe, and the arithmetic is the grid's -- 2.2 M cells, one byte
|
conversion, upload and deposit. It is llvmpipe, and the arithmetic is the grid's -- 2.2 M cells, one byte
|
||||||
each, every frame the time fade moves it.
|
each, every frame the time fade moves it. The *draw* mean is now below the run before the per-trace
|
||||||
|
split (2.02 -> 6.16 against 2.09 -> 20.91): the solid traces are no longer drawn over the image, so the
|
||||||
|
frame draws four quads where it used to draw four quads *and* every trace.
|
||||||
|
- **`cells`/`grid_bytes` are the per-signal memory figure**: 556 537 cells and 2.2 MB *per signal*, times
|
||||||
|
the four signals here (2 226 150 cells, 8 904 600 bytes). It is the axes rect's device pixels, so it is
|
||||||
|
the screen and the trace count and nothing the trigger has done -- and the app's default layout, four
|
||||||
|
signals in one row, is four grids for that one row.
|
||||||
- **The dump's `density:` line** is captured on the last tick the display was **on**, not read at exit: the
|
- **The dump's `density:` line** is captured on the last tick the display was **on**, not read at exit: the
|
||||||
exit probe switches the trigger off before it dumps, which takes the display with it, so a dump that asked
|
exit probe switches the trigger off before it dumps, which takes the display with it, so a dump that asked
|
||||||
the plots would say `rows=0` about a run whose tail had the image up. `upload_bytes` is the **most** any
|
the plots would say `rows=0` about a run whose tail had the image up. `upload_bytes` is the **most** any
|
||||||
one frame uploaded -- one byte a cell, over the rows that moved -- and not the last frame's, for the same
|
one frame uploaded -- one byte a cell **per trace**, over the grids that moved -- and not the last frame's,
|
||||||
reason.
|
for the same reason.
|
||||||
|
|
||||||
**Read `peak` as the display's ceiling**: with the default `full_scale` of 1.0 a cell at 1.0 is full ink, so
|
**Read `peak` as the display's ceiling**: with the default `full_scale` of 1.0 a cell at 1.0 is full ink
|
||||||
a steady display under the acquisition fade sits between 1 and 2, and a `peak` well above that (the
|
(the trace's own colour), so a steady display under the acquisition fade sits between 1 and 2, and a `peak`
|
||||||
per-hit deposit read 189 after two sweeps) is the defect this probe's `on` leg now catches.
|
well above that (the per-hit deposit read 189 after two sweeps) is the defect this probe's `on` leg now
|
||||||
|
catches.
|
||||||
|
|
||||||
**And `N = 1` is the display without the feature**, which is the same command at
|
**And `N = 1` is the display without the feature**, which is the same command at
|
||||||
`UDPSCOPE_PROBE_PERSIST_DEPTH=1`:
|
`UDPSCOPE_PROBE_PERSIST_DEPTH=1`:
|
||||||
|
|||||||
@@ -115,8 +115,9 @@ precedent, not an exception.
|
|||||||
schedule in use, oldest first — halves, and `1/2^age` at that. Both lines and the two exit-dump
|
schedule in use, oldest first — halves, and `1/2^age` at that. Both lines and the two exit-dump
|
||||||
lines are in [probe-readings.md](probe-readings.md).
|
lines are in [probe-readings.md](probe-readings.md).
|
||||||
- `UDPSCOPE_PROBE_DENSITY_MS` (+ `UDPSCOPE_PROBE_DENSITY_TAU`, default 1.0 s) — the **phosphor
|
- `UDPSCOPE_PROBE_DENSITY_MS` (+ `UDPSCOPE_PROBE_DENSITY_TAU`, default 1.0 s) — the **phosphor
|
||||||
density**: one screen-sized intensity image every triggered acquisition is accumulated into, drawn
|
density**: one screen-sized intensity image **per trace**, in that trace's own colour, every
|
||||||
behind the traces, fading by a per-acquisition factor or by a time constant. It states the display
|
triggered acquisition accumulated into it (the newest included), drawn *in place of* the traces,
|
||||||
|
fading by a per-acquisition factor or by a time constant. It states the display
|
||||||
**off** through the panel's own switch first, so the `off` leg is the run without it, then arms the
|
**off** through the panel's own switch first, so the `off` leg is the run without it, then arms the
|
||||||
trigger on the mock's `square` with one signal per row — the arrangement the persistence probe uses
|
trigger on the mock's `square` with one signal per row — the arrangement the persistence probe uses
|
||||||
— and only then switches it on, so the acquisition already on screen is the first image. Four legs,
|
— and only then switches it on, so the acquisition already on screen is the first image. Four legs,
|
||||||
@@ -131,8 +132,9 @@ precedent, not an exception.
|
|||||||
kill $PY
|
kill $PY
|
||||||
```
|
```
|
||||||
|
|
||||||
**The assertions, in order:** `off` reads `rows=0 cells=0 ink=0.0 peak=0.0000` (there is no grid at
|
**The assertions, in order:** `off` reads `rows=0 traces=0 cells=0 ink=0.0 peak=0.0000` (there is no
|
||||||
all); `on` reads `rows=4` — every row has its own image — and **`peak=1.0000`**, one sweep's ink
|
grid at all); `on` reads `rows=4` — every row has its own image, one per trace in its own colour,
|
||||||
|
so `traces=4` with one signal a row — and **`peak=1.0000`**, one sweep's ink
|
||||||
being one per cell it covers; `step-acquisitions` reads **`peak=1.5000`**, which is the panel's
|
being one per cell it covers; `step-acquisitions` reads **`peak=1.5000`**, which is the panel's
|
||||||
0.5 applied to the old image plus the new sweep's own pass; and `step-time` reads
|
0.5 applied to the old image plus the new sweep's own pass; and `step-time` reads
|
||||||
**`ratio=0.367879`**, one step of `dt = tau` through the plot's own `decay_density()`. A `peak` in
|
**`ratio=0.367879`**, one step of `dt = tau` through the plot's own `decay_density()`. A `peak` in
|
||||||
|
|||||||
@@ -123,16 +123,19 @@
|
|||||||
<section id="persistence">
|
<section id="persistence">
|
||||||
<title>Persistence: how often the trace has been here</title>
|
<title>Persistence: how often the trace has been here</title>
|
||||||
<p><gui>persistence</gui> — the switch in the trigger panel's <gui>Advanced</gui> block — keeps
|
<p><gui>persistence</gui> — the switch in the trigger panel's <gui>Advanced</gui> block — keeps
|
||||||
one <em>image</em> of the last acquisitions instead of their traces. Every acquisition is
|
an <em>image</em> of the last acquisitions instead of their traces. Every acquisition is
|
||||||
accumulated into it, so a cell's brightness means how many of them passed through it: the
|
accumulated into it, the one on screen included, so a cell's brightness means how many of them
|
||||||
sweep on screen is the brightest, the ones behind it add on top, and where several agree the
|
passed through it: the sweep on screen is the brightest, the ones behind it add on top, and
|
||||||
ink is brighter still. It is off by default.</p>
|
where several agree the ink is brighter still. There is no stroke drawn over the image — the
|
||||||
|
acquisition on screen <em>is</em> its newest ink — so it fades with the rest in both modes. Each
|
||||||
|
signal is kept as its own image in that signal's own colour, so where two signals cross the same
|
||||||
|
place you see both, not one blended line. It is off by default.</p>
|
||||||
|
|
||||||
<p>Two things make it different from <gui>multiple events</gui> beside it, and the panel offers
|
<p>Two things make it different from <gui>multiple events</gui> beside it, and the panel offers
|
||||||
both because they answer different questions. <em>It never runs out of room</em>: the image is
|
both because they answer different questions. <em>It never runs out of room</em>: each signal's
|
||||||
the size of the plot on screen, so a display of a thousand acquisitions costs what a display of
|
image is the size of the plot on screen, so a display of a thousand acquisitions costs what a
|
||||||
one costs, and there is no count to choose. <em>And it fades</em>, in one of two ways —
|
display of one costs — one image per signal, whatever the plot's trace slots — and there is no
|
||||||
<gui>fades by</gui> picks them:</p>
|
count to choose. <em>And it fades</em>, in one of two ways — <gui>fades by</gui> picks them:</p>
|
||||||
<list>
|
<list>
|
||||||
<item><p><gui>acquisitions</gui>: every acquisition keeps
|
<item><p><gui>acquisitions</gui>: every acquisition keeps
|
||||||
<gui>per acquisition</gui> of what was there before it (0.5, the default, is the schedule a
|
<gui>per acquisition</gui> of what was there before it (0.5, the default, is the schedule a
|
||||||
|
|||||||
+33
-25
@@ -3384,6 +3384,7 @@ void apply_density(App& app, DisplayMode display, bool live_display) {
|
|||||||
app.density_was_on = on;
|
app.density_was_on = on;
|
||||||
|
|
||||||
int rows_on = 0;
|
int rows_on = 0;
|
||||||
|
int traces_on = 0;
|
||||||
unsigned long long cells = 0;
|
unsigned long long cells = 0;
|
||||||
double ink = 0.0;
|
double ink = 0.0;
|
||||||
double peak = 0.0;
|
double peak = 0.0;
|
||||||
@@ -3396,18 +3397,14 @@ void apply_density(App& app, DisplayMode display, bool live_display) {
|
|||||||
: plot::DensityDecay::PerAcquisition;
|
: plot::DensityDecay::PerAcquisition;
|
||||||
cfg.time_constant_s = static_cast<float>(tc.density_tau_s);
|
cfg.time_constant_s = static_cast<float>(tc.density_tau_s);
|
||||||
cfg.per_acquisition = static_cast<float>(tc.density_factor);
|
cfg.per_acquisition = static_cast<float>(tc.density_factor);
|
||||||
// The brightness ceiling: the ink this fade settles at, so the newest acquisition is a
|
// The brightness ceiling is left at the library's default of **one sweep** -- the trace's own
|
||||||
// *fraction* of full ink and the display reaches white only where the acquisitions agree. For a
|
// brightness -- so the acquisition just deposited reads as the trace it is a picture of and a
|
||||||
// per-acquisition fade that is the geometric sum `1/(1-f)` exactly. A time fade has no fixed
|
// signal crossing the same place every acquisition settles at the trace's colour rather than
|
||||||
// one -- it settles where the trigger's rate and the constant meet, which is the point of it --
|
// brighter. The fade's steady state is deliberately *not* the ceiling: it put a gain offset
|
||||||
// so it keeps the library's default of two sweeps, and a fast trigger then reads brighter than
|
// between the newest sweep and the image behind it (the newest at a fraction of the settled
|
||||||
// a slow one the way a phosphor's does. The gamma is left at the library's 1.0: the cell is a
|
// brightness), which is what a user reported as a gain offset between the latest acquisition
|
||||||
// count of sweeps, so half as many sweeps must read as half the ink. (With the ceiling at one
|
// and the persistent image, and the "dimmer by design" headroom that made the ramp visible is
|
||||||
// sweep -- the library's first default -- one sweep was already full white and the fade was
|
// the wrong call at this brightness scale -- reversed here.
|
||||||
// invisible, which is exactly what a user reported seeing.)
|
|
||||||
cfg.full_scale = tc.density_decay == PersistDecay::Time
|
|
||||||
? 2.0f
|
|
||||||
: static_cast<float>(1.0 / std::max(1.0 - tc.density_factor, 0.05));
|
|
||||||
// The plot drops what it held when this turns the display off, so the step below only ever
|
// The plot drops what it held when this turns the display off, so the step below only ever
|
||||||
// speaks about an image that is on screen.
|
// speaks about an image that is on screen.
|
||||||
p->set_density(cfg);
|
p->set_density(cfg);
|
||||||
@@ -3415,16 +3412,26 @@ void apply_density(App& app, DisplayMode display, bool live_display) {
|
|||||||
++rows_on;
|
++rows_on;
|
||||||
// Time first, then the deposit: under `PerAcquisition` the decay is the accumulate path's own,
|
// Time first, then the deposit: under `PerAcquisition` the decay is the accumulate path's own,
|
||||||
// so this is a no-op there; under `TimeConstant` it is this frame's fade and the deposit lands
|
// so this is a no-op there; under `TimeConstant` it is this frame's fade and the deposit lands
|
||||||
// on top of it at full ink.
|
// on top of it at full ink. Either way the *newest* acquisition is inside the image, so it
|
||||||
|
// fades with the rest -- the renderer draws no solid trace over the density.
|
||||||
p->decay_density(dt);
|
p->decay_density(dt);
|
||||||
if (deposit) p->accumulate_density();
|
if (deposit) p->accumulate_density();
|
||||||
cells += static_cast<unsigned long long>(p->density_grid().cell_count());
|
// One grid per trace, each in its own signal's colour, so the row's figures are the **sum over
|
||||||
ink += p->density_grid().ink();
|
// its traces** and `cells` scales with the signal count as well as the row count. `peak` is the
|
||||||
peak = std::max(peak, static_cast<double>(p->density_grid().peak()));
|
// brightest of them, which is the reading that says whether any one trace is saturating.
|
||||||
|
for (plot::u32 t = 0; t < p->trace_count(); ++t) {
|
||||||
|
const plot::DensityGrid& g = p->density_grid(t);
|
||||||
|
if (g.cell_count() == 0u) continue; // an unbound slot holds no grid
|
||||||
|
++traces_on;
|
||||||
|
cells += static_cast<unsigned long long>(g.cell_count());
|
||||||
|
ink += g.ink();
|
||||||
|
peak = std::max(peak, static_cast<double>(g.peak()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
app.density_pending = false;
|
app.density_pending = false;
|
||||||
if (rows_on == 0) return; // nothing was on this frame: the dump's figures stay the last real ones
|
if (rows_on == 0) return; // nothing was on this frame: the dump's figures stay the last real ones
|
||||||
app.dbg_density_rows = rows_on;
|
app.dbg_density_rows = rows_on;
|
||||||
|
app.dbg_density_traces = traces_on;
|
||||||
app.dbg_density_cells = cells;
|
app.dbg_density_cells = cells;
|
||||||
app.dbg_density_grid_bytes = cells * sizeof(plot::f32);
|
app.dbg_density_grid_bytes = cells * sizeof(plot::f32);
|
||||||
app.dbg_density_ink = ink;
|
app.dbg_density_ink = ink;
|
||||||
@@ -6804,15 +6811,16 @@ int run_ui(int argc, char** argv, const Config& cfg, ScopeModel& model, Acquisit
|
|||||||
{
|
{
|
||||||
// The phosphor density, as of the last tick it was **on** (the exit probe switches the
|
// The phosphor density, as of the last tick it was **on** (the exit probe switches the
|
||||||
// trigger off before this runs, so the plots hold nothing by now -- see `App::dbg_density_*`).
|
// trigger off before this runs, so the plots hold nothing by now -- see `App::dbg_density_*`).
|
||||||
// `frames` is how many ticks drew an image, `cells`/`grid_bytes` its resolution and memory
|
// `frames` is how many ticks drew an image; `rows`/`traces` how many plots and how many of
|
||||||
// over the rows it was on, `ink`/`peak` the numbers the plot's own readers report, and
|
// their traces held a grid (one per trace, in the trace's own colour); `cells`/`grid_bytes`
|
||||||
// `upload_bytes` the most any one frame uploaded -- one byte a cell, on the frames whose grid
|
// its resolution and memory over them, `ink`/`peak` the numbers the plot's own readers
|
||||||
// moved; a frame whose grid did not move uploads nothing at all.
|
// report, and `upload_bytes` the most any one frame uploaded -- one byte a cell, on the frames
|
||||||
std::printf("[udpscope] density: frames=%d rows=%d cells=%llu grid_bytes=%llu ink=%.1f "
|
// whose grids moved; a frame whose grids did not move uploads nothing at all.
|
||||||
"peak=%.1f upload_bytes=%lld\n",
|
std::printf("[udpscope] density: frames=%d rows=%d traces=%d cells=%llu grid_bytes=%llu "
|
||||||
app.dbg_density_frames, app.dbg_density_rows, app.dbg_density_cells,
|
"ink=%.1f peak=%.1f upload_bytes=%lld\n",
|
||||||
app.dbg_density_grid_bytes, app.dbg_density_ink, app.dbg_density_peak,
|
app.dbg_density_frames, app.dbg_density_rows, app.dbg_density_traces,
|
||||||
app.dbg_density_upload_bytes);
|
app.dbg_density_cells, app.dbg_density_grid_bytes, app.dbg_density_ink,
|
||||||
|
app.dbg_density_peak, app.dbg_density_upload_bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -217,11 +217,11 @@
|
|||||||
|
|
||||||
Both displays live here and they are two different things: *multiple events* keeps the
|
Both displays live here and they are two different things: *multiple events* keeps the
|
||||||
last N acquisitions as N traces, dimmer with age, while *persistence* accumulates every
|
last N acquisitions as N traces, dimmer with age, while *persistence* accumulates every
|
||||||
acquisition into one screen-sized image (the plotter's `Plot::set_density()`). The
|
acquisition into an image (one screen-sized grid per trace, in that trace's own colour,
|
||||||
first is a count and the second is a decay, which is why neither is a mode of the
|
the plotter's `Plot::set_density()`), drawn in place of the traces. The first is a
|
||||||
other. -->
|
count and the second is a decay, which is why neither is a mode of the other. -->
|
||||||
<object class="GtkExpander" id="advanced_exp">
|
<object class="GtkExpander" id="advanced_exp">
|
||||||
<property name="tooltip-text" translatable="yes">what the trigger mode does with the acquisitions it takes: the last few kept as dimmer traces, and a persistence image every acquisition is accumulated into</property>
|
<property name="tooltip-text" translatable="yes">what the trigger mode does with the acquisitions it takes: the last few kept as dimmer traces, and a persistence image (one per signal, in its own colour) every acquisition is accumulated into</property>
|
||||||
<child type="label">
|
<child type="label">
|
||||||
<!-- A box rather than a plain label, for the search block's reason: the contextual
|
<!-- A box rather than a plain label, for the search block's reason: the contextual
|
||||||
help's "(i)" joins its last child and a hint hung on the expander's own *child* is
|
help's "(i)" joins its last child and a hint hung on the expander's own *child* is
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ struct App {
|
|||||||
/// off, which turns the display off with it -- a dump that asked the plots would say `rows=0`
|
/// 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.
|
/// about a run whose whole tail had the image up.
|
||||||
int dbg_density_rows = 0;
|
int dbg_density_rows = 0;
|
||||||
|
int dbg_density_traces = 0;
|
||||||
unsigned long long dbg_density_cells = 0;
|
unsigned long long dbg_density_cells = 0;
|
||||||
unsigned long long dbg_density_grid_bytes = 0;
|
unsigned long long dbg_density_grid_bytes = 0;
|
||||||
double dbg_density_ink = 0.0;
|
double dbg_density_ink = 0.0;
|
||||||
|
|||||||
+30
-21
@@ -6102,10 +6102,12 @@ double density_probe_tau() {
|
|||||||
return s > 0.0 ? std::min(s, 3600.0) : 1.0;
|
return s > 0.0 ? std::min(s, 3600.0) : 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What the density holds over every row it is on: the rows, the cells, and the two numbers the
|
/// What the density holds over every row it is on: the rows, the traces (one grid each), the cells,
|
||||||
/// plot reports. A row with the display off contributes nothing -- it has no grid at all.
|
/// and the two numbers the plot reports. A row with the display off contributes nothing -- it has no
|
||||||
|
/// grid at all.
|
||||||
struct DensityReading {
|
struct DensityReading {
|
||||||
int rows = 0;
|
int rows = 0;
|
||||||
|
int traces = 0;
|
||||||
unsigned long long cells = 0;
|
unsigned long long cells = 0;
|
||||||
double ink = 0.0;
|
double ink = 0.0;
|
||||||
double peak = 0.0;
|
double peak = 0.0;
|
||||||
@@ -6116,11 +6118,17 @@ DensityReading density_read(const App& a) {
|
|||||||
for (int i = 0; i < a.rows->row_count(); ++i) {
|
for (int i = 0; i < a.rows->row_count(); ++i) {
|
||||||
const RowSet::Row& row = a.rows->row(i);
|
const RowSet::Row& row = a.rows->row(i);
|
||||||
if (row.plot == nullptr || !row.plot->density().enabled) continue;
|
if (row.plot == nullptr || !row.plot->density().enabled) continue;
|
||||||
const plot::DensityGrid& g = row.plot->density_grid();
|
|
||||||
++r.rows;
|
++r.rows;
|
||||||
r.cells += static_cast<unsigned long long>(g.cell_count());
|
// **One grid per trace**, each in its trace's own colour, so the row's cells/ink are the sum
|
||||||
r.ink += g.ink();
|
// over its traces and the peak the brightest of them.
|
||||||
r.peak = std::max(r.peak, static_cast<double>(g.peak()));
|
for (plot::u32 t = 0; t < row.plot->trace_count(); ++t) {
|
||||||
|
const plot::DensityGrid& g = row.plot->density_grid(t);
|
||||||
|
if (g.cell_count() == 0u) continue; // an unbound slot holds no grid
|
||||||
|
++r.traces;
|
||||||
|
r.cells += static_cast<unsigned long long>(g.cell_count());
|
||||||
|
r.ink += g.ink();
|
||||||
|
r.peak = std::max(r.peak, static_cast<double>(g.peak()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -6129,12 +6137,18 @@ DensityReading density_read(const App& a) {
|
|||||||
/// plotter's own accumulation (its per-acquisition fade happens inside it, before the deposit), and
|
/// plotter's own accumulation (its per-acquisition fade happens inside it, before the deposit), and
|
||||||
/// the time step is `decay_density(tau)` -- the same call the frame tick makes with a frame's `dt`.
|
/// the time step is `decay_density(tau)` -- the same call the frame tick makes with a frame's `dt`.
|
||||||
double density_step(App& a, bool by_time) {
|
double density_step(App& a, bool by_time) {
|
||||||
double before = 0.0;
|
// The ink summed over every trace's grid (one grid per trace): the same total `density_read()`
|
||||||
for (int i = 0; i < a.rows->row_count(); ++i) {
|
// reports, so a step's ratio is off one reading.
|
||||||
const RowSet::Row& row = a.rows->row(i);
|
const auto ink = [&]() {
|
||||||
if (row.plot == nullptr || !row.plot->density().enabled) continue;
|
double total = 0.0;
|
||||||
before += row.plot->density_grid().ink();
|
for (int i = 0; i < a.rows->row_count(); ++i) {
|
||||||
}
|
const RowSet::Row& row = a.rows->row(i);
|
||||||
|
if (row.plot == nullptr || !row.plot->density().enabled) continue;
|
||||||
|
for (plot::u32 t = 0; t < row.plot->trace_count(); ++t) total += row.plot->density_grid(t).ink();
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
};
|
||||||
|
const double before = ink();
|
||||||
for (int i = 0; i < a.rows->row_count(); ++i) {
|
for (int i = 0; i < a.rows->row_count(); ++i) {
|
||||||
plot::Plot* p = a.rows->row(i).plot;
|
plot::Plot* p = a.rows->row(i).plot;
|
||||||
if (p == nullptr || !p->density().enabled) continue;
|
if (p == nullptr || !p->density().enabled) continue;
|
||||||
@@ -6144,12 +6158,7 @@ double density_step(App& a, bool by_time) {
|
|||||||
p->accumulate_density();
|
p->accumulate_density();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
double after = 0.0;
|
const double after = ink();
|
||||||
for (int i = 0; i < a.rows->row_count(); ++i) {
|
|
||||||
const RowSet::Row& row = a.rows->row(i);
|
|
||||||
if (row.plot == nullptr || !row.plot->density().enabled) continue;
|
|
||||||
after += row.plot->density_grid().ink();
|
|
||||||
}
|
|
||||||
return before > 0.0 ? after / before : 0.0;
|
return before > 0.0 ? after / before : 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6180,11 +6189,11 @@ void density_probe_report(App& a, const char* what, double ratio) {
|
|||||||
tc = a.model->trigger();
|
tc = a.model->trigger();
|
||||||
}
|
}
|
||||||
std::fprintf(stderr,
|
std::fprintf(stderr,
|
||||||
"[density-probe] %s enabled=%d fade=%s tau=%.3f factor=%.3f rows=%d cells=%llu "
|
"[density-probe] %s enabled=%d fade=%s tau=%.3f factor=%.3f rows=%d traces=%d "
|
||||||
"ink=%.1f peak=%.4f ratio=%.6f\n",
|
"cells=%llu ink=%.1f peak=%.4f ratio=%.6f\n",
|
||||||
what, tc.density ? 1 : 0,
|
what, tc.density ? 1 : 0,
|
||||||
tc.density_decay == PersistDecay::Time ? "time" : "acquisitions", tc.density_tau_s,
|
tc.density_decay == PersistDecay::Time ? "time" : "acquisitions", tc.density_tau_s,
|
||||||
tc.density_factor, r.rows, r.cells, r.ink, r.peak, ratio);
|
tc.density_factor, r.rows, r.traces, r.cells, r.ink, r.peak, ratio);
|
||||||
std::fflush(stderr);
|
std::fflush(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-8
@@ -756,11 +756,12 @@ GtkWidget* build_trigger_panel(App& app) {
|
|||||||
"acquisitions are also bounded by the buffer's own size");
|
"acquisitions are also bounded by the buffer's own size");
|
||||||
app.trigger_persist_row = row_for(ui, "persistence_row", app.trigger_persist_spin);
|
app.trigger_persist_row = row_for(ui, "persistence_row", app.trigger_persist_spin);
|
||||||
|
|
||||||
// The phosphor *density*: one screen-sized image every acquisition is accumulated into, drawn
|
// The phosphor *density*: one screen-sized image per trace, in that trace's own colour, every
|
||||||
// behind the traces and fading by a time constant or by a per-acquisition factor -- the other
|
// acquisition accumulated into it, drawn *in place of* the traces and fading by a time constant
|
||||||
// display of the same acquisitions, and nothing like the multiple-event one above (N traces
|
// or by a per-acquisition factor -- the other display of the same acquisitions, and nothing like
|
||||||
// against one image, and no count to choose). It applies in trigger mode only, which the body
|
// the multiple-event one above (an image per signal against N retained traces, and no count to
|
||||||
// already is: the whole block goes insensitive with the trigger off, the panel's own rule.
|
// choose). It applies in trigger mode only, which the body already is: the whole block goes
|
||||||
|
// insensitive with the trigger off, the panel's own rule.
|
||||||
app.trigger_density_switch = gtk_switch_new();
|
app.trigger_density_switch = gtk_switch_new();
|
||||||
gtk_widget_set_valign(app.trigger_density_switch, GTK_ALIGN_CENTER);
|
gtk_widget_set_valign(app.trigger_density_switch, GTK_ALIGN_CENTER);
|
||||||
gtk_widget_set_halign(app.trigger_density_switch, GTK_ALIGN_END);
|
gtk_widget_set_halign(app.trigger_density_switch, GTK_ALIGN_END);
|
||||||
@@ -771,9 +772,10 @@ GtkWidget* build_trigger_panel(App& app) {
|
|||||||
}
|
}
|
||||||
gtk_widget_set_tooltip_text(
|
gtk_widget_set_tooltip_text(
|
||||||
app.trigger_density_switch,
|
app.trigger_density_switch,
|
||||||
"accumulate every triggered acquisition into one screen-sized image, drawn behind the "
|
"accumulate every triggered acquisition into a screen-sized image per signal, in that "
|
||||||
"traces: brightness is how often a trace passed, so an image of a thousand acquisitions "
|
"signal's own colour, drawn in place of the traces: brightness is how often a trace passed, "
|
||||||
"costs what one costs. Trigger mode only, and cleared whenever the display leaves it");
|
"so an image of a thousand acquisitions costs what one costs. Trigger mode only, and cleared "
|
||||||
|
"whenever the display leaves it");
|
||||||
g_signal_connect(app.trigger_density_switch, "notify::active",
|
g_signal_connect(app.trigger_density_switch, "notify::active",
|
||||||
G_CALLBACK(on_density_toggled), &app);
|
G_CALLBACK(on_density_toggled), &app);
|
||||||
// Not `row_for()`: it expands its child, and an expanded switch is a wide pill that reads as a
|
// Not `row_for()`: it expands its child, and an expanded switch is a wide pill that reads as a
|
||||||
|
|||||||
Reference in new issue
Block a user