fix: the density's brightness ceiling is the fade's steady state, and the ramp is linear
A user looked at the display and reported "the colour fading is not applied, it looks white or full color, no opacity gradient" -- and the *numbers* were right: ink decayed, `step-time` was 1/e, `step-acquisitions` was 1 + 0.5 x 1. It was the mapping from a cell's value to a pixel. **Neither the shader nor the blend was wrong** (the answer to the question that was asked): the fragment write is already the weighted one (`vec4(u_color.rgb, u_color.a * ink)`) with the frame's straight-alpha blend, and a cell at half ink over a grid line rendered 160 where the arithmetic predicts 160. What was wrong was the **scale**: - `full_scale` defaulted to **1.0**, so the *newest* acquisition already sat at the ceiling and everything above it clamped to the same pixel -- a display of one sweep and a display of five were one white. It is now **1/(1 - 0.5) = 2.0**, the steady state of the default per-acquisition fade, so one sweep reads half ink and white is reached only where acquisitions agree. A caller whose fade settles elsewhere states its own sum (udpscope's is `1/(1 - factor)`, in its commit). - `gamma` defaulted to **0.45**, the value `Style::intensity_gamma` wants where the measure is a *dwell fraction*; on a *count* it spends most of the ramp on the last sweep (a quarter of the scale mapped to 0.54). It is now **1.0 -- linear in sweeps**, because half as many sweeps must read as half the ink. The case that would have caught this is a **framebuffer** reading, not the grid's, and `a_phosphor_density_is_drawn_under_the_traces` now makes it: it fades the grid to a quarter through the time rule (`dt = tau ln 4`, asserted at 0.25 off the ink) and reads the same pixel back, so the ramp is asserted where the eye sees it -- **`empty=21 full=83 quarter=54`** (red channel), against the 199 / 199 the ceiling and a single sweep both read at the old defaults. A full cell must be measurably above the background and above a quarter cell, and a quarter cell above the background: three assertions on pixels, none of which the grid's own numbers can make. Suite: **396 cases, 0 failed**, 4/4 ctest entries.
This commit is contained in:
1 parent
6a6915e64e
commit
738205293e
3 files changed
+68
-12
No files matched your search
@@ -7638,6 +7638,42 @@ PLOT_TEST(a_phosphor_density_is_drawn_under_the_traces) {
|
||||
px[2] != ghost_without[2]);
|
||||
PLOT_CHECK(!pixel_is(px, trace_color));
|
||||
|
||||
// **The screen half**: a cell at a quarter of what a full one holds must render measurably
|
||||
// fainter, and a full cell must not be the background. That is the assertion the *eye* makes --
|
||||
// it is what "the colour fading is not applied, it looks white" turned out to be -- and nothing
|
||||
// in the grid's own numbers can make it: they were right while the picture was flat.
|
||||
//
|
||||
// The row measured is the one the *ghost* is in (the sweep before the last), not the trace's own:
|
||||
// a trace drawn over a cell hides the density in it by construction.
|
||||
const auto channel = [&](const std::vector<plot::u8>& f, plot::u32 x, plot::u32 y, int c) {
|
||||
return static_cast<int>(f[(plot::usize(y) * kW + x) * 4u + static_cast<plot::usize>(c)]);
|
||||
};
|
||||
const int ghost_full = channel(with, mid_x, ghost_row, 0);
|
||||
// The background to compare against is a row *away* from both: the ghost's own row holds the
|
||||
// trace in the frame drawn without a density (that is where the trace was before it moved), so
|
||||
// the difference between the two frames there is the trace, not the ink.
|
||||
const plot::u32 blank_row = ghost_row + 40u < kH ? ghost_row + 40u : ghost_row - 40u;
|
||||
const int empty_red = channel(without, mid_x, blank_row, 0);
|
||||
// A quarter of the ink: `exp(-dt/tau)` is the time fade's own rule, so `dt = tau*ln(4)` is one
|
||||
// step to a quarter whatever one sweep deposited.
|
||||
plot::DensityConfig fading = cfg;
|
||||
fading.decay = plot::DensityDecay::TimeConstant;
|
||||
fading.time_constant_s = 1.0f;
|
||||
p.set_density(fading);
|
||||
const double before = p.density_grid().ink();
|
||||
p.decay_density(std::log(4.0));
|
||||
const double after = p.density_grid().ink();
|
||||
PLOT_CHECK_NEAR(after / before, 0.25, 1e-6);
|
||||
r.begin_frame(kW, kH, kFrameRect);
|
||||
r.draw(p);
|
||||
r.end_frame();
|
||||
PLOT_CHECK(!r.last_frame_had_gl_error());
|
||||
const std::vector<plot::u8> faded = read_frame(ctx);
|
||||
const int ghost_quarter = channel(faded, mid_x, ghost_row, 0);
|
||||
PLOT_CHECK(ghost_full > empty_red + 20);
|
||||
PLOT_CHECK(ghost_full > ghost_quarter + 20);
|
||||
PLOT_CHECK(ghost_quarter > empty_red);
|
||||
|
||||
// A frame with nothing new to deposit: the grid did not move, so the conversion and the upload
|
||||
// are skipped -- the frame costs the one draw call -- and the picture is bit-identical.
|
||||
r.begin_frame(kW, kH, kFrameRect);
|
||||
@@ -7647,11 +7683,13 @@ PLOT_TEST(a_phosphor_density_is_drawn_under_the_traces) {
|
||||
PLOT_CHECK(r.stats().density_cells > 0u);
|
||||
PLOT_CHECK(r.stats().density_upload_bytes == 0u);
|
||||
const std::vector<plot::u8> again = read_frame(ctx);
|
||||
PLOT_CHECK(again == with);
|
||||
PLOT_CHECK(again == faded);
|
||||
|
||||
std::printf("[density gl] cells=%u uploaded=%u differing=%u ghost row=%u trace row=%u"
|
||||
" axes=%.0f,%.0f %.0fx%.0f\n",
|
||||
first.density_cells, first.density_upload_bytes, differing, ghost_row, trace_row,
|
||||
axes.x, axes.y, axes.w, axes.h);
|
||||
std::printf("[density gl] luminance red: empty=%d full=%d quarter=%d (ink %.0f -> %.0f)\n",
|
||||
empty_red, ghost_full, ghost_quarter, before, after);
|
||||
ctx.destroy();
|
||||
}
|
||||
Reference in new issue
Block a user