Skip to content

Hot Path Audio Span Based Fetch

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

Span-Based Audio Fetch and Prepared Conversion Kernels

Reviewed: November 1, 2026 · Source: b7140848
State: Partial delivery: live SGE descriptor mappings #190 and bounded ADPCM payload mappings #275.
Release availability: Those mapping changes are main only at this review; absent from v0.8.136-0-g458730bf5373 (September 30, 2026).

Current main reuses callback-owned RAM mappings while reading fresh guest-written descriptor values and ADPCM payload words. A general VoiceSpan engine with bulk PCM conversion and SIMD kernels remains research. Mapping reuse must not be described as a cache of descriptor values or permission to reuse stale samples.

Delivered components and remaining design

Mechanism Current state Boundary
SGE descriptor RAM mapping Merged #190 Reuse mapping, reload descriptor contents on each access
ADPCM payload RAM mapping Merged #275 Bounded cached word reads; current table base and descriptor observed per word
Decoded ADPCM block Merged #187 Separate per-voice content cache with fresh-byte comparison
Callback-local format decode Merged #191 One local CFG_FMT value; see Prepared Voice Controls
General VoiceSpan / bulk PCM conversion Unimplemented research Must prove all mapping, stream, loop and completion boundaries
Format-specific SIMD kernels Unimplemented research Separate measured experiment after scalar semantics are isolated

Before mapping reuse

flowchart LR
    A[Next sample or ADPCM word] --> B[Read SGE descriptor through generic physical access]
    B --> C[Resolve payload address]
    C --> D[Read payload through general access path]
    D --> E[Convert / decode]
    E --> A
Loading

Current ADPCM fetch path

flowchart TD
    A[voice_get_samples needs a new block] --> B[mcpx_apu_read_adpcm_block]
    B --> C[For each encoded word: read current SGE table base]
    C --> D[mcpx_apu_sample_word_address]
    D --> E[Reload descriptor through valid cached mapping or fallback]
    E --> F[mcpx_apu_sample_read_word]
    F --> G{RAM mapping / FlatView / bounds valid?}
    G -->|Yes| H[Read current payload word with ldl_le_phys_cached]
    G -->|No| I[Try bounded mapping or use ldl_le_phys]
    H --> J[Next encoded word]
    I --> J
    J -->|More| C
    J -->|Complete| K[mcpx_apu_adpcm_decode_cached]
    K --> L[Bounds-check and emit requested samples]
Loading

MCPXAPUSampleReadCache owns an SGE mapping and payload mapping for one voice_get_samples() call. g_auto cleanup destroys both at callback exit. The streaming ADPCM path still copies from the established RAM path; it is not converted into a universal span engine. PCM still uses get_data_ptr() with the descriptor cache and its existing sample-format conversion loop.

Mapping correctness contract

mcpx_apu_sge_read_descriptor() admits only a suitable RAM mapping, checks descriptor address and current FlatView identity, and reloads the descriptor word through ldl_le_phys_cached(). It does not cache the page-address value. On an unsuitable or changed mapping it rebuilds or uses ldl_le_phys().

mcpx_apu_sample_read_word() checks the payload range, four-byte footprint and current view. A cold payload mapping is bounded by remaining page bytes, and is avoided for one trailing word. Non-RAM, short or otherwise unsuitable mappings fall back to the ordinary physical read. mcpx_apu_read_adpcm_block() retains the previous per-word observation of the SGE base register and live descriptor. The helper also preserves existing 32-bit payload-address arithmetic.

These are address-translation savings under a limited lifetime. They do not establish that arbitrary guest pointers, PCM page crossings, or mutable voice state are safe to cache indefinitely.

Evidence and retained adverse result

#190 and #275 contain implementation and qualification history. Issue #310 retains adverse Deck Morrowind cadence/tail observations for the accepted mapping stack despite faster component reads. A component-read reduction is not a demonstrated overall game gain. Keep that result visible when comparing future mapping changes; this documentation repair did not run a new campaign.

Remaining VoiceSpan design

The broader proposal resolves the largest safe contiguous span and selects one scalar conversion kernel outside the sample loop. This is unimplemented pseudocode:

typedef struct VoiceSpan {
    const uint8_t *host_ptr;
    hwaddr guest_address;
    size_t guest_bytes;
    size_t sample_count;
    unsigned format;
    unsigned channels;
    unsigned segment;
} VoiceSpan;
flowchart LR
    A[Resolve live source and bounded span] --> B[Clamp page / SGE / segment / loop / block / output limits]
    B --> C[Select scalar conversion kernel]
    C --> D[Convert admitted samples]
    D --> E[Advance existing position / completion / notifier state]
    E --> A
Loading

A span ends before any page/SGE, source buffer, loop, streaming segment, required ADPCM block, requested-output, stride/channel footprint, mapping or notification boundary. It must retain ordinary behavior for MMIO/non-RAM, unsupported interleave and ambiguous ownership. Kernel selection does not own loop/completion policy. SIMD is a later independent experiment.

The ownership window must prove which guest writes remain observable. Simply assuming all mid-block guest writes wait until the next block is an approximation and must be labeled and tested separately.

Reopening and qualification

Start with counters for elements fetched, SGE reads, address translations, span lengths and split/fallback reasons, plus VP/APU and whole-game cost. Introduce scalar spans before changing conversion arithmetic. Compare every PCM format/channel layout, odd strides, noncontiguous SGEs, ADPCM splits, starvation, loops, completion/notifiers, remaps, voice reuse and snapshots. Negative controls must change descriptor and payload bytes at the same address and around boundaries.

Accept a future engine only when output/state contracts pass and matched Release measurements show useful overall cadence, tails or efficiency. If spans are usually tiny or ownership/setup cost dominates, narrow or stop the experiment. Proposed counters and VoiceSpan are not current-main APIs.

Source and related work

Clone this wiki locally