Skip to content

perf: main window 10–15 fps — GUI thread bound on Qt rhiFlush backing-store composite (multi-panadapter) #3797

Description

@ten9876

Summary

The main window runs at 10–15 fps with multiple panadapters open, despite high-end hardware. CPU profiling shows the app is single-threaded-bound on the GUI thread in Qt's RHI backing-store composite path — not in AetherSDR's own logic (only ~2% of samples) and not GPU-starved. This issue captures the profile; the chosen fix is gated on an in-flight investigation of Qt's rhiFlush backing-store behavior.

Repro / environment

  • Radio: FlexRadio FLEX-8600, 4 panadapters (4 slices), 20m.
  • Build: RelWithDebInfo (-O2 -g -DNDEBUG), current main (post-v26.6.4).
  • RHI backend: OpenGL (SpectrumWidget: QRhi initialized, backend: OpenGL). On Linux we never call QRhiWidget::setApi(), so it defaults to OpenGL.
  • Hardware: i9-13980HX (32 threads), RTX 4090 Laptop + Intel UHD (hybrid), KDE Wayland. Process holds NVIDIA render-node fds → rendering on the 4090, GPU not starved.
  • Symptom: ~10–15 fps on the main window (measured via MangoHud).

Profile (perf, DWARF call-graph, main thread, ~18k samples / 6s)

Thread distribution: total ~105–112% CPU across 47 threads, but the GUI/main thread alone is ~79% of one core; every other thread (panadapter worker, audio, Qt pool) is ≤10%. Classic single-GUI-thread cap.

Main-thread self-time by library:

Library Self % What
libQt6Gui ~45% QImage copy / fill / blend (raster compositing)
libnvidia-eglcore ~29% NVIDIA GL driver — present / composite / texture upload
libc ~16% memcpy/memset behind the QImage ops
Qt6Core / Qt6Widgets ~4% event dispatch
AetherSDR (our code) ~2%

Call graph (caller view):

QApplicationPrivate::notify_helper (45%)
└─ QWidget::event (42.7%)
   ├─ QWidgetRepaintManager::paintAndFlush (31.8%)
   │  └─ flush → QPlatformBackingStore::rhiFlush (27.2%)
   │     ├─ QImage::detach() → QImage::copy(QRect const&)   ~15.2%   ← full backing-store stage copy / present
   │     └─ QRhi::endFrame → swapchain present              ~11.8%
   ├─ QRhiWidget::paintEvent → renderGpuFrame → QImage::fill(transparent)  ~7%  (appears ×2 → multiple panadapters)
   └─ QPushButton::paintEvent → QCommonStyle::drawControl   ~3.5%

Interpretation

With 4 QRhiWidget panadapters in one top-level window mixed with raster sibling widgets, Qt appears to repeatedly (a) copy/fill the raster backing store on the CPU via QImage (~61% across Qt6Gui + libc) and (b) push it through the NVIDIA GL composite/present (~29%) — per panadapter present. That CPU composite, not the GPU, is the 10–15 fps cap.

Two candidate fixes (pending investigation)

  1. Reduce the CPU QImage backing-store recopy (~61%). If Qt re-stages an unchanged full-window backing store on every RHI present (rather than dirty-rect / cached), guarding/caching that — or giving each panadapter its own native subsurface (WA_NativeWindow) / a single shared RHI surface so they bypass the shared raster backing-store composite — is the big win. Open question: is rhiFlush's copy full-window or dirty-rect, and is the staged texture cached across frames?
  2. Backend swap to Vulkan (~29%). Would attack the NVIDIA GL-driver present cost (and possibly some staging). Requires setApi(Vulkan) + a QVulkanInstance in main.cpp; not a launch-flag. May not touch the ~61% CPU QImage cost if that's backend-agnostic.

Status

A scoped deep-research investigation into Qt 6.11's exact rhiFlush backing-store / dirty-rect behavior is in flight to decide between fix #1 and #2 (and rank the mitigations: WA_NativeWindow per panadapter, per-panadapter top-level QWindow, single full-window RHI surface, WA_StaticContents, QOpenGLWidget vs QRhiWidget, Vulkan backend). Findings will be posted as a comment.

Prior related perf work: GPU-composited slice flags (#3617, #3695), flag re-raster idle-pause (#3746/#3764).

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUIUser interfacebugSomething isn't workingmaintainer-reviewRequires maintainer review before any action is takenmulti-panMulti-panadapter layout and slicingspectrumPanadapter and waterfall

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions