Repository navigation
APU Bulk VP to DSP Mix Transfer
Lifecycle: Performance - Unproven · Type: Exact backend bulk-transfer shortcut
Reviewed source:322986f4c5502b3fa887a9605ac8cd88e9b2c579
Question: Can the complete VP mix matrix be converted and written through one backend operation instead of 1,024 generic DSP writes per APU subframe?
APU/DSP investigation directory · DSP JIT Qualification
After VP processing produces:
float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME];mcpx_apu_dsp_frame() writes the complete matrix into GP DSP X memory:
for (int mixbin = 0; mixbin < NUM_MIXBINS; mixbin++) {
uint32_t base = GP_DSP_MIXBUF_BASE +
mixbin * NUM_SAMPLES_PER_FRAME;
for (int sample = 0; sample < NUM_SAMPLES_PER_FRAME; sample++) {
dsp_write_memory(d->gp.dsp, 'X', base + sample,
float_to_24b(mixbins[mixbin][sample]));
}
}With 32 mixbins and 32 samples, that is 1,024 interface calls per subframe. At the nominal 1,500-subframe cadence, the source-derived upper estimate is 1,536,000 generic write calls per second when the path is active.
This does not prove 1.536 million expensive calls occur in every title or that they dominate APU time. It establishes a precise counter target.
-
hw/xbox/mcpx/apu/dsp/gp_ep.cmcpx_apu_dsp_frame()- VP matrix →
GP_DSP_MIXBUF_BASE - GP and EP frame execution
-
hw/xbox/mcpx/apu/dsp/dsp.c- generic
dsp_write_memory()dispatch
- generic
-
hw/xbox/mcpx/apu/dsp/dsp_jit.c- JIT XRAM ownership
- mix-buffer alias mapping
hw/xbox/mcpx/apu/dsp/dsp_c.c-
hw/xbox/mcpx/apu/fpconv.h-
float_to_24b()conversion contract
-
flowchart TD
A[VP produces 32 x 32 float matrix] --> B[Select mixbin and sample]
B --> C[float_to_24b]
C --> D[dsp_write_memory generic interface]
D --> E[Backend dispatch]
E --> F[Interpret X-space address]
F --> G[Write one DSP word]
G --> H{1,024 words complete?}
H -->|No| B
H -->|Yes| I[Start/run GP DSP]
The numerical conversion is necessary. The candidate targets repeated interface and address machinery around it.
flowchart TD
A[VP produces 32 x 32 float matrix] --> B[dsp_write_mixblock24]
B --> C{Active backend}
C -->|Interpreter| D[Backend validates destination span]
C -->|JIT| E[Backend resolves owned XRAM alias]
D --> F[Convert and write contiguous 1,024 words]
E --> F
F --> G[Optional exact vectorized conversion experiment]
G --> H[Start/run GP DSP]
Proposed interface:
typedef struct DspMixBlockResult {
size_t words_written;
bool used_fast_span;
} DspMixBlockResult;
DspMixBlockResult dsp_write_mixblock24(
DSPState *dsp,
const float mixbins[NUM_MIXBINS][NUM_SAMPLES_PER_FRAME]);The first version should keep the conversion scalar and bit-identical:
static void write_mixblock_reference(
uint32_t *dst,
const float *src,
size_t words)
{
for (size_t i = 0; i < words; i++) {
dst[i] = float_to_24b(src[i]);
}
}SIMD is a later experiment because it can change rounding, saturation, NaN handling, or conversion ordering.
The JIT owns XRAM and maps the mix-buffer region as an alias. A fast backend method can resolve the known range once and write directly into that C-owned buffer.
The implementation must derive the destination from the backend’s actual region mapping rather than duplicating a fragile hard-coded pointer in gp_ep.c.
Possible internal API:
bool dsp_backend_get_writable_span(
DSPState *dsp,
char space,
uint32_t address,
size_t requested_words,
DspWordSpan *span);The mix transfer can use it only when the returned span covers the complete expected range and has ordinary buffer semantics.
The interpreter may expose its XRAM/mixbuffer directly through a private backend function or implement a backend-specific bulk callback. The public APU code should not inspect interpreter internals.
If a backend cannot offer the span, use the current word-at-a-time loop. The fallback makes the new interface safe for debugging, future engines, and unusual memory maps.
The candidate is exact only if it preserves:
- one
float_to_24b()result for each matrix element; - mixbin-major, sample-minor layout;
-
GP_DSP_MIXBUF_BASEalias behavior; - visibility before the GP DSP begins consuming the block;
- guest reads of the GP mix-buffer window;
- interpreter and JIT engine switching;
- snapshot state;
- P-memory/cache behavior unaffected by an X-space bulk write.
The bulk call must not defer conversion past GP execution or keep a pointer to the stack-local mixbins array.
After scalar bulk transfer proves interface savings, compare an exact or declared-equivalent vectorized converter:
load float lanes
apply scale/round/saturate matching float_to_24b
pack to 32-bit words with upper byte clear
store contiguous words
Required negative values include:
- ±0;
- values just below/above saturation;
- halfway rounding cases;
- subnormals;
- NaN and infinities if reachable;
- random full-range floats.
No SIMD path should be merged merely because ordinary music samples match.
- 1,024 calls through
dsp_write_memory()per active subframe. - 1,024 backend function-pointer dispatches.
- Repeated space/address decoding.
- Repeated memory-region lookup or Rust FFI entry.
- Per-word bounds/range interpretation that can be proven once for the whole mix block.
- 1,024 numerical conversions and stores unless a later exact vector path changes their organization.
- GP DSP execution.
- Correct mix-buffer memory visibility.
- Guest MMIO/memory reads.
- Engine synchronization and snapshot behavior.
- Generic fallback.
APU subframes
VP→DSP mix transfers
mix words requested
reference dsp_write_memory calls
bulk calls
bulk words written
bulk fast-span hits
bulk fallback calls{reason}
float_to_24b scalar conversions
bulk transfer host time
GP dsp_run host time
APU frame time
The baseline should verify the expected ratio:
reference write calls / active mix transfer ≈ 1,024
If the count is materially lower due to path conditions, document the actual behavior before implementation.
| Scenario | Required result |
|---|---|
| Random 32 × 32 matrix | Bit-identical DSP words |
| Saturation/rounding edge vectors | Exact float_to_24b() parity |
| Interpreter backend | Correct memory/output |
| JIT backend | Correct alias memory/output |
| GP mix-buffer guest reads | Same words at same addresses |
| GP program reads during frame | Complete block visible before run |
| Runtime engine switch | No layout or state loss |
| GP disabled/enabled transition | No stale deferred block |
| Snapshot save/load | Mix-buffer state preserved |
| Reset/bootstrap | Bulk state cannot survive reset |
| Long playback | No drift or underrun |
- Very narrow, measurable source seam.
- Strong call-count hypothesis.
- Existing JIT memory ownership makes a direct span plausible.
- Exact scalar implementation can isolate interface cost before SIMD.
- Backend fallback preserves compatibility.
- The generic calls may inline or be cheaper than expected in Release.
- Rust FFI cost may differ from interpreter dispatch cost.
- Direct memory exposure can violate backend encapsulation.
- Incorrect alias/address assumptions corrupt DSP state.
- Simultaneously adding SIMD would hide whether the gain came from dispatch or conversion.
Move to Researched when counters confirm a high generic-call rate, scalar bulk output is bit-identical across both engines and lifecycle tests, and Release measurements show a meaningful reduction in APU/frame or backend time.
Do not continue to SIMD or broader span APIs if bulk scalar transfer removes calls but does not affect local or whole-emulator performance. Preserve the evidence as a negative result.
DSP tooling guide and navigation updated November 2, 2026 for experimental PR #384, source 8f51d237. Capture/replay usage and limits.
The earlier wiki-wide review was published November 1, 2026. Emulator source: b7140848. Reviewed wiki content: 1d2b671. Current status · Release availability · Publication process.
- Home
- Current status
- Stable versus main
- Implemented mechanisms
- Controls and defaults
- Measured results and limits
- Complete change register
- System
- CPU and vCPU
- Memory and DMA
- Graphics / NV2A
- APU and audio
- Timing and timers
- Platform and I/O
- Hardware observations
- Fidelity boundaries
- Methodology
- PGR2 routes
- Debugging lag / hot paths
- Tools and commands
- DSP capture, replay and fuzzing
- HLE readiness and public XISO proof
- Concurrent DSP shadow and OpenAL proof
- DSP HLE operation inventory
- DSP HLE fix examples and testing
- Development
- Upstream shortlist
- Wiki maintenance
- Research / retired work
- Optimization catalog
- Hot-path shortcuts
- Researched experiments
- Unproven proposal directory
- PR lessons
- September26 snapshot
Repository · PRs · Issues