fix: a dense trace is drawn one pixel column at a time, so it cannot moiré
At a high rate, a high frequency or on noise the plots banded visibly and the bands crawled under a pan. Three things compound to produce it, none a bug alone: a pyramid bucket spans one to two pixel columns, Envelope emits two points per bucket so each contributes a near-vertical hairline and a near-vertical connector, and every one lands at a fractional x because a trace is deliberately not snapped -- so a column's brightness beats against the pixel grid, and a pan sweeps the beat. The cure is upstream (deps/plotter, pin moved): draw_traces reduces such a window onto the pixel grid, one snapped vertical segment per column from that column's lowest to its highest value. One sub-pixel phase for the whole trace, so there is nothing left to beat, and the geometry is bounded by axes.w rather than by the buckets. frame path: gains a columns= field, which is what separates "the segment count collapsed because the reduction ran" from "the window got narrower". On the fast mock at 3 kHz over 20 s: columns=1200, segments 2041/2237 over 1201 painted frames, against columns=0, segments 10960/216093 over 263 before -- the same run at 4.6x the frame rate, with trace ink: clean either way. Also corrects the documented mock invocation: --batch 20000 exceeds the datagram limit and the mock dies with Errno 90. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
62df049f53
commit
5f5b87ca60
5 files changed
+94
-8
No files matched your search
@@ -939,6 +939,38 @@ into the tick, the plot's own draw and the whole GTK paint, and report the segme
|
||||
and whether the LOD path was taken at all. A segment count in the millions is this defect
|
||||
returning.
|
||||
|
||||
**A dense trace is drawn one pixel column at a time, which is what cured the moiré.** At a
|
||||
high sample rate, a high signal frequency or on a noisy signal the plots showed strong,
|
||||
*crawling* banding — and three things compounded to produce it, none of which is a bug on its
|
||||
own. `MinMaxPyramid::level_for()` sizes a bucket at one to two pixel columns; `Envelope`
|
||||
emits two points per bucket, so each bucket contributes a near-vertical min→max hairline
|
||||
*and* a near-vertical connector to the next, about 3.6 near-vertical hairlines per column per
|
||||
trace; and every one of them lands at a *fractional* x, because a trace is deliberately not
|
||||
snapped. With src-alpha compositing a column's brightness is then a non-linear function of
|
||||
the sub-pixel phases overlapping it, the phase pattern repeats at the beat between the bucket
|
||||
pitch and the pixel pitch, and a pan sweeps the beat. It needs a *thin ribbon* to be visible:
|
||||
a window whose buckets each span several periods paints a saturated block and hides it
|
||||
entirely, which is why it showed up on fast and noisy signals and not on a slow sine at a
|
||||
whole-window view.
|
||||
|
||||
`deps/plotter`'s `Renderer::draw_traces` now reduces such a window onto the pixel grid: one
|
||||
exactly vertical segment per integer column, from the lowest to the highest screen y any
|
||||
gathered point put in it, flushed in framebuffer pixels with the snap on. The whole trace
|
||||
then has **one** sub-pixel phase, so there is nothing left to beat. See the upstream
|
||||
`docs/data-and-performance.md` for the four rules it keeps (a hole stays a hole, disjoint
|
||||
neighbours are joined so a fast edge keeps its join, a span under the stroke is drawn *as*
|
||||
the stroke, and the gate is the gathered density rather than the LOD level) and for why this
|
||||
is the one carve-out in `u_snap`'s rule that chrome may be snapped and data may not.
|
||||
|
||||
It is also a large speed-up, because the geometry becomes O(axes.w) instead of O(buckets).
|
||||
Measured on the fast mock (`--rate 1300000 --batch 2000 --freq 3000`, a 20 s run): `frame
|
||||
path: raw=1 lod=262 columns=0 frames, segments 10960/216093` and **263 painted frames**
|
||||
before, `raw=2 lod=1198 columns=1200 frames, segments 2041/2237` and **1201 painted frames**
|
||||
after — the same run at 4.6× the frame rate, with `trace ink:` reading `0 collapsed, 0
|
||||
staircase, 0 short, worst drawn-vs-quoted 0.0000 px` either way. `frame path:`'s
|
||||
`columns=` field is the reading that separates "the segment count collapsed because the
|
||||
reduction ran" from "the window got narrower".
|
||||
|
||||
**The panels and the toolbar.** The header bar carries only what you reach for
|
||||
*while watching a signal*: the time window, pause/resume, the trigger toggle, the
|
||||
side-panel toggle and an overflow menu. Those
|
||||
|
||||
Reference in new issue
Block a user