aethersdr / aethersdr/AetherSDR
Software-rendered spectrum heat-map fill saturates the main thread (934 ms/s painting at 25 fps on Pi 5) — UI freezes under any extra load
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 221
- Forks
- 117
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 299
Description
Summary
On a software-rendered platform (Raspberry Pi 5, Wayland/labwc, no GPU paint path), the spectrum heat-map fill costs ~37 ms per frame at the standard 25 fps — 934 ms of every second on the main thread is spent painting (AE's own renderstats: paintsPerSec=25.6, paintMsPerSec=934). The main thread runs permanently at ~95% paint saturation; any additional main-thread load (observed trigger: the disconnected state, plausibly its reconnect overlay animation) tips it into input starvation and the UI reads as frozen — live value readouts still tick, clicks go nowhere.
Main d40863c4, native ARM64 build, real FLEX-6300 / IC-9700 bench.
Evidence
- Main thread pegged at 100% CPU (19,384 jiffies vs <1,000 for every worker thread — DSP/PanadapterStream near idle, so this is not FFT compute).
- gdb samples land in the paint pipeline every time:
SpectrumWidget::drawSpectrum(SpectrumWidget.cpp:15403, polyline point indexing),QTextLine::layout_helper, Qt Gui raster internals — different innermost frame each sample, so paints complete and immediately reschedule (cost problem, not a stuck loop). renderstatsduring the event:paintsPerSec = 25.58,paintMsPerSec = 933.99→ ~36.5 ms/frame.- RSS flat at ~315 MB throughout (run under a systemd
MemoryMax=2.5Gscope): not a leak. - Recovery observed live: reconnecting the radio removed the extra load and the UI became responsive again, with paint cost unchanged.
The hot path
drawSpectrum's heat-map fill builds a QPolygonF trapezoid per FFT column per frame, each with per-column gradient color math (SpectrumWidget.cpp ~15398 onward). At ~500+ columns × 25 fps through the software rasterizer, that alone accounts for the budget. A QTextLine::layout_helper sample suggests per-frame text relayout rides along.
Knock-on effects worth knowing
- While saturated, an IC-9700 RS-BA1 connect attempt failed twice (never attached), while a FLEX TCP connect succeeded — consistent with the UDP handshake being sensitive to main-thread starvation. Hypothesis, not proven.
- On a marginal power supply this sustained load participated in genuine brownouts (5 V rail measured at 4.75 V idle on the old PSU — hardware issue, fixed; noted so nobody re-conflates the two).
Suggested directions (maintainer's call)
- Cache the heat-map fill as a column-gradient image instead of per-frame trapezoid fills, or render columns into a
QImageonce per FFT update rather than per paint. - Auto-degrade on software render: detect paint budget overrun (the renderstats plumbing already measures it) and step down fps / disable the heat-map fill with a status hint.
- The disconnected/reconnect overlay path deserves a look for animation cost on the same thread.
Full bench provenance (sampler CSVs, gdb transcripts) available on request.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in SpectrumWidget.cpp at SpectrumWidget::drawSpectrum around lines 15398-15403, then reproduce on a software-rendered Raspberry Pi setup while capturing the existing renderstats measurements. Compare the heat-map paint cost and any disconnected/reconnect overlay cost; done means the UI remains responsive under extra load without the reported main-thread paint saturation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, raspberry-pi
- Domain
- computer-graphics, desktop, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100