Summary
On wide displays, horizontal frequency scrolling (mouse wheel) of the panadapter is choppy because the entire waterfall history image is reprojected on the CPU on every pan step — even though that history is only ever displayed during time-scrollback, never in the live view.
At ultrawide widths the history image is enormous: capacity is kWaterfallHistoryMs (20 min) ÷ 50 ms/row = 24 000 rows, so at ~4860 px wide it is ~0.5 GB (4860 × 24000 × 4). reprojectWaterfall() runs a QPainter::drawImage over both the visible waterfall and this full history image on every center-frequency change (SpectrumWidget.cpp reprojectWaterfall).
Evidence (instrumented, RTX 4090 / i9-13900K, 4860×… panadapter)
Timing the two reprojections separately during a 10 s scroll:
WfReprojectSplit visMs=3.21 visPx=4860x728 histMs=107.52 histPx=4860x24000
WfReprojectSplit visMs=2.99 visPx=4860x728 histMs=101.96 histPx=4860x24000
...
histMs: n=91 min=73 max=118 avg=108 <- full history reproject, per pan step
visMs: n=91 max=4.0 avg=3.3 <- visible waterfall reproject
The history reprojection (avg 108 ms/step) is ~33× the visible one and blows the 16 ms frame budget ~7×. Built-in PerfSummary telemetry during continuous scrolling confirmed the user-visible effect:
| metric (continuous-scroll windows) |
value |
uiLagMaxMs |
110–164 ms |
panAgeP95Ms / wfAgeP95Ms |
120–155 ms |
renderP95Ms (GPU) |
6–8 ms ✅ (GPU is healthy) |
The GPU render path is not the bottleneck; the synchronous CPU history reproject on the main thread is.
Impact
Frequency scrolling stutters badly on wide/ultrawide panadapters; the wider the window, the worse (cost scales with pixel width). The live view does not even use the history image, so the work is entirely wasted during normal tuning.
Root cause
reprojectWaterfall() keeps the full history continuously aligned to the current frequency frame so newly-appended rows stay consistent. That correctness goal is real, but paying ~0.5 GB of drawImage per pan step to maintain it — for data only shown on time-scrollback — is the wrong trade.
Proposed fix (PR to follow)
Reproject the visible waterfall immediately (cheap, ~3 ms) but defer + coalesce the history reprojection: remember the net transform and apply it once after scrolling settles (debounced), pausing history appends while pending and flushing on demand before a viewport rebuild (time-scrollback). The live view is visually unchanged.
After-fix telemetry (continuous scroll, up to 49 tune-cmds/s): uiLagMaxMs 27–38 ms, panAgeP95Ms/wfAgeP95Ms 24–30 ms — a 4–5× improvement. A small residual (one ~108 ms reproject per scroll gesture, on stop) remains and will be tracked separately.
Related
Distinct root cause from #3283 (which covers QPainter widget rasterization and GPU texture re-upload); this is specifically the history-image reprojection. Cross-referencing for the broader perf picture.
Environment
Windows 11, Qt 6.8.3 (msvc2022_64), RTX 4090 / i9-13900K, ultrawide (5144×1440). Platform-independent code path (reprojectWaterfall is plain Qt) — affects all platforms with wide panadapters.
Summary
On wide displays, horizontal frequency scrolling (mouse wheel) of the panadapter is choppy because the entire waterfall history image is reprojected on the CPU on every pan step — even though that history is only ever displayed during time-scrollback, never in the live view.
At ultrawide widths the history image is enormous: capacity is
kWaterfallHistoryMs(20 min) ÷ 50 ms/row = 24 000 rows, so at ~4860 px wide it is ~0.5 GB (4860 × 24000 × 4).reprojectWaterfall()runs aQPainter::drawImageover both the visible waterfall and this full history image on every center-frequency change (SpectrumWidget.cppreprojectWaterfall).Evidence (instrumented, RTX 4090 / i9-13900K, 4860×… panadapter)
Timing the two reprojections separately during a 10 s scroll:
The history reprojection (avg 108 ms/step) is ~33× the visible one and blows the 16 ms frame budget ~7×. Built-in
PerfSummarytelemetry during continuous scrolling confirmed the user-visible effect:uiLagMaxMspanAgeP95Ms/wfAgeP95MsrenderP95Ms(GPU)The GPU render path is not the bottleneck; the synchronous CPU history reproject on the main thread is.
Impact
Frequency scrolling stutters badly on wide/ultrawide panadapters; the wider the window, the worse (cost scales with pixel width). The live view does not even use the history image, so the work is entirely wasted during normal tuning.
Root cause
reprojectWaterfall()keeps the full history continuously aligned to the current frequency frame so newly-appended rows stay consistent. That correctness goal is real, but paying ~0.5 GB ofdrawImageper pan step to maintain it — for data only shown on time-scrollback — is the wrong trade.Proposed fix (PR to follow)
Reproject the visible waterfall immediately (cheap, ~3 ms) but defer + coalesce the history reprojection: remember the net transform and apply it once after scrolling settles (debounced), pausing history appends while pending and flushing on demand before a viewport rebuild (time-scrollback). The live view is visually unchanged.
After-fix telemetry (continuous scroll, up to 49 tune-cmds/s):
uiLagMaxMs27–38 ms,panAgeP95Ms/wfAgeP95Ms24–30 ms — a 4–5× improvement. A small residual (one ~108 ms reproject per scroll gesture, on stop) remains and will be tracked separately.Related
Distinct root cause from #3283 (which covers
QPainterwidget rasterization and GPU texture re-upload); this is specifically the history-image reprojection. Cross-referencing for the broader perf picture.Environment
Windows 11, Qt 6.8.3 (msvc2022_64), RTX 4090 / i9-13900K, ultrawide (5144×1440). Platform-independent code path (
reprojectWaterfallis plain Qt) — affects all platforms with wide panadapters.