Skip to content

APU Voice Worker Dispatch and Mix Reduction

OpenAI Codex edited this page Nov 1, 2026 · 2 revisions

APU Voice-Worker Dispatch and Mix Reduction

Reviewed: November 1, 2026 · Source: b7140848
Release availability: #201 is in v0.8.136-0-g458730bf5373 (September 30, 2026); Auto-four #200 and completion-wait #305 are main only at this review.

State: Auto-four defaults and reliability fixes delivered; targeted wakeups, cost-aware scheduling and touched-bin reduction remain research.
Question: When does parallel voice processing overcome worker wake, full-buffer clear, synchronization and central reduction costs?

APU/DSP investigation directory · MCPX APU & Audio

Current worker policy

Audio → Advanced → Voice processing workers offers Auto (up to 4) and explicit counts 1–16. audio.vp.num_workers=0 is Auto: the helper resolves host logical CPU count, clamps to a minimum of one and caps Auto at four. Explicit counts are clamped to 1–16. Restart xemu after changing this setting.

#200 changed the previous Auto cap of sixteen to four. It uses the normal sinc resampler and does not depend on held linear resampling #189. Closed/unmerged #316 is a separate upstream-handoff effort and does not undo the delivered fork policy. Voice workers process APU voices; xemu still exposes one Xbox guest CPU.

Worker-count helper · Configuration defaults · Audio UI.

Delivered reliability fixes

  • #201 unlocks the dispatch mutex before a worker exits. Condition waits return with that mutex owned, so exit must release it before teardown/join can complete.
  • #305 rechecks completion predicates after condition-variable wakes. Dispatch waits while workers_pending is nonzero; a wake alone is not proof that output is complete. Initialization similarly checks worker readiness.

These are correctness/reliability repairs, with no invented speedup percentage. The scheduler still broadcasts to the configured pool and clears/reduces full productive-worker matrices. It does not implement the larger scheduling proposal below.

Verified starting point

The VP owns a configurable worker pool. For a nonempty dispatch, queued voices are assigned to workers, the implementation broadcasts work_pending, and the APU thread waits for work_finished. Each productive worker clears a complete mix-bin matrix, processes its assigned voices, then adds every mixbin/sample entry into the central matrix while holding the dispatch mutex.

The source therefore combines useful parallel work with fixed costs:

schedule voices
broadcast to configured workers
worker wake / lock handoff
clear full 32 × 32 mix matrix per productive worker
process assigned voices
lock dispatch state
reduce full 32 × 32 matrix into central output
signal completion

At 32 samples per block and 48 kHz audio, nominal block cadence is 1,500 per second. Four Auto workers give an upper bound of 6,000 wake opportunities per second; an explicit sixteen-worker pool gives 24,000. These are code-derived opportunities for nonempty dispatches, not measured wake counts. Runtime frequency and productive work must be measured.

Source seams

  • hw/xbox/mcpx/apu/vp/vp.c
    • voice_worker_thread()
    • voice_work_enqueue()
    • voice_work_schedule()
    • dispatch broadcast/wait and central reduction
    • get_voice_bin_src_dst() dependency grouping
  • hw/xbox/mcpx/apu/apu.c
    • se_frame() and frame utilization
  • Configuration fields for maximum voice workers and HRTF.

Current flow

flowchart TD
    A[Build voice work queue] --> B[Assign queue entries to workers]
    B --> C[Broadcast work_pending]
    C --> D[Configured workers wake]
    D --> E{Worker queue nonempty?}
    E -->|No| F[Mark worker complete and wait again]
    E -->|Yes| G[Clear full worker mix matrix]
    G --> H[Process assigned voices]
    H --> I[Lock dispatch mutex]
    I --> J[Reduce all 32 x 32 entries into central matrix]
    J --> K[Mark worker complete]
    F --> L{All workers complete?}
    K --> L
    L -->|Yes| M[Signal APU thread]
    M --> N{{workers_pending still nonzero after wake?}}
    N -->|Yes| O[Wait again under dispatch mutex]
    O --> N
    N -->|No| P[Consume completed matrix]
Loading

The current scheduler already tracks multipass mix-bin dependencies. A replacement must preserve those ordering constraints.

Remaining scheduler research

1. Configuration sweep before code changes

Run equivalent workloads with:

workers = 1, 2, 4, 8, 16, Auto

Group results by:

  • active voice count;
  • HRTF-active voice count;
  • PCM versus ADPCM;
  • 2D versus 3D;
  • filter/resampler activity;
  • multipass dependencies;
  • APU frame utilization.

Auto-four has already landed. A new sweep can locate remaining crossover points and test explicit overrides without implying that one count is best for every workload.

2. Explicit serial path for small worksets

VoiceCostEstimate estimate = estimate_voice_work(queue, queue_len);

if (queue_len <= serial_voice_threshold ||
    estimate.total <= serial_cost_threshold) {
    process_voice_queue_serial(d, queue, queue_len, central_mixbins);
} else {
    dispatch_voice_workers(d, queue, queue_len, central_mixbins);
}

The serial route should use the same voice_process() implementation and preserve voice order. It is an exact host optimization if all guest-visible ordering and numerical reduction rules remain the same.

3. Wake only workers that receive work

A broadcast wakes workers with empty queues. Possible replacements include:

  • one condition per worker;
  • a shared claim queue with a generation counter;
  • an active-worker mask plus targeted signals;
  • a persistent task counter consumed by workers.

The simplest first prototype can schedule min(configured_workers, work_count) workers and signal exactly those workers.

4. Cost-aware scheduling

Voice count is a weak cost model. A voice may include:

PCM or ADPCM fetch
resampling
filtering
HRTF
multiple output bins
multipass dependencies
stream/loop transitions
notifications

An estimate can be coarse and still outperform round-robin count distribution:

uint32_t voice_cost(const VoicePreparedInfo *v)
{
    uint32_t cost = BASE_COST;
    cost += v->adpcm ? ADPCM_COST : 0;
    cost += v->resample ? RESAMPLE_COST : 0;
    cost += v->filter ? FILTER_COST : 0;
    cost += v->hrtf ? HRTF_COST : 0;
    cost += popcount(v->destination_bins) * MIXBIN_COST;
    return cost;
}

Weights must come from measurements, not permanent magic numbers copied from one title.

5. Clear and reduce only touched bins

Each worker currently owns a complete matrix. Track the mixbins it can write:

typedef struct WorkerMix {
    uint32_t touched_mixbins;
    float values[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME];
} WorkerMix;

Before processing:

for_each_set_bit(bin, planned_destination_mask) {
    memset(worker->values[bin], 0, sizeof(worker->values[bin]));
}

During reduction:

for_each_set_bit(bin, worker->touched_mixbins) {
    for (unsigned sample = 0; sample < NUM_SAMPLES_PER_FRAME; sample++) {
        central[bin][sample] += worker->values[bin][sample];
    }
}

The planned mask must account for HRTF submix routing and multipass destination/clear behavior.

Unimplemented scheduler proposal

flowchart TD
    A[Prepared voice queue] --> B[Estimate work and dependency groups]
    B --> C{Serial route cheaper?}
    C -->|Yes| D[Process in deterministic order into central matrix]
    C -->|No| E[Choose productive worker count]
    E --> F[Cost-balance dependency-safe groups]
    F --> G[Signal only selected workers]
    G --> H[Clear/process only planned/touched bins]
    H --> I[Reduce touched bins in defined order]
    D --> J[Complete block]
    I --> J
Loading

Numerical ordering

Floating-point addition is not associative. Parallel reduction may already produce an order different from a serial voice loop. A new scheduler can change it again.

The investigation must choose one contract:

  • preserve the current worker/reduction order exactly;
  • preserve a deterministic order but accept a new bounded numerical result;
  • compare with a declared sample-error metric;
  • or classify the route as approximate.

“Sounds fine” is not sufficient for an exact claim. Conversely, bit-identical output may be unnecessarily restrictive if the existing parallel path is already order-dependent. Record current determinism before selecting the acceptance rule.

Protected invariants

  • Multipass source/destination/clear dependencies.
  • Voice start/stop and list traversal semantics.
  • Voice lock behavior.
  • Notifications and envelope/position commits.
  • Exactly one contribution per productive voice.
  • Completion before later GP/DSP use of the VP matrix.
  • Safe pause/reset/snapshot/shutdown worker quiescence.
  • No worker reads mutable queue storage after it is recycled.

Proposed counters for further work

APU voice dispatches
queued voices per dispatch
configured workers
selected productive workers
workers awakened
workers assigned work
workers completing with empty queues
voice cost estimate by worker
worker clear time / bins cleared
worker voice-processing time
worker mutex wait time
central reduction time / bins reduced
serial-route blocks
parallel-route blocks
load imbalance: max worker time - min productive worker time
APU frame deadline misses
audio underruns

Histograms are more useful than only averages for queue length, active workers, and worker time.

Future scheduler qualification matrix

Workload Required property
Zero voices No unnecessary worker dispatch
One cheap voice Serial or one-worker route; correct output
Two/few cheap voices No empty-worker storm
Many uniform-cost voices Scaling and balanced work
Mixed HRTF/non-HRTF Cost model does not strand one heavy worker
PCM + ADPCM + filters Correct state/output and realistic weights
Multipass chain Dependency order preserved
Voice starts/stops mid-stream Queue and prepared state remain valid
Voice lock/unlock No processing of locked mutable state
Small forced worker pool Correct fallback and no deadlock
Pause/reset/snapshot/shutdown All workers quiesce deterministically
Repeated identical run Declared determinism/error envelope

Advantages

  • Strong source-backed fixed overhead.
  • Initial worker-count sweep requires no implementation.
  • Serial threshold and targeted wakeups can be introduced independently.
  • Touched-bin reduction attacks both memory clearing and aggregation.
  • Counters also inform HRTF and voice-span work.

Costs and risks

  • More scheduler logic can exceed the work saved for tiny blocks.
  • Cost weights can become title-specific heuristics.
  • Dependency grouping is more important than perfect load balance.
  • Targeted signaling can create missed-wakeup bugs.
  • Changed reduction order can alter floating-point output.
  • A lower worker count can hurt many-voice/HRTF workloads while helping common light scenes.

Acceptance rule

Classify a new scheduler experiment as Researched when it has useful evidence, and as Implemented only if it lands. Auto-four and the two reliability fixes are already delivered. Further scheduler changes must preserve the declared output/ordering contract and show useful overall game cadence, tails or efficiency across relevant workload classes. This review adds no fresh benchmark of current main.

Rejection rule

Close or narrow the work if the existing worker pool is rarely active, if voice processing dominates fixed overhead at all realistic queue sizes, or if new scheduling adds instability without whole-emulator gain.

Related

Clone this wiki locally