New src/dusk/perf module: a simple push/pop tick-stack (perfPush/perfPop/
perfReport) for measuring how long a labeled span of code takes, wired
into engine.c's init/dispose. perfGetTick() is a placeholder - real time
on DUSK_LINUX (clock_gettime) and DUSK_PSP (sceKernelGetSystemTimeWide),
0 elsewhere until a proper cross-platform answer is settled on.
Used it this session to chase a reported PSP hitch when crossing a chunk
boundary. Confirmed mapPositionSet itself (unload/load sweeps, chunk
order rebuild, entity chunkIndex recompute) is consistently under ~3ms -
not the cause. Along the way, found and fixed a real, unrelated bug:
logDebug()/logError() on PSP did a fopen+write+fclose to the Memory
Stick on every single call, which was massively inflating any perf
measurement that logged from inside another measurement (the actual
source of the ~29-128ms numbers first seen). That's now gated behind a
new DUSK_PSP_LOG_FILE CMake option (default off).
Also, as a real test based on this session's findings:
- MAP_CHUNK_LOAD_CONCURRENCY dropped from 2 to 1 (8 and 4 both crashed/
were untested further on real PSP hardware; 1 is confirmed safe).
- MAP_CHUNK_LOAD_DELAY_TEST: an intentionally exaggerated (now 50ms)
artificial delay between starting successive chunk loads, gated in
mapChunkLoadNext/mapUpdate, to observe the effect on framerate.
All perf instrumentation call sites added during this investigation
(assetChunkLoaderSync, assetMeshLoaderSync, mapPositionSet, the map area
callback invocation, engine.c's frame boundary) were removed again once
they'd served their purpose - only the reusable perf module itself and
the two real fixes above remain.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Wii NAND save storage is blocked by Dolphin's IOS ticket-rights check for
homebrew (documented earlier this session as a known, non-code limitation),
so Wii now defaults DUSK_SAVE_WII_METHOD to SD instead of NAND.
Also: asset_t gains ASSET.baseDirectory, populated by each platform's asset
loader with the real directory dusk.dsk was opened from (or, on PSP, the
directory EBOOT.PBP itself lives in, since dusk.dsk is embedded inside it).
Wii SD and PSP now write their single combined save file directly into
that directory instead of a separate hardcoded path, and no longer need to
create it (it's already known to exist). Linux now uses a `saves/`
subdirectory of that same directory instead of ~/.dusk/saves, since it
keeps multiple files (settings + one per slot). Wii NAND and GameCube/Wii
memory-card storage are unaffected - neither has a real filesystem-path
concept this applies to.
Built and verified on Linux, PSP (Docker), GameCube (Docker) and Wii
(Docker) - confirmed via the compiler invocation that Wii now compiles
with DUSK_SAVE_WII_METHOD_SD.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Replaces the ansnd-based backend (a third-party library with a custom
DSP ucode Dolphin's HLE audio emulation doesn't recognize, requiring
-a LLE as a workaround) with libogc's own asndlib - the standard,
HLE-supported audio API most GC/Wii homebrew already uses.
Also fixes three real bugs found while getting a large (41MB) WAV
asset actually playing on Dolphin:
- audiostreamdolphin.c previously buffered a stream's entire loop
segment into one allocation up front (inherited from the ansnd
design) - fine for a short test tone, but an out-of-memory crash for
a real multi-minute track on GameCube's 24MB (or even Wii's 88MB)
total RAM. Rewritten to stream bounded windows via ASND's real
double-buffer primitives (ASND_SetVoice + ASND_AddVoice, polled from
the main thread - never from an interrupt callback), matching how
Linux/PSP already work.
- ASND_TestVoiceBufferReady() returns SND_OK (0) when ready and
SND_BUSY (1) when not, in the actual toolchain fork
(extremscorner/libogc2) this project builds against - opposite of a
plain boolean. Treating the raw result as one meant a genuinely
ready voice's SND_OK read as false, silently skipping every
ASND_AddVoice() call forever after a stream's first buffered window.
- audioStreamPcmRead()'s 16-bit fast path read WAV's little-endian
sample bytes straight into the output buffer with no byte-swap -
correct by coincidence on every little-endian platform this engine
targets (Linux, PSP), but reversed every sample's bytes on
Dolphin/GameCube/Wii's big-endian PowerPC.
libmad is GPL-licensed (see audiostreammp3decodersw.h's own note from
the prior MP3 change); libogc's asndlib is under a permissive
BSD-style license, so this doesn't add another such dependency.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
minimp3's sliding-window frame confirmation could spuriously discard real
frames near the tail of a not-yet-full window, losing ~5.5% of a real
VBR file's content even at a tuned 256KB window (~0.3% residual) -
audible as the whole track finishing early with stutters at each drop.
libmad's mad_stream/mad_frame/mad_synth API reports "need more data"
(MAD_ERROR_BUFLEN) and "genuinely bad data" separately rather than
overloading one return value, which was the actual ambiguity minimp3
couldn't resolve. Verified against the same real file with a 32KB window
(vs minimp3's 256KB): frame count and duration match the Xing header
exactly. Confirmed building for Linux, GameCube, and Wii (the latter two
via the project's real devkitPPC/libogc Docker toolchain) - libmad ships
inside libogc itself on Dolphin, no separate fetch needed there.
libmad is GPL-licensed, unlike the rest of this MIT project - a
deliberate tradeoff, noted at each site that pulls it in.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
New ASSET_LOADER_TYPE_MP3 (hand-rolled MPEG-1/2/2.5 Layer III header
parser - no third-party dependency needed just for metadata, since PSP's
hardware path doesn't need one at all) plus a shared audiostreammp3.c
stream layer mirroring audiostreampcm.c's shape. Generalized the stream
dispatch (hoisted sampleRate/channels onto audiostream_t, added
audioStreamGetTotalFrames()/Seek()/Read()) so all three platform audio
backends keep working unchanged, just calling the generic names instead
of PCM-specific ones.
Two decoder backends behind one interface: PSP uses the real sceMp3
hardware decoder (firmware-offloaded, lazily initialized on first use);
Linux and Dolphin share one minimp3-based software decoder (public
domain, vendored via CMake FetchContent) - libogc's own MP3Player wraps
libmad (GPL) and drives its own output pipeline, not a fit for the
ansnd-based architecture already in place, so skipped in favor of the
shared minimp3 path.
Fixed three real bugs found via hardware/runtime testing along the way:
- LAME's Xing header counts its own placeholder frame in the declared
total, which made playback stall permanently one frame short of the
declared end (looked like "never loops") - fixed by subtracting it.
- sceMp3Decode() can return more PCM than one MPEG frame's worth in a
single call (PSP's pcmBuf is provisioned for 2x), overflowing the
shared per-frame decode buffer with no bound check - very intermittent
corruption/clicking on real hardware. Widened the buffer to the real
worst case and added an assertion.
- sceMp3ResetPlayPosition()'s exact internal reset semantics aren't
documented precisely enough to trust for looping - occasionally
disagreed with the fresh stream position fed right after, clicking at
the loop boundary about 1 in 3-4 loops. Rewind now fully tears down and
recreates the decoder instead, the same path already proven correct at
first Init. Also widened the PSP ring buffer to absorb that now-heavier
operation, capping each top-up call's own work so the bigger buffer
doesn't turn into one long blocking decode burst instead.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Allocates an ansnd voice per stream and configures/starts it in
single-buffer mode (no continuous re-feed needed yet, matching the
PSP/Linux single-shot approach). Real linear panning from
directionality via left/right voice volume. ansnd has no polling API
for voice state, so finish detection uses a voice_callback (fired
from ansnd's own audio DMA interrupt, not the main thread) that sets
a flag audioStreamDolphinIsFinished() reads.
Links libansnd (new for this project) and wires duskdolphin/audio
into the CMake build for the first time.
Verified via the dusk-dolphin toolchain: both GameCube and Wii
targets compile and link cleanly. Runtime verification in headless
Dolphin was inconclusive - confirmed via an A/B test against a
pre-audio baseline build that a pre-existing MMIO flood (unrelated
to this change, reproduces identically with zero audio code present)
keeps the harness from reaching the game's own steady state before
timing out.
EBOOT.PBP packing was a POST_BUILD step with no dependency on the asset
pak, so an assets-only rebuild could silently leave a stale dusk.dsk
embedded. cmake/targets/psp.cmake now repacks EBOOT.PBP via a properly
tracked custom command depending on the executable, PARAM.SFO, and
dusk.dsk (and correctly embeds Dusk.prx rather than the raw ELF when
BUILD_PRX is on).
Separately, libzip's zip_source_filep_create (lazy seeked FILE* reads)
proved unreliable on real PSP hardware, corrupting reads of the embedded
PSAR (first EINVAL, then zlib data errors) even though the packaged data
was verified byte-perfect. assetInitPBP now reads the whole PSAR into
memory once and uses zip_source_buffer_create instead. Confirmed working
on real hardware.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
PSP checks memory-stick reachability via sceIoGetstat. GameCube gets two
devices (CARD_SLOTA/CARD_SLOTB), each mounted via CARD_Init/CARD_Mount and
checked for free space via CARD_GetDirectory/CARD_GetBlockCount. Wii picks
between three storage methods at compile time (DUSK_SAVE_WII_METHOD =
NAND/CARD/SD in wii.cmake, default NAND via ISFS) since real hardware
behavior for the default is unverified.
Also fixes two pre-existing bugs found while build/runtime-testing this
across PPSSPP and Dolphin: wrong libogc language macros in
systemGetLocaleDolphin, and a missing SYS_STDIO_Report(true) call that
silently swallowed all guest console output in Dolphin.
Co-Authored-By: Claude Sonnet 5 <[email protected]>