Skip to content

Commit 8710f58

Browse files
ten9876claude
andauthored
GPU-accelerated spectrum/waterfall rendering via QRhi (#391) (#698)
* Phase 1: QRhiWidget proof of life for GPU spectrum (#391) SpectrumWidget conditionally inherits QRhiWidget when AETHER_GPU_SPECTRUM is enabled. Renders solid dark background via beginPass/endPass. Child widgets (VfoWidget, OverlayMenu, zoom buttons) composite correctly on top of the QRhi surface. - CMake: AETHER_GPU_SPECTRUM option with Qt6::GuiPrivate, Qt6::ShaderTools - Conditional base class via SPECTRUM_BASE_CLASS macro - paintEvent wrapped in #ifndef, QRhi stubs in #ifdef - Validated: renders, child widgets visible, mouse events work Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Phase 2: GPU waterfall + overlay rendering via QRhi (#391) Waterfall QImage uploaded as RGBA8 texture, rendered as textured quad with ring buffer scrolling via fragment shader fract(uv.y + offset). All overlays (grid, FFT, band plan, slices, TNF, spots, scales) painted into an overlay QImage via QPainter, composited as alpha-blended texture. Key fix: fract() must be in the fragment shader, not vertex shader. Per-vertex fract() linearizes between two identical values when the UV range spans exactly 1.0 (fract(0+offset) == fract(1+offset)), producing constant UV across the quad. This was likely the same bug that killed the previous GPU attempt. Shaders compiled at build time via qt_add_shaders() — no binary .qsb files in the repo, shader errors are build errors not silent runtime failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Phase 2: Incremental waterfall row uploads — 97% → 40% CPU (#391) Replace full 3.5MB QImage upload per frame with per-row incremental uploads (~6KB each). Track last uploaded row, walk the ring buffer delta, upload only changed rows with setDestinationTopLeft(). CPU reduction: 97% (QPainter) → 52% (full upload) → 40% (incremental). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Phase 2: Static overlay caching — 97% → 35% CPU (#391) Split overlay into static (grid, band plan, scales, slices, TNF, spots) and dynamic (FFT spectrum line) layers. Static overlay only repainted when markOverlayDirty() is called from state-change methods. Per-frame work: memcpy cached static, paint FFT on top, upload composite. Replaced all update() calls with markOverlayDirty() except the two data paths (updateSpectrum, updateWaterfallRow) which only need a render, not a static overlay repaint. CPU progression: 97% (QPainter) → 52% (full upload) → 40% (incremental waterfall) → 35% (static overlay caching). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix non-GPU build: move markOverlayDirty() outside #ifdef markOverlayDirty() was declared inside #ifdef AETHER_GPU_SPECTRUM but called from non-guarded code. The method handles both paths internally so it must be visible regardless of the GPU flag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Enable GPU spectrum by default, add CI dependencies AETHER_GPU_SPECTRUM now defaults to ON for shipped binaries. Added qt6-base-private-dev, qt6-shader-baker, and qt6-shadertools-dev to the CI Docker image. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Graceful fallback when Qt6::ShaderTools not available GPU spectrum defaults to ON but falls back to OFF if Qt6::ShaderTools or Qt6::GuiPrivate aren't found, instead of failing the build. Allows CI to build successfully while the Docker image is being updated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert graceful fallback — GPU rendering is required Qt6::ShaderTools and Qt6::GuiPrivate must be present. Build fails loudly if missing, forcing the CI image to be updated rather than silently shipping without GPU acceleration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add missing Qt modules to all release workflows All release builds (AppImage, Windows, macOS DMG, macOS PKG) were missing qtwebsockets (TCI server), qtshadertools (GPU spectrum). ARM AppImage also missing serialport, hidapi, fftw3, portaudio, qt6-base-private-dev. This is why TCI wasn't in shipped binaries — the aqtinstall modules list only had qtmultimedia and qtserialport. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update build dependencies with full feature list All packages listed for Arch, Ubuntu, Fedora, macOS with collapsible table explaining what each dependency enables. Includes GPU spectrum (qt6-shadertools), TCI (qt6-websockets), StreamDeck (hidapi), NR2 (fftw3) and all other auto-detected features. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Auto-disable GPU spectrum on Qt < 6.7 QRhiWidget requires Qt 6.7+. CI Docker image has Qt 6.4 (Ubuntu 24.04) which doesn't have QRhiWidget. GPU spectrum now auto-disables on older Qt instead of failing the build. Release workflows use Qt 6.7 via aqtinstall and get GPU enabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5eaa35d commit 8710f58

13 files changed

Lines changed: 675 additions & 44 deletions

.github/docker/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ RUN apt-get update && apt-get install -y \
1818
qt6-multimedia-dev \
1919
qt6-websockets-dev \
2020
qt6-serialport-dev \
21+
qt6-base-private-dev \
22+
qt6-shader-baker \
23+
qt6-shadertools-dev \
2124
libgl1-mesa-dev \
2225
libasound2-dev \
2326
libfftw3-dev \

.github/workflows/appimage.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,17 @@ jobs:
4242
- name: Install system Qt (ARM only)
4343
if: matrix.use_aqt == false
4444
run: |
45-
sudo apt-get install -y qt6-base-dev qt6-multimedia-dev
45+
sudo apt-get install -y qt6-base-dev qt6-multimedia-dev \
46+
qt6-websockets-dev qt6-serialport-dev qt6-base-private-dev \
47+
qt6-shader-baker qt6-shadertools-dev \
48+
libhidapi-dev portaudio19-dev libfftw3-dev
4649
4750
- name: Install Qt 6.7 via aqtinstall (x86_64)
4851
if: matrix.use_aqt == true
4952
run: |
5053
pip3 install aqtinstall
5154
aqt install-qt linux desktop 6.7.3 linux_gcc_64 \
52-
-m qtmultimedia \
55+
-m qtmultimedia qtwebsockets qtserialport qtshadertools \
5356
--outputdir ${{ github.workspace }}/Qt
5457
echo "Qt6_DIR=${{ github.workspace }}/Qt/6.7.3/gcc_64/lib/cmake/Qt6" >> $GITHUB_ENV
5558
echo "CMAKE_PREFIX_PATH=${{ github.workspace }}/Qt/6.7.3/gcc_64" >> $GITHUB_ENV

.github/workflows/macos-dmg.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
host: 'mac'
2323
target: 'desktop'
2424
arch: 'clang_64'
25-
modules: 'qtmultimedia qtserialport'
25+
modules: 'qtmultimedia qtserialport qtwebsockets qtshadertools'
2626

2727
- name: Install build tools
2828
run: brew install ninja create-dmg autoconf automake libtool

.github/workflows/macos-installer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
host: 'mac'
2525
target: 'desktop'
2626
arch: 'clang_64'
27-
modules: 'qtmultimedia qtserialport'
27+
modules: 'qtmultimedia qtserialport qtwebsockets qtshadertools'
2828

2929
- name: Install build tools
3030
run: brew install ninja autoconf automake libtool

.github/workflows/windows-installer.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
host: 'windows'
2626
target: 'desktop'
2727
arch: 'win64_msvc2019_64'
28-
modules: 'qtmultimedia qtserialport'
28+
modules: 'qtmultimedia qtserialport qtwebsockets qtshadertools'
2929

3030
- name: Install Ninja and Inno Setup
3131
run: choco install ninja innosetup -y

CMakeLists.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,23 @@ else()
161161
endif()
162162
endif()
163163

164+
# GPU-accelerated spectrum/waterfall rendering via QRhi (#391)
165+
# Requires: Qt 6.7+ (QRhiWidget), Qt6::ShaderTools (build-time shader compilation)
166+
# Defaults to ON but auto-disables on Qt < 6.7 (e.g. Ubuntu 24.04 ships Qt 6.4).
167+
option(AETHER_GPU_SPECTRUM "Enable QRhi GPU spectrum rendering" ON)
168+
if(AETHER_GPU_SPECTRUM)
169+
if(Qt6_VERSION VERSION_LESS "6.7")
170+
set(AETHER_GPU_SPECTRUM OFF)
171+
message(STATUS "GPU spectrum rendering disabled (requires Qt 6.7+, found ${Qt6_VERSION})")
172+
else()
173+
find_package(Qt6 REQUIRED COMPONENTS ShaderTools)
174+
find_package(Qt6GuiPrivate QUIET)
175+
message(STATUS "GPU spectrum rendering enabled (QRhi, Qt ${Qt6_VERSION})")
176+
endif()
177+
else()
178+
message(STATUS "GPU spectrum rendering disabled (use -DAETHER_GPU_SPECTRUM=ON to enable)")
179+
endif()
180+
164181
# NVIDIA NIM BNR (Background Noise Removal) — GPU-accelerated neural denoising
165182
# Requires: grpc++, protobuf, NVIDIA RTX 4000+ GPU, Docker container
166183
option(ENABLE_BNR "Enable NVIDIA NIM BNR noise removal (requires grpc++)" OFF)
@@ -557,6 +574,21 @@ if(ENABLE_BNR)
557574
target_link_libraries(AetherSDR PRIVATE ${GRPC_LIBRARIES} ${PROTOBUF_LIBRARIES})
558575
endif()
559576

577+
if(AETHER_GPU_SPECTRUM)
578+
target_compile_definitions(AetherSDR PRIVATE AETHER_GPU_SPECTRUM)
579+
if(TARGET Qt6::GuiPrivate)
580+
target_link_libraries(AetherSDR PRIVATE Qt6::GuiPrivate)
581+
endif()
582+
qt_add_shaders(AetherSDR "aether_shaders"
583+
PREFIX "/shaders"
584+
FILES
585+
resources/shaders/texturedquad.vert
586+
resources/shaders/texturedquad.frag
587+
resources/shaders/overlay.vert
588+
resources/shaders/overlay.frag
589+
)
590+
endif()
591+
560592
if(HAVE_PIPEWIRE)
561593
target_compile_definitions(AetherSDR PRIVATE HAVE_PIPEWIRE)
562594
message(STATUS "Linux DAX bridge enabled (PulseAudio pipe modules)")

README.md

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,51 @@ Pre-built binaries are available from [Releases](https://github.com/ten9876/Aeth
6666

6767
## Building from Source
6868

69-
### Dependencies (Linux)
69+
### Dependencies
70+
71+
Install all dependencies for a full-featured build. Optional packages are noted — the build succeeds without them but the corresponding features are disabled.
7072

7173
```bash
7274
# Arch / CachyOS / Manjaro
73-
sudo pacman -S qt6-base qt6-multimedia cmake ninja pkgconf autoconf automake libtool
75+
sudo pacman -S qt6-base qt6-multimedia qt6-websockets qt6-serialport \
76+
qt6-shadertools cmake ninja pkgconf autoconf automake libtool \
77+
fftw portaudio hidapi qtkeychain-qt6
7478

7579
# Ubuntu 24.04+ / Debian / Linux Mint
76-
sudo apt install qt6-base-dev qt6-multimedia-dev cmake ninja-build pkg-config \
77-
autoconf automake libtool gstreamer1.0-pulseaudio gstreamer1.0-plugins-base \
78-
libxkbcommon-dev portaudio19-dev libfftw3-dev
80+
sudo apt install qt6-base-dev qt6-base-private-dev qt6-multimedia-dev \
81+
qt6-websockets-dev qt6-serialport-dev qt6-shader-baker qt6-shadertools-dev \
82+
cmake ninja-build pkg-config autoconf automake libtool \
83+
libfftw3-dev portaudio19-dev libhidapi-dev qtkeychain-qt6-dev \
84+
libxkbcommon-dev gstreamer1.0-pulseaudio gstreamer1.0-plugins-base
7985

8086
# Fedora
81-
sudo dnf install qt6-qtbase-devel qt6-qtmultimedia-devel cmake ninja-build \
82-
autoconf automake libtool
87+
sudo dnf install qt6-qtbase-devel qt6-qtbase-private-devel qt6-qtmultimedia-devel \
88+
qt6-qtwebsockets-devel qt6-qtserialport-devel qt6-qtshadertools-devel \
89+
cmake ninja-build autoconf automake libtool \
90+
fftw3-devel portaudio-devel hidapi-devel qtkeychain-qt6-devel
8391

8492
# macOS (Homebrew)
85-
brew install qt@6 ninja cmake pkgconf autoconf automake libtool
93+
brew install qt@6 ninja cmake pkgconf autoconf automake libtool \
94+
fftw portaudio hidapi qtkeychain
8695
```
8796

97+
<details>
98+
<summary>What each dependency enables</summary>
99+
100+
| Package | Feature |
101+
|---------|---------|
102+
| qt6-base, qt6-multimedia | Core application (required) |
103+
| qt6-base-private-dev | GPU-accelerated spectrum/waterfall (QRhi) |
104+
| qt6-shadertools-dev | GPU shader compilation |
105+
| qt6-websockets-dev | TCI server, FreeDV Reporter spots |
106+
| qt6-serialport-dev | FlexControl, serial PTT/CW, MIDI controllers |
107+
| libfftw3-dev | NR2 spectral noise reduction |
108+
| portaudio19-dev | PortAudio audio backend |
109+
| libhidapi-dev | StreamDeck, USB HID encoders (RC-28, PowerMate) |
110+
| qtkeychain-qt6-dev | SmartLink credential persistence |
111+
112+
</details>
113+
88114
> **Linux Mint / Ubuntu note:** If PC audio devices show as "Dummy Output",
89115
> install `gstreamer1.0-pulseaudio`. For PipeWire systems, also install `gstreamer1.0-pipewire`.
90116

resources/shaders/overlay.frag

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#version 440
2+
3+
layout(location = 0) in vec2 v_uv;
4+
layout(location = 0) out vec4 fragColor;
5+
6+
layout(binding = 1) uniform sampler2D tex;
7+
8+
void main()
9+
{
10+
fragColor = texture(tex, v_uv);
11+
}

resources/shaders/overlay.vert

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#version 440
2+
3+
layout(location = 0) in vec2 position;
4+
layout(location = 1) in vec2 texcoord;
5+
6+
layout(location = 0) out vec2 v_uv;
7+
8+
void main()
9+
{
10+
gl_Position = vec4(position, 0.0, 1.0);
11+
v_uv = texcoord;
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#version 440
2+
3+
layout(location = 0) in vec2 v_uv;
4+
layout(location = 0) out vec4 fragColor;
5+
6+
layout(binding = 1) uniform sampler2D tex;
7+
8+
layout(std140, binding = 0) uniform Uniforms {
9+
float rowOffset;
10+
float padding1;
11+
float padding2;
12+
float padding3;
13+
};
14+
15+
void main()
16+
{
17+
// Apply ring buffer offset per-pixel (not per-vertex, to avoid fract() interpolation issue)
18+
vec2 uv = vec2(v_uv.x, fract(v_uv.y + rowOffset));
19+
fragColor = texture(tex, uv);
20+
}

0 commit comments

Comments
 (0)