Skip to content

feat(gui): stacked-trace spectrum stream panadapter render mode#3899

Merged
ten9876 merged 3 commits into
aethersdr:mainfrom
jensenpat:feat/stacked-trace-spectrum
Jun 30, 2026
Merged

feat(gui): stacked-trace spectrum stream panadapter render mode#3899
ten9876 merged 3 commits into
aethersdr:mainfrom
jensenpat:feat/stacked-trace-spectrum

Conversation

@jensenpat

@jensenpat jensenpat commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator
Screenshot 2026-06-28 at 10 57 21 PM

Summary

Adds a stacked-trace spectrum stream as a panadapter render mode, selectable from the Display pane (Spectrum: 2D / 3D). In 3D the spectrum region becomes a perspective stacked-trace surface — a rolling history of FFT traces receding into the distance — while the waterfall, frequency scale, and all overlays keep working underneath. The 2D path is unchanged. Built on the project's existing Qt + QRhi infrastructure and designed to run on macOS, Windows, and Linux.

Features

  • Perspective stacked-trace surface. A rolling history (96 rows × 768 columns) drawn back-to-front: the newest trace spans the full width across the front; older traces recede into a narrower, higher trapezoid; each ridge fills to the plot floor so nearer traces occlude farther ones.
  • GPU height-map mesh. A static vertex grid samples a ring-buffered height texture, so pan/zoom/resize rebuild no geometry — panning stays fluid. Each new FFT frame uploads a single texture row (~1.5 KB) instead of re-rasterizing the surface.
  • Noise-floor-anchored amplitude with a floor→peak colour gradient. Ridge height is anchored to the measured noise floor (the same estimator the 2D auto-floor uses), with a non-linear floor-expansion curve, so the floor is visible and coloured up through the peaks rather than only the crests. A 3D Floor depth slider controls how far below the floor to surface. The colour palette follows the existing waterfall colour-scheme selector.
  • Dim per-row trace outline. A depth-faded hairline traces each receding ridge.
  • Broadband impulse rejection. A temporal median rejects 1-frame interference bursts before they enter the height history, so a strong transient doesn't become a full-height artifact that recedes across the surface.
  • Overlays preserved. Spots, memories, slice/VFO markers, navigation hints, band plan, and the dBm/time/frequency scales all render exactly as in 2D — the 3D surface replaces only the spectrum trace.
  • Controls (Display pane, persisted per-panadapter): the 2D/3D selector and the 3D Floor depth slider.
  • Display-pane background control (small, self-contained): right-clicking the background Clear button turns the spectrum background off entirely (no image, just the fill colour) and persists it; left-click still reverts to the default logo.

Cross-platform

  • No platform-specific shader code. The two new shaders are plain GLSL 440 and are baked by Qt's shader tool (qsb) to the full variant set — SPIR-V (Vulkan), GLSL (OpenGL/GLES), HLSL (Direct3D 11), and MSL (Metal) — the same set the existing spectrum shaders produce.
  • Backend selection is unchanged. The QRhi API is chosen exactly as the existing GPU spectrum already does it: Metal on macOS (#ifdef Q_OS_MAC), the platform default elsewhere (Direct3D 11 on Windows, OpenGL/Vulkan on Linux). The new surface rides the same QRhiWidget.
  • Portable feature choices. No geometry/tessellation shaders; occlusion is by back-to-front draw order (no depth-attachment dependency); 1-px lines; the R16F height texture is guarded by a capability check.
  • Graceful degradation. GPU mesh → GPU cached-image quad (RGBA8) → CPU QPainter surface, so it still renders where the height texture or QRhi itself isn't available (including older-Qt builds where the GPU spectrum is compiled out).
  • Compiles in CI on Windows and Linux alongside macOS.

Verification

  • Live-tested RX on hardware (macOS / Metal): dense, smooth surface; noise floor visible with the floor→peak gradient; dim trace outlines; fluid pan; impulse bursts rejected; perf healthy (full FFT rate, ~1 ms UI-thread lag, 0% frame loss). The 2D trace + waterfall round-trips unchanged.
  • The impulse rejection was additionally verified with a standalone render harness (a 1-frame broadband burst injected mid-history produces no artifact).
  • Runtime rendering on Windows/Linux GPU backends has not yet been eyeballed — CI covers the build + shader bake, and the QRhi/NDC plumbing matches the existing cross-platform spectrum pipelines. A quick visual check on those platforms is welcome.

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat

@jensenpat jensenpat added New Feature New feature request GUI User interface labels Jun 29, 2026
@jensenpat jensenpat marked this pull request as ready for review June 29, 2026 05:34
@jensenpat jensenpat requested review from a team as code owners June 29, 2026 05:34
@jensenpat jensenpat linked an issue Jun 29, 2026 that may be closed by this pull request
@jensenpat jensenpat force-pushed the feat/stacked-trace-spectrum branch from 3998eb8 to 822f4d3 Compare June 29, 2026 05:43
@jensenpat jensenpat marked this pull request as draft June 29, 2026 05:44
@jensenpat jensenpat removed New Feature New feature request GUI User interface labels Jun 29, 2026
@jensenpat jensenpat marked this pull request as ready for review June 29, 2026 05:45
@jensenpat jensenpat force-pushed the feat/stacked-trace-spectrum branch from 822f4d3 to 7fff14e Compare June 29, 2026 06:06
@jensenpat jensenpat marked this pull request as draft June 29, 2026 06:06
@jensenpat jensenpat marked this pull request as ready for review June 29, 2026 06:13

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, @jensenpat — this is a well-structured feature. The static-grid-plus-ring-texture design (no geometry rebuild on pan/zoom), the three-tier graceful degradation (GPU mesh → cached-image quad → CPU QPainter), the R16F capability guard, the thorough qWarning+fallback on every GPU init step, and the careful "3D replaces only the spectrum trace, everything else stays 2D" framing all read cleanly. AppSettings is used correctly (the Display* keys follow the existing per-panadapter pattern), releaseResources() tears down every new GPU resource, and CI is green on Windows/macOS/CodeQL. A few things worth a look before merge:

1. GPU mesh uploads only the single newest row per frame — skipped rows are still drawn (likely bug).
In renderGpuFrame() the height texture is updated only when head != m_dssMeshHeadUploaded, and only that one head row is uploaded:

if (m_dss.rowCount() > 0 && head != m_dssMeshHeadUploaded) {
    ... upload one row at DestinationTopLeft(0, head) ...
    m_dssMeshHeadUploaded = head;
}

But pushRow() runs per incoming FFT frame in updateSpectrum(), while this upload runs per repaint. If the FFT push rate ever outruns the repaint rate (coalesced paints under load), the head advances by more than one ring slot between uploads. The intermediate slots are within drawnRows and are drawn, but their texels were never uploaded → stale/uninitialized data recedes across the surface. Consider uploading every row between the previously-uploaded head and the current head (the slots are contiguous mod kRows), rather than just the latest. Worth confirming on a fast FFT rate with a throttled repaint.

2. zCurve floor-expansion is applied only on the GPU path, not the CPU fallback (render-path parity gap).
dss_mesh.vert does s = pow(s, max(zCurve, 0.05)) to lift the floor→signal band, but DssRenderer::rebuild() computes strength linearly with no equivalent curve. So the GPU mesh surfaces the noise-floor carpet (the headline feature) while the cached-image/CPU fallback flattens it — the two paths render visibly different surfaces. (Slope-shading and the outline styling also differ, but those are more cosmetic.) This is a recurring bug class here; matching the curve in rebuild() would keep the fallback faithful.

3. dss_mesh.vert: the half-texel offset the comment describes isn't actually applied.

// Sample the ring-buffered height row (+half texel to hit the row centre).
float texY = fract(rowOffset + v);

rowOffset = head/rows and v = rr/rows, so texY lands on the row boundary (head+rr)/rows, not the center. With Nearest filtering, fp rounding at an exact boundary can floor to the neighbouring row (off-by-one row sampling). Either add the + 0.5/rows the comment promises or drop the comment.

4. Background "Clear → disable" looks out of scope.
The right-click-Clear-disables-background change (backgroundImageDisabled signal, the "none" persistence in MainWindow_Wiring.cpp, and the bgPath != "none" guard in MainWindow_Session.cpp) is unrelated to the stacked-trace render mode. It's reasonable on its own, but it's a distinct user-facing behaviour that isn't mentioned in the PR description — consider splitting it out or at least documenting it so it's reviewed on its own merits.

None of these block the 2D path (unchanged) or compilation. (1) and (2) are the ones I'd most want resolved since they affect the actual 3D output. Thanks for the detailed write-up and the cross-platform care.


🤖 aethersdr-agent · cost: $6.0567 · model: claude-opus-4-8

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review (8 finder angles + verify). Nice feature — a GPU height-map mesh with a CPU fallback. Requesting changes for two user-visible correctness issues and a persistence bug; the rest are consistency/perf. Most fixes here are design-level rather than one-liners, so the inline notes are prescriptive fixes (one has an applicable suggestion).

Blockers:

  • #1 Overlays + click-to-tune are wrong in 3D. Markers, squelch/auto-SQL lines, and band-plan labels are drawn over the receding-trapezoid surface using the 2D linear-x / dynamic-range-y mappings, and click-to-tune maps screen-x→freq linearly. In 3D they all point at the wrong place. Either project them through the same trapezoid the shader uses, or suppress freq/dB-anchored overlays in 3D.
  • #2 GPU height texture only uploads the current ring head, so when the FFT rate exceeds the frame rate the rows pushed between frames render stale. Upload every row from the last-uploaded head to the current head.
  • #4 3D mode/floor don't survive a session reload (wirePanLifecycle omits them) and the syncDisplaySettings() default-arg trap desyncs the controls.

Also worth addressing: the GPU palette LUT ignores the color-gain/black-level/min-dBm controls (#3); the CPU fallback renders a different surface than the GPU path — it omits the zCurve lift and uses a different depth parametrization (#5); switching into 3D starts blank (history only fed in 3D) and KiwiSDR uses a different floor anchor (#6); the software path rebuilds an uncapped full-res QImage every frame (#7, suggestion inline); and the mesh samples texels on their boundaries rather than centers (#8).

Minor (not inlined): GPU mode resources stay resident for the session after the first 3D visit; the perspective constants are triplicated (DssRenderer constexpr / SpectrumWidget magic floats / shader uniforms) and the floor-anchor math is copy-pasted CPU↔GPU (will drift); the hand-maintained 12 * sizeof(float) UBO size and kCols-vs-QVarLengthArray<…,1024> coupling are silent footguns; the full 2D FFT-trace vertex pipeline still runs (and is discarded) every 3D frame; and braces-on-control-flow (AGENTS.md C++ style) is omitted throughout the new DssRenderer.cpp / SpectrumWidget.cpp code.

Comment thread src/gui/SpectrumWidget.cpp Outdated
Comment thread src/gui/SpectrumWidget.cpp Outdated
Comment thread src/gui/SpectrumWidget.cpp
Comment thread src/gui/MainWindow_Session.cpp
Comment thread src/gui/DssRenderer.cpp
Comment thread src/gui/SpectrumWidget.cpp Outdated
Comment thread src/gui/SpectrumWidget.cpp
Comment thread resources/shaders/dss_mesh.vert
Add a perspective stacked-trace spectrum stream as a panadapter render mode,
selectable from the Display pane (Spectrum: 2D / 3D). In 3D the spectrum region
becomes a rolling history of FFT traces receding into the distance, drawn over
the existing waterfall; the 2D path is unchanged. Built on the existing Qt +
QRhi infrastructure and designed for macOS, Windows, and Linux.

Features:
- Perspective stacked-trace surface: 96-row x 768-col rolling history, newest
  trace full-width at the front, older traces receding into a narrower/higher
  trapezoid, painter's-algorithm occlusion.
- GPU height-map mesh: a static vertex grid samples a ring-buffered height
  texture, so pan/zoom/resize rebuild no geometry (fluid pan); one ~1.5 KB
  texture row uploaded per frame instead of re-rasterizing the surface.
- Noise-floor-anchored amplitude with a floor->peak colour gradient and a
  non-linear floor-expansion curve, so the floor is visible and coloured up
  through the peaks. Palette follows the waterfall colour scheme.
- Dim depth-faded per-row trace outline.
- Broadband impulse rejection (temporal median) so a 1-frame interference burst
  never enters the height history as a full-height receding artifact.
- Overlays preserved: spots, memories, slice/VFO markers, nav hints, band plan,
  and the scales render as in 2D (the surface replaces only the spectrum trace).
- Display-pane controls (persisted per-panadapter): the 2D/3D selector and a
  "3D Floor" depth slider.

Cross-platform:
- No platform-specific shader code. The new GLSL 440 shaders bake via qsb to
  SPIR-V / GLSL / HLSL / MSL (same variant set as the existing spectrum shaders).
- Backend selection unchanged: Metal on macOS (#ifdef Q_OS_MAC), platform
  default elsewhere (D3D11 / OpenGL-Vulkan); the surface rides the same
  QRhiWidget.
- Portable choices: no geometry/tessellation shaders, occlusion by draw order
  (no depth-attachment dependency), 1-px lines, R16F guarded by a capability
  check.
- Graceful degradation: GPU mesh -> GPU cached-image quad (RGBA8) -> CPU
  QPainter surface (also covers builds where the GPU spectrum is compiled out).

Verified live RX on macOS/Metal (dense/smooth surface, floor visible with
gradient, dim outline, fluid pan, impulse rejection; full FFT rate, ~1 ms UI
lag, 0% frame loss; 2D round-trips unchanged) and the impulse rejection with a
standalone render harness. Windows/Linux runtime not yet eyeballed; CI covers
the build + shader bake.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jensenpat jensenpat force-pushed the feat/stacked-trace-spectrum branch from 7fff14e to b803a41 Compare June 30, 2026 06:53
@jensenpat

jensenpat commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks both for the thorough review — all points addressed in b803a415 (force-pushed; CI re-running). Live-verified on a FLEX-8400M: 3D mesh renders correctly, floor knob works, dB overlays gone in 3D, 2D round-trips, panFps 25 / 0% FFT loss / gpuOverlay=25 (confirming the catch-up upload). What changed:

Blockers

  • Overlays / click-to-tune in 3D. The dB (vertical) axis genuinely doesn't map onto the surface, so I suppress the dB-anchored overlays in both paths — squelch line, auto-SQL floor, and the dBm scale strip. The frequency (x) axis is well-defined in 3D: the surface's full-width front edge is the linear axis the bottom frequency scale already reads, i.e. exactly mhzToX. So I kept the frequency-anchored overlays (spots, memories, slice/VFO markers, band plan) — they line up with the bottom scale, and keeping spots/memories visible in 3D is a hard product requirement here. Click-to-tune likewise stays (linear x→freq = the bottom scale; clicking tunes the frequency column you click). Happy to project markers along the receding trapezoid as a follow-up if you'd prefer that over front-edge alignment.
  • Stale rows when the FFT rate outruns the frame rate. Now a catch-up loop uploads every ring slot from m_dssMeshHeadUploaded up to the current head (contiguous mod kRows), batched, instead of just the newest.
  • Persistence + the syncDisplaySettings default-arg trap. wirePanLifecycle now restores DisplaySpectrumRenderMode + Display3DFloorDepth per pan; the KiwiSDR caller (MainWindow_KiwiSdr.cpp) and Reset-Display (MainWindow_Wiring.cpp) now pass the live spectrumRenderMode()/dssFloorDepth(), and Reset also calls setSpectrumRenderMode(0)/setDssFloorDepth(6) + persists the keys.

Consistency / perf

  • GPU palette ignored the colour controls. The LUT is now baked through dbmToRgb() (entry i = colour of floor + (i/255)*range) and keyed on dssPaletteToken() + the quantised floor/range, so gain/black-level/min-dBm reach the GPU and GPU↔CPU colours match. The vertex shader outputs the linear strength as the LUT index (the zCurve lift applies to height only), so LUT[s] = dbmToRgb(dbm) exactly matches the CPU palette(dbm).
  • CPU ↔ GPU render parity. DssRenderer::rebuild() now applies the same pow(s, zCurve) floor-lift and the same depth parametrization (v = rr/rows). The perspective constants (kBackWidthFrac / kDepthSpanFrac / kFrontMaxRidgeFrac / kHaze) are now public static constexpr on DssRenderer and read by both the CPU renderer and the GPU UBO; the floor/range math is centralised in dssFloorDbm() / dssSpanDb(). (Slope-shading/outline styling on the CPU path remain a cosmetic extra the GPU frag doesn't replicate.)
  • Blank-on-entry + KiwiSDR floor anchor. The ring is now fed every frame (Flex via updateSpectrum, KiwiSDR via updateKiwiSdrWaterfallRow), so toggling to 3D shows a populated surface immediately. dssFloorDbm() uses the measured floor of the active source (Flex m_measuredNoiseFloorDbm or m_kiwiSdrAutoFloorDbm), so the 3D-Floor slider behaves the same on both.
  • Software 3D resolution cap. Applied the suggestion — the CPU surface is built at specRect.size().boundedTo(1024×512) and stretched on draw.
  • Texel-centre sampling. rowOffset now carries the row half-texel ((head+0.5)/rows), and columns sample centres (u*(cols-1)+0.5)/cols via a new texCols uniform (geometry u stays [0,1]).

Minors

  • Perspective constants de-triplicated (shared DssRenderer constants; shader comments point back to them).
  • UBO size is a named kDssMeshUboFloats used by both the buffer create and the writer, with static_assert; the fixed QVarLengthArray<…,1024> is gone (per-row QByteArray + entries sized to kRows).
  • The full 2D FFT-trace build + vertex write is now skipped in 3D (was built and discarded each frame).
  • Braces added on all control flow in the new DssRenderer.cpp and the new SpectrumWidget logic (AGENTS.md §C++).
  • GPU mesh resources are allocated once at QRhi init and kept resident (small; avoids re-alloc churn when toggling 2D⇄3D) — left intentional; can lazy-init/free on mode change if you'd rather.

Background "Clear → disable" scope. Documented in the PR description (it's a small, self-contained Display-pane affordance) rather than split out, since it's already squashed into this one commit — happy to break it into its own PR if you'd prefer.

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Hi @jensenpat — thanks for this, the stacked-trace surface is a genuinely cool addition and the cross-platform care in the PR description shows. 👏 Quick hand from CI triage on the red build.

What failed

On commit b803a41:

Job Result
build (Linux, Qt 6.8.3 / GCC) ❌ fails at the Build (compile) step
analyze (cpp) (Linux, same container) ❌ fails at the same compile step → CodeQL reports neutral because its build prerequisite never finished
check-macos ✅ green
check-windows ⏳ still running when I looked
Qt Accessibility static analysis ✅ green

So this is a Linux-only compile error, identical across the two Linux jobs (they share ghcr.io/ten9876/aethersdr-ci + the Qt 6.8.3 install). It's not a test or logic failure — the object files never finished building.

Why Linux only (and what it's not)

I want to head off a tempting-but-wrong theory: this is not the usual "GPU spectrum compiled out on old Linux Qt" divergence. AETHER_GPU_SPECTRUM auto-disables below Qt 6.7 (CMakeLists.txt:290-295), but the Linux build job pins Qt 6.8.3 via install-qt-action (.github/workflows/ci.yml:71), so AETHER_GPU_SPECTRUM is ON on Linux just like on macOS. The GPU mesh path, qfloat16 upload, and RHI code all compile on both. I confirmed qfloat16 and the mesh code are correctly inside the AETHER_GPU_SPECTRUM guard, so the include leak / guard-leak class is ruled out.

That leaves the most common reason this exact repo goes "green on my Mac, red on the Linux container": GCC + libstdc++ is stricter than Clang + libc++. libc++ transitively pulls in a lot of standard headers that libstdc++ does not, and GCC rejects a few constructs Clang accepts. The only new translation units in this PR are src/gui/DssRenderer.cpp, src/gui/DssRenderer.h, and the always-compiled DSS additions in src/gui/SpectrumWidget.cpp, so the error is in one of those. There's no global -Werror, so it's a real hard error, not a promoted warning.

Most likely culprits, in rough order:

  1. A missing standard #include that libc++ provided implicitly — e.g. <cstdint>, <limits>, or a <cmath>/<algorithm> symbol used in a TU that doesn't include it. DssRenderer.cpp does include <algorithm>/<cmath>, but double-check the new DSS blocks in SpectrumWidget.cpp (e.g. std::round, std::clamp near the height-texture upload) are in a TU that actually includes those headers rather than relying on a transitive macOS pull-in.
  2. A type/narrowing or template-deduction mismatch GCC rejects — e.g. int n = binsDbm.size() is a qsizetypeint narrowing, and std::clamp/std::min/std::max calls that mix float and double literals fail template deduction on GCC if the argument types don't line up exactly.

Getting the exact line

The fastest path is the failed-job log directly — the compiler prints file:line: error: with the precise diagnostic:

To reproduce locally, build against GCC + Qt ≥ 6.7 (the macOS/Clang build hides it). If you only have a Mac handy, a quick container repro:

docker run --rm -it -v "$PWD":/src ghcr.io/ten9876/aethersdr-ci:latest \
  bash -lc 'cd /src && cmake -B build-linux -G Ninja && cmake --build build-linux'

(You'll want the Qt 6.8.3 install the workflow does, or point -DCMAKE_PREFIX_PATH at a 6.7+ gcc_64 kit.)

On the Copilot review comments

Worth flagging so you don't chase them as the build break: the Copilot notes (overlay projection through the 2D axes in 3D, the stale-ring catch-up upload, the palette LUT ignoring color-gain/black-level, per-pan render-mode persistence, the syncDisplaySettings callers in MainWindow_KiwiSdr.cpp/MainWindow_Wiring.cpp not passing the new params, CPU-vs-GPU surface parity, and the uncapped software 3D resolution) are all runtime/correctness/quality observations — none of them are the compile error. They're solid follow-ups for a green build, but the Linux compile has to pass first. A couple (the KiwiSDR/Reset-Display callers, and the software-path resolution cap) are cheap and worth folding into the same fix-up.

Once Linux compiles, please re-push and I'll keep an eye on check-windows completing too. Happy to take another look at the log if the fix isn't obvious. Thanks again for volunteering your time on this — it's a nice feature. 🙂


🤖 aethersdr-agent · cost: $9.1184 · model: claude-opus-4-8

@ten9876 ten9876 dismissed their stale review June 30, 2026 15:06

changes applied

ten9876 and others added 2 commits June 30, 2026 10:50
The 3D surface rendered near-monochrome: the palette LUT was baked through
dbmToRgb(), whose waterfall black-level window forces everything below
~(min + (125-black)*0.4) dBm to black. Since the surface spans down to the
noise floor, the bottom ~30% of every curtain was clipped to black and only
the strongest signals showed any colour — white trace lines over black.

- Map the surface's strength axis (noise floor -> ref) across the FULL
  colormap gradient instead, via a shared dssStrengthToRgb() helper used by
  both the GPU LUT bake and the CPU fallback (buildDssImage), so both paths
  colour identically.
- Add a user-adjustable "3D Gain" slider (0-100, default 70) to the Display
  overlay menu: gamma-shapes the strength->colour lookup so colour can be
  pulled down toward the noise floor or kept on strong signals only.
  Persisted per-panadapter (Display3DGain), with reset wiring.

Also addresses review nits in the same code:
- null-guard the CPU fallback against a partial GPU init (m_dssGpuTex/SRB/
  sampler) so it can't deref null on OOM/device-loss;
- reuse a member qfloat16 row buffer for the mesh height upload (COW-safe;
  no per-frame allocation in the common single-new-row case);
- route the 3DSS-path warnings through qCWarning(lcGui) instead of bare
  qWarning();
- hoist the duplicated kDssMaxW/kDssMaxH caps to one class constant;
- document the GUI-thread-only access invariant on m_dss.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CI build job compiles with AETHER_GPU_SPECTRUM off (Qt < 6.7), where
the CPU paint path still builds the 3DSS surface but the GPU-guarded members
it referenced were compiled out:

- m_dssZCurve was inside the #ifdef block yet used by buildDssImage() (CPU) —
  a pre-existing break for GPU-off builds; moved it out alongside m_dss.
- kDssMaxW/kDssMaxH (this branch hoisted them into the guard) are used by the
  CPU paintEvent fallback; moved them out too.
- setDssGain() reset m_dssLutToken (a GPU-only member) unconditionally; guarded
  that line — the CPU surface re-colours via m_dss.invalidate() regardless.

Verified SpectrumWidget.cpp now compiles cleanly both with and without
AETHER_GPU_SPECTRUM.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved by the maintainer (Jeremy / KK7GWY). 3D stacked-trace render mode: colour mapping fixed (full colormap across the strength axis), user-adjustable 3D Gain control added, review nits addressed, and the GPU-off build (pre-existing break) fixed. CI fully green.

@ten9876 ten9876 merged commit 145c1e6 into aethersdr:main Jun 30, 2026
6 checks passed
ten9876 added a commit that referenced this pull request Jul 2, 2026
#3961)

## Release documentation prep for **v26.7.1** (2026-07-02)

29 commits since v26.6.5. Docs-only + version bump — no code behavior
change.

### Version bump 26.6.5 → 26.7.1
The three canonical locations (per `AGENTS.md`):
- `CMakeLists.txt:2` — `project(AetherSDR VERSION 26.7.1)` →
`AETHERSDR_VERSION` (the app's version string)
- `README.md` — Current version line
- `AGENTS.md` — Current version line

### `CHANGELOG.md`
Promoted `[Unreleased]` → **`## [v26.7.1] — 2026-07-02`** in house style
(v-prefix, em-dash, "N commits since…" opener + headline), authored from
all merged PRs and categorized:
- **Added:** 3D stacked-trace spectrum (#3899), 3D dBm scale (#3937),
in-process NVIDIA BNR + AFX download-on-demand (#3902), TX meter
mouse-over readouts (#3936), SWR manual sweep range (#3885), CW sidetone
capture (#3895), KiwiSDR metadata scaffolding (#3898), bridge verbs
(#3920)
- **Changed:** BNR container/NIM backend removed, 60 fps + per-pixel GPU
FFT trace (#3958), FlexLib-sourced model capabilities (#3954/#2177), RX
speaker-latency cut (#3897), Display pane sections (#3935)
- **Fixed:** #3922, #3926, #3941, #3940, #3942, #3921, #3903, #3892,
#3924, #3891, #3890, #3900, #3947
- Fresh empty `[Unreleased]` restored above the release block.
Internal/CI/docs/self-fix PRs (#3931/#3916/#3934/#3929) omitted per
house style.

### `ROADMAP.md`
- Cycle header → "post-v26.7.1"
- NVIDIA BNR removed from **In flight** (shipped this cycle)
- Five v26.7.1 highlights prepended to **Recently shipped**

### `README.md`
- Highlights: GPU spectrum bullet now notes the **per-pixel FFT trace at
up to 60 fps** and the **3D stacked-trace** spectrum mode

### Reviewer notes
- Touches protected paths (`*.md` + `AGENTS.md` Tier-1 +
`CMakeLists.txt`) — needs `@aethersdr/infrastructure` sign-off.
- Tagging `v26.7.1` and cutting the release build are **not** part of
this PR (docs prep only).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC: 3-D stacked-trace spectrum panadapter render mode

2 participants