feat(render): a reduced column can be graded by dwell, not drawn as one flat band
A pixel column reduced from a dense envelope is honest about where the signal went and silent about how long it was there: its min and max are identical whether its samples fill the span or pile at its two ends, so a ramp and a pulse are one band. Style::intensity_grading grades it instead -- bright where the signal sits, faint where it only passes through -- by asking the gather for a level finer than the pixel grid and reading the distribution from those sub-buckets. Off by default: a change of look, not a correction of one. The measure is dwell per row, each segment depositing one unit spread over the rows it spans; counting +1 per row instead grades a sine's fast middle as brightly as its rails, which is the pattern inverted. The accumulator is one column wide (a full w*h density image is 33 MB at 4K) with each span written as two delta writes, and normalisation is against the column's own peak -- a frame-wide scale fades the trace out exactly where it moves fastest. Alpha is quantised into tiers, one LineBatch and one draw call each, because the line pipeline's colour is a uniform and a per-segment alpha is inexpressible. The finer ask is floored at level 1 whenever the flat path would have used the pyramid: level_for() answers 0 -- raw samples -- as soon as a sub-column falls under one bucket, so an unfloored ask turns a moderately dense frame into an O(visible) gather (measured at 172 samples a pixel: raw=1154 lod=0 and 6.4 ms a frame against raw=710 lod=453 and 2.3 ms). Grading is a way of drawing a reduction and may not undo it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e81e84645f
commit
cc908efa36
6 files changed
+629
-13
No files matched your search
@@ -216,6 +216,66 @@ polyline, over one sine period across 60 000 samples. A count of *partially cove
|
||||
always spills into its neighbour column, so the two renderings measure 577 and 652, which
|
||||
separates nothing. The frame's whole geometry collapses with it: 627 segments against 60 054.
|
||||
|
||||
## A reduced column can be graded by dwell
|
||||
|
||||
`Style::intensity_grading` draws each of those columns **graded** rather than as one flat band:
|
||||
bright where the signal sits, faint where it only passes through, the way a digital-phosphor
|
||||
scope reads. It is off by default — a change of look, not a correction of one — and
|
||||
`FrameStats::graded_traces` counts the traces that took it, a subset of `column_traces`, since
|
||||
grading is a way of drawing the reduction and not a third path.
|
||||
|
||||
What it puts back is the information the reduction throws away: a column's min and max are the
|
||||
**same** whether its samples fill the span evenly or pile at its two ends, so a ramp and a pulse
|
||||
are one band. The distribution cannot be recovered from the band, which is why the gather is
|
||||
asked at a level **finer than the pixel grid** (`intensity_sub_columns` sub-buckets per column)
|
||||
and the dwell read from those sub-buckets.
|
||||
|
||||
Five things carry it, and each was arrived at by being wrong first:
|
||||
|
||||
- **The measure is dwell per row, each segment depositing one unit spread over the rows it
|
||||
spans** (`1 / height`). Counting +1 per row instead grades a sine's fast middle as brightly as
|
||||
its rails, which is the pattern inverted — the middle is where the signal spends the *least*
|
||||
time.
|
||||
- **The accumulator is one column wide.** Gathered x is monotonic, so points arrive column by
|
||||
column: accumulate, read, clear. A full `axes.w × axes.h` density image is 33 MB at 4K; one
|
||||
column of `axes.h` floats is L1-resident. A span is written as a `+1/height` delta at its first
|
||||
row and the negation one past its last, so it costs two writes whatever its height, and the
|
||||
running sum over the column is the per-row dwell.
|
||||
- **Normalisation is per column, against the column's own peak.** A frame-wide scale needs the
|
||||
whole image to exist before anything can be drawn, *and* reads worse: a column the signal flew
|
||||
through uniformly comes out faint at every row, so the trace fades out exactly where it moves
|
||||
fastest. The cost of the local scale is one extra non-clearing walk of the accumulator to find
|
||||
the peak.
|
||||
- **Alpha is quantised into `intensity_steps` tiers, one `LineBatch` and one draw call each.**
|
||||
The line pipeline's colour is a **uniform**, not per-instance (`LineInstance` is four floats of
|
||||
geometry; only `TextInstance` carries `a_colour`), so a per-segment alpha is inexpressible and
|
||||
the tiers are what make the gradient drawable at all. `intensity_floor` keeps the faintest tier
|
||||
visible — a fast riser is one sub-bucket deep everywhere and must not be dimmed out of
|
||||
existence — and `intensity_gamma` lifts the faint end. A connector between two disjoint columns
|
||||
is emitted at the **faintest** tier: it is inferred geometry, not a place the signal was
|
||||
measured to dwell.
|
||||
- **Grading may not undo the reduction.** `level_for()` answers 0 — raw samples — as soon as the
|
||||
asked-for fraction of a column falls under one bucket (`kBucket` is 64), so the finer ask is
|
||||
**floored at level 1** whenever the flat path would have used the pyramid. Without the floor
|
||||
every moderately dense frame became an O(visible) gather: measured in udpscope at 1.3 MS/s over
|
||||
a 2 s window, 172 samples a pixel, the flat path sat on the pyramid and grading dropped the
|
||||
whole figure to raw (`raw=1154 lod=0` against `raw=710 lod=453`) and cost 2.3 → 6.4 ms of draw
|
||||
mean. The floor degrades the *look* instead — fewer sub-columns than asked for, down to what the
|
||||
coarsest buckets give, which at 64 samples a bucket is still two or three per column. That is
|
||||
also why raising `intensity_sub_columns` past about 8 buys nothing: the bucket size is the
|
||||
limit, not the number.
|
||||
|
||||
`a_dense_trace_can_be_graded_by_dwell` is the guard, and it draws the same frame both ways. It
|
||||
profiles the mean peak coverage over the band's two outer fifths against its middle fifth: the
|
||||
flat band is flat (**191 and 191**, one ink) and the graded one is **191 against 131**, a middle
|
||||
at 0.69 of the edge. That ratio is a *tier* ratio and not the dwell ratio — the default gamma
|
||||
0.45 and floor 0.3 compress it on purpose. The geometry stays bounded by the axis and not by its
|
||||
area: **8303 segments over a 572 px axis**, 14.5 runs a column, against the flat path's 627 at
|
||||
one end and `572 × 156` rows at the other. A fourth frame over a window wide enough that the
|
||||
flat path is on the pyramid is what exercises the floor (`raw=0 lod=1 … seg=634
|
||||
visible=200001`); the test's first window sits at 105 samples a pixel, where both paths are raw
|
||||
and the floor is never reached.
|
||||
|
||||
## One sample or a statistic of many
|
||||
|
||||
Every number the library reports is one the samples actually hold. What differs between
|
||||
|
||||
Reference in new issue
Block a user