Savestate: store only live audio queue data (AudioChannel section v3)

FixedSizeQueue::DoState serializes the entire fixed backing store. For
the sceAudio channel queues that is 512KB per channel (32768*8 s16
samples), or ~4.6MB of mostly dead bytes in every savestate across the
nine channels - the live sample count at any moment is normally a few
KB. This addresses the existing TODO in DoState.

Add DoStateCompact(), which stores only the live [head, head+count)
region and restores it linearized at the front of storage. A wrapped
live region is written as its two pieces in pop order; since the POD
DoArray path writes raw bytes with no per-element or per-call header,
the single linear read on load consumes them identically. The count is
validated on load and a bad value fails the load cleanly via
p.SetError.

AudioChannel bumps its section to v3 to use the compact form; old
states still load through the unchanged full-storage path. This shrinks
every savestate by several MB uncompressed and cuts the copy/compress
cost of each save, including the rewind feature's periodic states.
This commit is contained in:
Ren committed 2026-07-27 16:20:49 +02:00
1 parent a6e4d691ef
commit 3abb56e7f5
2 files changed
+47 -2

No files matched your search

+37
View File
@@ -161,6 +161,43 @@ public:
p.DoMarker("FixedSizeQueue");
}
// Compact serialization: stores only the live [head_, head_+count_) region
// instead of the whole fixed storage. NOT format-compatible with DoState();
// callers must gate on their own section version.
void DoStateCompact(PointerWrap &p) {
int size = N;
Do(p, size);
if (size != N) {
ERROR_LOG(Log::Common, "Savestate failure: Incompatible queue size.");
p.SetError(p.ERROR_FAILURE);
return;
}
Do(p, count_);
if (count_ < 0 || count_ > N) {
ERROR_LOG(Log::Common, "Savestate failure: Bad compact queue count.");
p.SetError(p.ERROR_FAILURE);
return;
}
if (p.mode == PointerWrap::MODE_READ) {
// Restore linearized: live data starts at the front of storage.
head_ = 0;
tail_ = count_ == N ? 0 : count_;
DoArray<T>(p, storage_, count_);
} else {
if (head_ + count_ <= N) {
DoArray<T>(p, storage_ + head_, count_);
} else {
// Live region wraps; write the two pieces in pop order. Raw
// bytes only (POD DoArray has no per-call header), so the
// single linear read above consumes them identically.
const int firstPart = N - head_;
DoArray<T>(p, storage_ + head_, firstPart);
DoArray<T>(p, storage_, count_ - firstPart);
}
}
p.DoMarker("FixedSizeQueue");
}
private:
T *storage_;
int head_;
+10 -2
View File
@@ -44,7 +44,7 @@ AudioChannel g_audioChans[PSP_AUDIO_CHANNEL_MAX + 1];
void AudioChannel::DoState(PointerWrap &p)
{
auto s = p.Section("AudioChannel", 1, 2);
auto s = p.Section("AudioChannel", 1, 3);
if (!s)
return;
@@ -59,7 +59,15 @@ void AudioChannel::DoState(PointerWrap &p)
Do(p, defaultRoutingMode);
Do(p, defaultRoutingVolMode);
}
chanSampleQueues[index].DoState(p);
if (s >= 3) {
// v3: compact queue form — only the live samples, not the whole 512KB
// fixed storage per channel. Cuts ~4.6MB of dead bytes from every
// savestate. Old savestates (s < 3) still load through the
// full-storage path below.
chanSampleQueues[index].DoStateCompact(p);
} else {
chanSampleQueues[index].DoState(p);
}
}
void AudioChannel::reset()