Skip to content

APU HRTF Optimization

github-actions[bot] edited this page Sep 15, 2026 · 1 revision

APU HRTF Optimization

Lifecycle: Performance - Unproven · Type: Exact-oriented signal-processing optimization
Reviewed source: 322986f4c5502b3fa887a9605ac8cd88e9b2c579
Question: How much host time does the current HRTF path consume, and which exact transformations can remove indexing and scalar convolution overhead without changing output?

APU/DSP investigation directory · MCPX APU & Audio

Why this deserves a separate investigation

HRTF is not merely another voice flag. The current path performs a 31-tap convolution for both channels for every output sample, advances a circular history buffer, interpolates fractional interaural delay, and smooths every coefficient and the delay value toward a target.

At 32 samples per APU subframe and a nominal 1,500-subframe cadence, the convolution alone has a source-derived upper shape of:

31 taps × 2 channels × 32 samples × 1,500 subframes
≈ 2.98 million tap iterations per second per HRTF voice

That number is not measured CPU time. It does establish that HRTF has a repeated inner loop worthy of dedicated counters rather than being hidden inside a general worker investigation.

Source seams

  • hw/xbox/mcpx/apu/vp/hrtf.h
    • HRTF_NUM_TAPS = 31
    • HRTF_SAMPLES_PER_FRAME = NUM_SAMPLES_PER_FRAME
    • hrtf_filter_step_parameters()
    • hrtf_filter_process()
    • circular indexing and fractional-delay interpolation
  • hw/xbox/mcpx/apu/vp/vp.c
    • HRTF admission for 3D voices
    • target selection and surrounding filter/mix work
  • Audio configuration and worker scheduling determine how many active voices can reach the route.

Current execution flow

flowchart TD
    A[32-sample voice block] --> B{3D voice and HRTF enabled?}
    B -->|No| C[Continue normal filtering/mixing]
    B -->|Yes| D[Load HRTF target entry]
    D --> E[For each of 32 samples]
    E --> F[Smooth 62 coefficients and ITD]
    F --> G[For left and right channel]
    G --> H[Write new sample into circular history]
    H --> I[For each of 31 taps]
    I --> J[Modulo index history]
    J --> K[Optional second modulo index for fractional delay]
    K --> L[Interpolate sample and multiply-accumulate]
    L --> M[Advance circular position]
    M --> C
Loading

The modulo operations, parameter smoothing, interpolation branch, scalar multiply-adds, and history access all occur inside a route that may run for many voices.

Investigation sequence

1. Establish the maximum-cost bound

Run the same title and voice workload with HRTF enabled and disabled. This is not an equivalent-output performance comparison; it is a cost-removal experiment that bounds the amount of time the complete HRTF route could explain.

Record active HRTF voices and voice-worker distribution so a faster run is not confused with changed voice scheduling.

2. Mirror the history buffer

The current inner loop computes circular indices with % HRTF_BUFLEN. A mirrored history layout can provide a contiguous tap window.

typedef struct MirroredHrtfHistory {
    float data[2 * HRTF_BUFLEN];
    unsigned write_pos;
} MirroredHrtfHistory;

static inline void history_push(MirroredHrtfHistory *h, float sample)
{
    unsigned p = h->write_pos;
    h->data[p] = sample;
    h->data[p + HRTF_BUFLEN] = sample;
    h->write_pos = (p + 1 == HRTF_BUFLEN) ? 0 : p + 1;
}

A pointer can then address a contiguous reverse or prepared-order span without modulo per tap. The exact storage order should be selected to make both integer and fractional-delay source samples contiguous.

3. Add exact scalar and SIMD kernels

Keep one scalar reference implementation and add separately testable kernels:

float hrtf_convolve_scalar(const float *samples,
                           const float *coeff,
                           unsigned taps);
float hrtf_convolve_sse2(const float *samples,
                         const float *coeff,
                         unsigned taps);
float hrtf_convolve_neon(const float *samples,
                         const float *coeff,
                         unsigned taps);

The vector route must define its floating-point contract. Reordering additions changes rounding even when every input and coefficient is identical. Possible policies are:

  • bit-identical accumulation order, which limits vectorization;
  • bounded numerical difference with an explicit audio error metric;
  • platform-specific exactness only where the instruction sequence reproduces the scalar order.

Do not call a reordered reduction exact without evidence.

4. Separate stable-parameter work

The current code smooths all coefficients and ITD every sample. Measure how often target parameters actually change and how long they remain stable.

An exact stable route can be used when current values are exactly equal to target values:

if (params_exactly_stable(filter)) {
    process_fixed_coefficients(filter, in, out);
} else {
    process_smoothed_coefficients(filter, in, out);
}

Clamping a value that is merely close to the target is an approximation. It should be a separate experiment with spectral/error reporting, not silently folded into the exact route.

Candidate execution flow

flowchart TD
    A[HRTF voice block] --> B{Parameters exactly stable?}
    B -->|No| C[Reference smoothing route]
    B -->|Yes| D[Fixed-coefficient route]
    C --> E{Mirrored history available?}
    D --> E
    E -->|No| F[Reference circular scalar convolution]
    E -->|Yes| G{Qualified host SIMD kernel?}
    G -->|No| H[Contiguous scalar convolution]
    G -->|Yes| I[SSE/NEON convolution]
    F --> J[Output samples]
    H --> J
    I --> J
Loading

Every stage should be benchmarked independently: mirrored history, stable-parameter bypass, and SIMD remove different work.

What may disappear

  • Modulo arithmetic inside the tap loop.
  • Branching and a second history lookup when fractional delay is zero.
  • Per-sample parameter smoothing when parameters are exactly stable.
  • Scalar multiply-add loop overhead on qualified SIMD hosts.
  • Some worker time and APU frame utilization for HRTF-heavy scenes.

What remains mandatory

  • The same history and delay state after every processed sample.
  • Correct smoothing progression while targets are moving.
  • Correct fractional-delay interpolation.
  • Correct left/right delay sign and channel behavior.
  • Reset, voice reuse, pause, and snapshot history semantics.
  • A scalar reference route on unsupported hosts.
  • Deterministic handling of NaN, denormal, saturation/clamping, and host floating-point policy where they can appear.

Advantages

  • Strong repeated-loop structure with clear local timers.
  • Natural scalar oracle for SIMD and history-layout variants.
  • x86-64 and ARM64 can have independent kernels behind one contract.
  • Can improve a default-enabled, per-voice path without changing guest scheduling.
  • Supports incremental patches rather than one large rewrite.

Costs and risks

  • Real workloads may have few HRTF-active voices.
  • Reordered floating-point sums can alter audio subtly.
  • Mirrored history increases state and snapshot complexity.
  • Stable-parameter detection can cost more than it saves if checked poorly.
  • SIMD kernels require alignment, tail handling, and platform maintenance.
  • HRTF-disabled comparisons are not correctness comparisons.

Required counters

APU subframes
active 3D voices
HRTF-active voices
HRTF blocks and samples
parameter target changes
stable-parameter blocks
fractional-delay tap iterations
zero-fraction blocks
scalar tap iterations
SIMD tap iterations
history modulo operations
HRTF CPU time per voice/block
worker and whole APU frame time

Correctness fixtures

  • Unit impulse through each channel.
  • Fixed tones, noise, and chirps.
  • Static source with fixed coefficients and delay.
  • Moving source with rapid target changes.
  • Positive and negative ITD targets.
  • Integer and fractional delays near boundaries.
  • History-buffer wrap on every possible write position.
  • Voice reset/reuse and HRTF handle changes.
  • Snapshot save/restore in the middle of a transition.
  • Many simultaneous HRTF voices under worker scheduling.
  • x86-64 scalar/SIMD and ARM64 scalar/NEON comparisons.

For intended-exact paths, compare output samples and internal filter state. For approximate accumulation or convergence variants, report maximum absolute error, RMS error, spectral difference, and listening-test methodology.

Performance campaign

Use at least three workload classes:

no HRTF voices
one persistent HRTF voice
many HRTF voices with moving targets

Run worker counts 1/2/4/8/16 because a faster kernel can shift the worker crossover point. Report local HRTF time alongside whole APU utilization, vCPU progress, p95/p99 frame time, underruns, and total CPU use.

Acceptance rule

Promote an exact-oriented subexperiment to Researched when route counters show meaningful HRTF use, state/output comparisons pass for its declared arithmetic contract, and an uninstrumented Release build improves HRTF-heavy workloads without regressions elsewhere.

Rejection rule

Reject or narrow a route when HRTF is rarely reached, vector/reordered output exceeds the declared error contract, maintenance cost outweighs the measured gain, or improvements disappear at whole-emulator scale.

Related

Clone this wiki locally