Skip to content

perf: reuse GPU FFT-trace vertex scratch buffers + guard 1-bin spectrum #3781

Description

@svabi79

Request preparation

  • I used an AI assistant to help structure this request
  • I checked for existing issues covering the same feature

What would you like?

Two small, low-risk hardening changes to the GPU FFT-trace vertex generation in SpectrumWidget::renderGpuFrame().

What.

  1. Reuse the three per-frame scratch buffers used to build the FFT trace (the line and fill vertex arrays plus the point array) instead of allocating and freeing them every frame.
  2. Guard the trace generation (and its draw) on n >= 2 bins.

Why.

  1. Per-frame allocation churn. Each rendered frame currently does:
    QVector<float> lineVerts(n * 2 * kFftVertStride);   // up to ~196k floats
    QVector<float> fillVerts(n * 2 * kFftVertStride);   // up to ~196k floats
    QVector<Pt>    pts(n);
    At kMaxFftBins = 8192 that is up to ~850 KB of heap alloc+free per frame, at the FFT frame rate, on the GUI thread. It is not a measurable frame-time cost today (the alloc is sub-millisecond — see below), so this is hygiene, not a perf win: it removes steady-state allocator traffic on the render thread and the occasional growth/fragmentation it can cause.
  2. Defensive correctness. For a 1-bin spectrum (n == 1) the generator divides by (n - 1) (→ ∞) in the position step and reads pts[1] (out of bounds) in the central-difference normal. n is always ≫ 2 in practice, so this is latent — but it is undefined behaviour at a data boundary and cheap to guard.

How.

  1. Hoist the three buffers to reused members (m_fftLineScratch / m_fftFillScratch / m_fftPtScratch); resize() keeps capacity, so after warm-up the steady state is allocation-free. Output is mechanically identical — every element is overwritten each frame exactly as before.
  2. Compute n before the guard and require n >= 2 for both the generation block and the draw block (so a degenerate spectrum neither generates nor draws stale vertices).

Measured

renderP95 unchanged at 0.3 ms (within noise — the allocation was already sub-millisecond), i9-13900K / RTX 4090, GPU path. The benefit is reduced GUI-thread heap traffic, not frame time — reported honestly.

(For the record: a parallel per-row allocation in updateWaterfallRow() was considered and deliberately not changed — m_prevTileScanline = scanline shares the buffer into a persistent member each row, so a member-hoist would detach (re-allocate) on the next resize() anyway, giving zero reuse, and wfUpdateP95 is only 0.1 ms for the whole function. Not worth the COW/aliasing risk.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUIUser interfaceenhancementImprovement to existing featuregood first issueGood for newcomersmaintainer-reviewRequires maintainer review before any action is takenspectrumPanadapter 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