SPU: Streamline per-voice flag processing

- Walk the voice array directly and consume the noise, pitch modulation, and
  reverb masks one bit at a time.

- Pass the previous voice volume into SampleVoice explicitly, preserving pitch
  modulation ordering while avoiding repeated indexed lookups and the per-voice
  address multiplication generated for the 148-byte Voice structure.

~2% speedup in SPU-bound scenarios.
cdrom-async-readahead
Stenzek 2 weeks ago
parent c66b2694da
commit 2781502e04
No known key found for this signature in database

@ -8,6 +8,7 @@ add_executable(core-tests
../util/zip_helpers.cpp
cheats_tests.cpp
cpu_disasm_tests.cpp
spu_tests.cpp
stub_cpu.cpp
)

@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "common/types.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <random>
namespace {
static constexpr u32 NUM_VOICES = 24;
struct VoiceLoopResult
{
std::array<u16, NUM_VOICES> steps;
std::array<bool, NUM_VOICES> noise_enabled;
s32 reverb_left;
s32 reverb_right;
bool operator==(const VoiceLoopResult&) const = default;
};
static VoiceLoopResult RunIndexedVoiceLoop(const std::array<s32, NUM_VOICES>& volumes,
const std::array<s32, NUM_VOICES>& left,
const std::array<s32, NUM_VOICES>& right,
const std::array<u16, NUM_VOICES>& rates, u32 noise_modes,
u32 pitch_modulation_enable, u32 reverb_on)
{
VoiceLoopResult result = {};
for (u32 voice = 0; voice < NUM_VOICES; voice++)
{
const bool noise_enabled = ((noise_modes >> voice) & 1u) != 0;
const bool pitch_enabled = voice > 0 && ((pitch_modulation_enable >> voice) & 1u) != 0;
u16 step = rates[voice];
if (pitch_enabled)
{
const s32 factor = std::clamp(volumes[voice - 1], -0x8000, 0x7FFF) + 0x8000;
step = static_cast<u16>(static_cast<u32>(static_cast<s32>(static_cast<s16>(step)) * factor) >> 15);
}
result.steps[voice] = std::min<u16>(step, 0x3FFF);
if ((reverb_on >> voice) & 1u)
{
result.reverb_left += left[voice];
result.reverb_right += right[voice];
}
result.noise_enabled[voice] = noise_enabled;
}
return result;
}
static VoiceLoopResult RunShiftedVoiceLoop(const std::array<s32, NUM_VOICES>& volumes,
const std::array<s32, NUM_VOICES>& left,
const std::array<s32, NUM_VOICES>& right,
const std::array<u16, NUM_VOICES>& rates, u32 noise_modes,
u32 pitch_modulation_enable, u32 reverb_on)
{
VoiceLoopResult result = {};
pitch_modulation_enable &= ~1u;
s32 previous_voice_last_volume = 0;
u32 voice = 0;
for (const s32 volume : volumes)
{
const bool noise_enabled = (noise_modes & 1u) != 0;
const bool pitch_enabled = (pitch_modulation_enable & 1u) != 0;
u16 step = rates[voice];
if (pitch_enabled)
{
const s32 factor = std::clamp(previous_voice_last_volume, -0x8000, 0x7FFF) + 0x8000;
step = static_cast<u16>(static_cast<u32>(static_cast<s32>(static_cast<s16>(step)) * factor) >> 15);
}
result.steps[voice] = std::min<u16>(step, 0x3FFF);
if (reverb_on & 1u)
{
result.reverb_left += left[voice];
result.reverb_right += right[voice];
}
result.noise_enabled[voice] = noise_enabled;
previous_voice_last_volume = volume;
noise_modes >>= 1;
pitch_modulation_enable >>= 1;
reverb_on >>= 1;
voice++;
}
return result;
}
TEST(SPU, ShiftedVoiceFlagsMatchIndexedFlags)
{
std::mt19937 generator(0x4D595350u);
std::uniform_int_distribution<u32> bits_distribution;
std::uniform_int_distribution<s32> sample_distribution(-0x10000, 0xFFFF);
std::uniform_int_distribution<u32> rate_distribution(0, 0xFFFF);
for (u32 iteration = 0; iteration < 10000; iteration++)
{
std::array<s32, NUM_VOICES> volumes;
std::array<s32, NUM_VOICES> left;
std::array<s32, NUM_VOICES> right;
std::array<u16, NUM_VOICES> rates;
for (u32 voice = 0; voice < NUM_VOICES; voice++)
{
volumes[voice] = sample_distribution(generator);
left[voice] = sample_distribution(generator);
right[voice] = sample_distribution(generator);
rates[voice] = static_cast<u16>(rate_distribution(generator));
}
const u32 noise_modes = bits_distribution(generator);
const u32 pitch_modulation_enable = bits_distribution(generator);
const u32 reverb_on = bits_distribution(generator);
EXPECT_EQ(RunShiftedVoiceLoop(volumes, left, right, rates, noise_modes, pitch_modulation_enable, reverb_on),
RunIndexedVoiceLoop(volumes, left, right, rates, noise_modes, pitch_modulation_enable, reverb_on));
}
}
} // namespace

@ -351,7 +351,9 @@ static void WriteToCaptureBuffer(u32 index, s16 value);
static void IncrementCaptureBufferPosition();
static void ReadADPCMBlock(u16 address, ADPCMBlock* block);
static std::tuple<s32, s32> SampleVoice(u32 voice_index);
static std::tuple<s32, s32> SampleVoice(Voice& voice, u32 voice_index, bool noise_enabled,
bool pitch_modulation_enabled, bool irq9_enabled,
s32 previous_voice_last_volume);
static void UpdateNoise();
@ -2045,10 +2047,11 @@ void SPU::ReadADPCMBlock(u16 address, ADPCMBlock* block)
}
}
ALWAYS_INLINE_RELEASE std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
ALWAYS_INLINE_RELEASE std::tuple<s32, s32> SPU::SampleVoice(Voice& voice, u32 voice_index, bool noise_enabled,
bool pitch_modulation_enabled, bool irq9_enabled,
s32 previous_voice_last_volume)
{
Voice& voice = s_state.voices[voice_index];
if (!voice.IsOn() && !s_state.SPUCNT.irq9_enable)
if (!voice.IsOn() && !irq9_enabled)
{
voice.last_volume = 0;
@ -2083,7 +2086,7 @@ ALWAYS_INLINE_RELEASE std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
{
// interpolate/sample and apply ADSR volume
s32 sample;
if (IsVoiceNoiseEnabled(voice_index))
if (noise_enabled)
sample = GetVoiceNoiseLevel();
else
sample = voice.Interpolate();
@ -2102,9 +2105,9 @@ ALWAYS_INLINE_RELEASE std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
// Pitch modulation
u16 step = voice.regs.adpcm_sample_rate;
if (IsPitchModulationEnabled(voice_index))
if (pitch_modulation_enabled)
{
const s32 factor = std::clamp<s32>(s_state.voices[voice_index - 1].last_volume, -0x8000, 0x7FFF) + 0x8000;
const s32 factor = std::clamp<s32>(previous_voice_last_volume, -0x8000, 0x7FFF) + 0x8000;
step = Truncate16(static_cast<u32>((SignExtend32(step) * factor) >> 15));
}
step = std::min<u16>(step, 0x3FFF);
@ -2132,7 +2135,7 @@ ALWAYS_INLINE_RELEASE std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
if (!voice.current_block_flags.loop_repeat)
{
// End+Mute flags are ignored when noise is enabled. ADPCM data is still decoded.
if (!IsVoiceNoiseEnabled(voice_index))
if (!noise_enabled)
{
TRACE_LOG("Voice {} loop end+mute @ 0x{:04X}", voice_index, voice.current_address);
voice.ForceOff();
@ -2429,11 +2432,18 @@ void SPU::Execute(void* param, TickCount ticks)
s32 reverb_in_left = 0;
s32 reverb_in_right = 0;
u32 noise_mode = s_state.noise_mode_register;
u32 pitch_modulation_enable = s_state.pitch_modulation_enable_register & ~1u;
u32 reverb_on_register = s_state.reverb_on_register;
s32 previous_voice_last_volume = 0;
const bool irq9_enabled = s_state.SPUCNT.irq9_enable;
for (u32 voice = 0; voice < NUM_VOICES; voice++)
u32 voice_index = 0;
for (Voice& voice : s_state.voices)
{
const auto [left, right] = SampleVoice(voice);
const auto [left, right] =
SampleVoice(voice, voice_index, ConvertToBoolUnchecked(noise_mode & 1u),
ConvertToBoolUnchecked(pitch_modulation_enable & 1u), irq9_enabled, previous_voice_last_volume);
left_sum += left;
right_sum += right;
@ -2442,7 +2452,11 @@ void SPU::Execute(void* param, TickCount ticks)
reverb_in_left += left;
reverb_in_right += right;
}
previous_voice_last_volume = voice.last_volume;
noise_mode >>= 1;
pitch_modulation_enable >>= 1;
reverb_on_register >>= 1;
voice_index++;
}
if (!s_state.SPUCNT.mute_n)

Loading…
Cancel
Save