mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
ARM64 NEON implementations for the SPU2 mixer hot paths. Co-Authored-By: Ryan Walklin <ryan@testtoast.com> Co-Authored-By: Brian Degenhardt <bmd@bmdhacks.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
643 lines
13 KiB
C++
643 lines
13 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
||
// SPDX-License-Identifier: GPL-3.0+
|
||
|
||
#pragma once
|
||
|
||
#include "GS/MultiISA.h"
|
||
|
||
#include <algorithm>
|
||
#include <array>
|
||
#include <cstdio>
|
||
|
||
#if defined(__aarch64__)
|
||
#include <arm_neon.h>
|
||
#endif
|
||
|
||
// --------------------------------------------------------------------------------------
|
||
// SPU2 Register Table LUT
|
||
// --------------------------------------------------------------------------------------
|
||
extern const std::array<u16*, 0x401> regtable;
|
||
|
||
// --------------------------------------------------------------------------------------
|
||
// SPU2 Memory Indexers
|
||
// --------------------------------------------------------------------------------------
|
||
|
||
#define spu2Rs16(mmem) (*(s16*)((s8*)spu2regs + ((mmem)&0x1fff)))
|
||
#define spu2Ru16(mmem) (*(u16*)((s8*)spu2regs + ((mmem)&0x1fff)))
|
||
|
||
struct StereoOut32
|
||
{
|
||
static const StereoOut32 Empty;
|
||
|
||
s32 Left;
|
||
s32 Right;
|
||
|
||
StereoOut32() = default;
|
||
|
||
StereoOut32(s32 left, s32 right)
|
||
: Left(left)
|
||
, Right(right)
|
||
{
|
||
}
|
||
|
||
StereoOut32 operator*(const int& factor) const
|
||
{
|
||
return StereoOut32(
|
||
Left * factor,
|
||
Right * factor);
|
||
}
|
||
|
||
StereoOut32& operator*=(const int& factor)
|
||
{
|
||
Left *= factor;
|
||
Right *= factor;
|
||
return *this;
|
||
}
|
||
|
||
StereoOut32 operator+(const StereoOut32& right) const
|
||
{
|
||
return StereoOut32(
|
||
Left + right.Left,
|
||
Right + right.Right);
|
||
}
|
||
|
||
StereoOut32 operator/(int src) const
|
||
{
|
||
return StereoOut32(Left / src, Right / src);
|
||
}
|
||
};
|
||
|
||
extern void (*spu2Mix)();
|
||
extern s16* GetMemPtr(u32 addr);
|
||
extern s16 spu2M_Read(u32 addr);
|
||
extern void spu2M_Write(u32 addr, s16 value);
|
||
extern void spu2M_Write(u32 addr, u16 value);
|
||
MULTI_ISA_DEF(void spu2Mix();)
|
||
extern void spu2Output(StereoOut32 out);
|
||
|
||
static __forceinline s16 SignExtend16(u16 v)
|
||
{
|
||
return (s16)v;
|
||
}
|
||
|
||
static __forceinline s32 clamp_mix(s32 x)
|
||
{
|
||
return std::clamp(x, -0x8000, 0x7fff);
|
||
}
|
||
|
||
static __forceinline StereoOut32 clamp_mix(StereoOut32 sample)
|
||
{
|
||
#if defined(__aarch64__)
|
||
// vmin/vmax on s32x2 — one cycle each on A53 vs scalar cmp+csel pair × 2.
|
||
const int32x2_t v = vld1_s32(&sample.Left);
|
||
const int32x2_t lo = vdup_n_s32(-0x8000);
|
||
const int32x2_t hi = vdup_n_s32(0x7fff);
|
||
const int32x2_t out = vmin_s32(vmax_s32(v, lo), hi);
|
||
StereoOut32 r;
|
||
vst1_s32(&r.Left, out);
|
||
return r;
|
||
#else
|
||
return StereoOut32(clamp_mix(sample.Left), clamp_mix(sample.Right));
|
||
#endif
|
||
}
|
||
|
||
struct V_VolumeLR
|
||
{
|
||
static V_VolumeLR Max;
|
||
|
||
s32 Left;
|
||
s32 Right;
|
||
|
||
V_VolumeLR() = default;
|
||
V_VolumeLR(s32 both)
|
||
: Left(both)
|
||
, Right(both)
|
||
{
|
||
}
|
||
|
||
void DebugDump(FILE* dump, const char* title);
|
||
};
|
||
|
||
struct V_VolumeSlide
|
||
{
|
||
// Holds the "original" value of the volume for this voice, prior to slides.
|
||
// (ie, the volume as written to the register)
|
||
|
||
union
|
||
{
|
||
u16 Reg_VOL;
|
||
struct
|
||
{
|
||
u16 Step : 2;
|
||
u16 Shift : 5;
|
||
u16 : 5;
|
||
u16 Phase : 1;
|
||
u16 Decr : 1;
|
||
u16 Exp : 1;
|
||
u16 Enable : 1;
|
||
};
|
||
};
|
||
|
||
u32 Counter;
|
||
s32 Value;
|
||
|
||
public:
|
||
V_VolumeSlide() = default;
|
||
V_VolumeSlide(s16 regval, s32 fullvol)
|
||
: Reg_VOL(regval)
|
||
, Value(fullvol)
|
||
{
|
||
}
|
||
|
||
void Update();
|
||
void RegSet(u16 src); // used to set the volume from a register source
|
||
|
||
#ifdef PCSX2_DEVBUILD
|
||
void DebugDump(FILE* dump, const char* title, const char* nameLR);
|
||
#endif
|
||
};
|
||
|
||
struct V_VolumeSlideLR
|
||
{
|
||
static V_VolumeSlideLR Max;
|
||
|
||
V_VolumeSlide Left;
|
||
V_VolumeSlide Right;
|
||
|
||
public:
|
||
V_VolumeSlideLR() = default;
|
||
V_VolumeSlideLR(s16 regval, s32 bothval)
|
||
: Left(regval, bothval)
|
||
, Right(regval, bothval)
|
||
{
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
Left.Update();
|
||
Right.Update();
|
||
}
|
||
|
||
#ifdef PCSX2_DEVBUILD
|
||
void DebugDump(FILE* dump, const char* title);
|
||
#endif
|
||
};
|
||
|
||
struct V_ADSR
|
||
{
|
||
union
|
||
{
|
||
u32 reg32;
|
||
|
||
struct
|
||
{
|
||
u16 regADSR1;
|
||
u16 regADSR2;
|
||
};
|
||
|
||
struct
|
||
{
|
||
u32 SustainLevel : 4;
|
||
u32 DecayShift : 4;
|
||
u32 AttackStep : 2;
|
||
u32 AttackShift : 5;
|
||
u32 AttackMode : 1;
|
||
u32 ReleaseShift : 5;
|
||
u32 ReleaseMode : 1;
|
||
u32 SustainStep : 2;
|
||
u32 SustainShift : 5;
|
||
u32 : 1;
|
||
u32 SustainDir : 1;
|
||
u32 SustainMode : 1;
|
||
};
|
||
};
|
||
|
||
static constexpr int ADSR_PHASES = 5;
|
||
|
||
static constexpr int PHASE_STOPPED = 0;
|
||
static constexpr int PHASE_ATTACK = 1;
|
||
static constexpr int PHASE_DECAY = 2;
|
||
static constexpr int PHASE_SUSTAIN = 3;
|
||
static constexpr int PHASE_RELEASE = 4;
|
||
|
||
struct CachedADSR
|
||
{
|
||
bool Decr;
|
||
bool Exp;
|
||
u8 Shift;
|
||
s8 Step;
|
||
s32 Target;
|
||
};
|
||
|
||
std::array<CachedADSR, ADSR_PHASES> CachedPhases;
|
||
|
||
u32 Counter;
|
||
s32 Value; // Ranges from 0 to 0x7fff (signed values are clamped to 0) [Reg_ENVX]
|
||
u8 Phase; // monitors current phase of ADSR envelope
|
||
|
||
public:
|
||
void UpdateCache();
|
||
bool Calculate(int voiceidx);
|
||
void Attack();
|
||
void Release();
|
||
};
|
||
|
||
|
||
struct V_Voice
|
||
{
|
||
V_VolumeSlideLR Volume;
|
||
|
||
// Envelope
|
||
V_ADSR ADSR;
|
||
// Pitch (also Reg_PITCH)
|
||
u16 Pitch;
|
||
// Loop Start address (also Reg_LSAH/L)
|
||
u32 LoopStartA;
|
||
// Sound Start address (also Reg_SSAH/L)
|
||
u32 StartA;
|
||
// Next Read Data address (also Reg_NAXH/L)
|
||
u32 NextA;
|
||
// Voice Decoding State
|
||
s32 Prev1;
|
||
s32 Prev2;
|
||
|
||
// Pitch Modulated by previous voice
|
||
bool Modulated;
|
||
// Source (Wave/Noise)
|
||
bool Noise;
|
||
|
||
s8 LoopMode;
|
||
s8 LoopFlags;
|
||
|
||
// Sample pointer (19:12 bit fixed point)
|
||
s32 SP;
|
||
|
||
// Last outputted audio value, used for voice modulation.
|
||
s32 OutX;
|
||
|
||
// SBuffer now points directly to an ADPCM cache entry.
|
||
s16* SBuffer;
|
||
|
||
// Each voice has a buffer of decoded samples
|
||
s32 DecodeFifo[32];
|
||
u32 DecPosWrite;
|
||
u32 DecPosRead;
|
||
|
||
// it takes a few ticks for voices to start on the real SPU2?
|
||
void Start();
|
||
void Stop();
|
||
};
|
||
|
||
// ** Begin Debug-only variables section **
|
||
// Separated from the V_Voice struct to improve cache performance of
|
||
// the Public Release build.
|
||
struct V_VoiceDebug
|
||
{
|
||
s8 FirstBlock;
|
||
s32 SampleData;
|
||
s32 PeakX;
|
||
s32 displayPeak;
|
||
s32 lastSetStartA;
|
||
};
|
||
|
||
struct V_CoreDebug
|
||
{
|
||
V_VoiceDebug Voices[24];
|
||
// Last Transfer Size
|
||
u32 lastsize;
|
||
|
||
// draw adma waveform in the visual debugger
|
||
s32 admaWaveformL[0x100];
|
||
s32 admaWaveformR[0x100];
|
||
|
||
// Enabled when a dma write starts, disabled when the visual debugger showed it once
|
||
s32 dmaFlag;
|
||
};
|
||
|
||
// Debug tracking information - 24 voices and 2 cores.
|
||
extern V_CoreDebug DebugCores[2];
|
||
|
||
struct V_Reverb
|
||
{
|
||
s16 IN_COEF_L;
|
||
s16 IN_COEF_R;
|
||
|
||
u32 APF1_SIZE;
|
||
u32 APF2_SIZE;
|
||
|
||
s16 APF1_VOL;
|
||
s16 APF2_VOL;
|
||
|
||
u32 SAME_L_SRC;
|
||
u32 SAME_R_SRC;
|
||
u32 DIFF_L_SRC;
|
||
u32 DIFF_R_SRC;
|
||
u32 SAME_L_DST;
|
||
u32 SAME_R_DST;
|
||
u32 DIFF_L_DST;
|
||
u32 DIFF_R_DST;
|
||
|
||
s16 IIR_VOL;
|
||
s16 WALL_VOL;
|
||
|
||
u32 COMB1_L_SRC;
|
||
u32 COMB1_R_SRC;
|
||
u32 COMB2_L_SRC;
|
||
u32 COMB2_R_SRC;
|
||
u32 COMB3_L_SRC;
|
||
u32 COMB3_R_SRC;
|
||
u32 COMB4_L_SRC;
|
||
u32 COMB4_R_SRC;
|
||
|
||
s16 COMB1_VOL;
|
||
s16 COMB2_VOL;
|
||
s16 COMB3_VOL;
|
||
s16 COMB4_VOL;
|
||
|
||
u32 APF1_L_DST;
|
||
u32 APF1_R_DST;
|
||
u32 APF2_L_DST;
|
||
u32 APF2_R_DST;
|
||
};
|
||
|
||
struct V_SPDIF
|
||
{
|
||
u16 Out;
|
||
u16 Info;
|
||
u16 Unknown1;
|
||
u16 Mode;
|
||
u16 Media;
|
||
u16 Unknown2;
|
||
u16 Protection;
|
||
};
|
||
|
||
struct V_CoreRegs
|
||
{
|
||
u32 PMON;
|
||
u32 NON;
|
||
u32 VMIXL;
|
||
u32 VMIXR;
|
||
u32 VMIXEL;
|
||
u32 VMIXER;
|
||
u32 ENDX;
|
||
|
||
u16 MMIX;
|
||
u16 STATX;
|
||
u16 ATTR;
|
||
u16 _1AC;
|
||
};
|
||
|
||
struct V_VoiceGates
|
||
{
|
||
s32 DryL; // 'AND Gate' for Direct Output to Left Channel
|
||
s32 DryR; // 'AND Gate' for Direct Output for Right Channel
|
||
s32 WetL; // 'AND Gate' for Effect Output for Left Channel
|
||
s32 WetR; // 'AND Gate' for Effect Output for Right Channel
|
||
};
|
||
|
||
struct V_CoreGates
|
||
{
|
||
s32 InpL; // Sound Data Input to Direct Output (Left)
|
||
s32 InpR; // Sound Data Input to Direct Output (Right)
|
||
s32 SndL; // Voice Data to Direct Output (Left)
|
||
s32 SndR; // Voice Data to Direct Output (Right)
|
||
s32 ExtL; // External Input to Direct Output (Left)
|
||
s32 ExtR; // External Input to Direct Output (Right)
|
||
};
|
||
|
||
struct VoiceMixSet
|
||
{
|
||
StereoOut32 Dry, Wet;
|
||
|
||
VoiceMixSet() {}
|
||
VoiceMixSet(const StereoOut32& dry, const StereoOut32& wet)
|
||
: Dry(dry)
|
||
, Wet(wet)
|
||
{
|
||
}
|
||
};
|
||
|
||
struct V_Core
|
||
{
|
||
static const uint NumVoices = 24;
|
||
|
||
u32 Index; // Core index identifier.
|
||
|
||
// Voice Gates -- These are SSE-related values, and must always be
|
||
// first to ensure 16 byte alignment
|
||
|
||
V_VoiceGates VoiceGates[NumVoices];
|
||
V_CoreGates DryGate;
|
||
V_CoreGates WetGate;
|
||
|
||
V_VolumeSlideLR MasterVol; // Master Volume
|
||
V_VolumeLR ExtVol; // Volume for External Data Input
|
||
V_VolumeLR InpVol; // Volume for Sound Data Input
|
||
V_VolumeLR FxVol; // Volume for Output from Effects
|
||
|
||
V_Voice Voices[NumVoices];
|
||
|
||
u32 IRQA; // Interrupt Address
|
||
u32 TSA; // DMA Transfer Start Address
|
||
u32 ActiveTSA; // Active DMA TSA - Required for NFL 2k5 which overwrites it mid transfer
|
||
|
||
bool IRQEnable; // Interrupt Enable
|
||
bool FxEnable; // Effect Enable
|
||
bool Mute; // Mute
|
||
bool AdmaInProgress;
|
||
|
||
s8 DMABits; // DMA related?
|
||
u8 NoiseClk; // Noise Clock
|
||
u32 NoiseCnt; // Noise Counter
|
||
u32 NoiseOut; // Noise Output
|
||
u16 AutoDMACtrl; // AutoDMA Status
|
||
s32 DMAICounter; // DMA Interrupt Counter
|
||
u64 LastClock; // DMA Interrupt Clock Cycle Counter
|
||
u32 InputDataLeft; // Input Buffer
|
||
u32 InputDataTransferred; // Used for simulating MADR increase (GTA VC)
|
||
u32 InputPosWrite;
|
||
u32 InputDataProgress;
|
||
|
||
V_Reverb Revb; // Reverb Registers
|
||
|
||
s16 RevbDownBuf[2][64 * 2]; // Downsample buffer for reverb, one for each channel
|
||
s16 RevbUpBuf[2][64 * 2]; // Upsample buffer for reverb, one for each channel
|
||
u32 RevbSampleBufPos;
|
||
u32 EffectsStartA;
|
||
u32 EffectsEndA;
|
||
|
||
V_CoreRegs Regs; // Registers
|
||
|
||
// Preserves the channel processed last cycle
|
||
StereoOut32 LastEffect;
|
||
|
||
u8 CoreEnabled;
|
||
|
||
u8 AttrBit0;
|
||
u8 DmaMode;
|
||
|
||
// new dma only
|
||
bool DmaStarted;
|
||
u32 AutoDmaFree;
|
||
|
||
// old dma only
|
||
u16* DMAPtr;
|
||
u16* DMARPtr; // Mem pointer for DMA Reads
|
||
u32 ReadSize;
|
||
bool IsDMARead;
|
||
|
||
u32 KeyOn;
|
||
u32 KeyOff;
|
||
|
||
// psxmode caches
|
||
u16 psxSoundDataTransferControl;
|
||
u16 psxSPUSTAT;
|
||
|
||
// ----------------------------------------------------------------------------------
|
||
// V_Core Methods
|
||
// ----------------------------------------------------------------------------------
|
||
|
||
// uninitialized constructor
|
||
V_Core()
|
||
: Index(-1)
|
||
, DMAPtr(nullptr)
|
||
{
|
||
}
|
||
V_Core(int idx) : Index(idx) {};
|
||
|
||
void Init(int index);
|
||
void UpdateEffectsBufferSize();
|
||
void AnalyzeReverbPreset();
|
||
|
||
void WriteRegPS1(u32 mem, u16 value);
|
||
u16 ReadRegPS1(u32 mem);
|
||
|
||
// --------------------------------------------------------------------------------------
|
||
// Mixer Section
|
||
// --------------------------------------------------------------------------------------
|
||
|
||
StereoOut32 DoReverb(StereoOut32 Input);
|
||
s32 RevbGetIndexer(s32 offset);
|
||
|
||
StereoOut32 ReadInput();
|
||
StereoOut32 ReadInput_HiFi();
|
||
|
||
// --------------------------------------------------------------------------
|
||
// DMA Section
|
||
// --------------------------------------------------------------------------
|
||
|
||
// Returns the index of the DMA channel (4 for Core 0, or 7 for Core 1)
|
||
int GetDmaIndex() const
|
||
{
|
||
return (Index == 0) ? 4 : 7;
|
||
}
|
||
|
||
// returns either '4' or '7'
|
||
char GetDmaIndexChar() const
|
||
{
|
||
return 0x30 + GetDmaIndex();
|
||
}
|
||
|
||
__forceinline u16 DmaRead()
|
||
{
|
||
const u16 ret = static_cast<u16>(spu2M_Read(ActiveTSA));
|
||
++ActiveTSA;
|
||
ActiveTSA &= 0xfffff;
|
||
TSA = ActiveTSA;
|
||
return ret;
|
||
}
|
||
|
||
__forceinline void DmaWrite(u16 value)
|
||
{
|
||
spu2M_Write(ActiveTSA, value);
|
||
++ActiveTSA;
|
||
ActiveTSA &= 0xfffff;
|
||
TSA = ActiveTSA;
|
||
}
|
||
|
||
void LogAutoDMA(FILE* fp);
|
||
|
||
void DoDMAwrite(u16* pMem, u32 size);
|
||
void DoDMAread(u16* pMem, u32 size);
|
||
void FinishDMAread();
|
||
|
||
void AutoDMAReadBuffer(int mode);
|
||
void StartADMAWrite(u16* pMem, u32 sz);
|
||
void PlainDMAWrite(u16* pMem, u32 sz);
|
||
void FinishDMAwrite();
|
||
};
|
||
|
||
MULTI_ISA_DEF(
|
||
StereoOut32 ReverbUpsample(V_Core& core);
|
||
s32 ReverbDownsample(V_Core& core, bool right);
|
||
)
|
||
|
||
extern StereoOut32 (*ReverbUpsample)(V_Core& core);
|
||
extern s32 (*ReverbDownsample)(V_Core& core, bool right);
|
||
|
||
extern V_Core Cores[2];
|
||
extern V_SPDIF Spdif;
|
||
|
||
// Output Buffer Writing Position (the same for all data);
|
||
extern u16 OutPos;
|
||
// Input Buffer Reading Position (the same for all data);
|
||
extern u16 InputPos;
|
||
// SPU Mixing Cycles ("Ticks mixed" counter)
|
||
extern u32 Cycles;
|
||
|
||
extern s16 spu2regs[0x010000 / sizeof(s16)];
|
||
extern s16 _spu2mem[0x200000 / sizeof(s16)];
|
||
extern int PlayMode;
|
||
|
||
extern void SetIrqCall(int core);
|
||
extern void SetIrqCallDMA(int core);
|
||
extern void StartVoices(int core, u32 value);
|
||
extern void StopVoices(int core, u32 value);
|
||
extern void CalculateADSR(V_Voice& vc);
|
||
extern void UpdateSpdifMode();
|
||
|
||
namespace SPU2Savestate
|
||
{
|
||
struct DataBlock;
|
||
|
||
extern s32 FreezeIt(DataBlock& spud);
|
||
extern s32 ThawIt(DataBlock& spud);
|
||
extern s32 SizeIt();
|
||
} // namespace SPU2Savestate
|
||
|
||
// --------------------------------------------------------------------------------------
|
||
// ADPCM Decoder Cache
|
||
// --------------------------------------------------------------------------------------
|
||
// the cache data size is determined by taking the number of adpcm blocks
|
||
// (2MB / 16) and multiplying it by the decoded block size (28 samples).
|
||
// Thus: pcm_cache_data = 7,340,032 bytes (ouch!)
|
||
// Expanded: 16 bytes expands to 56 bytes [3.5:1 ratio]
|
||
// Resulting in 2MB * 3.5.
|
||
|
||
// The SPU2 has a dynamic memory range which is used for several internal operations, such as
|
||
// registers, CORE 1/2 mixing, AutoDMAs, and some other fancy stuff. We exclude this range
|
||
// from the cache here:
|
||
static constexpr s32 SPU2_DYN_MEMLINE = 0x2800;
|
||
|
||
// 8 short words per encoded PCM block. (as stored in SPU2 ram)
|
||
static constexpr int pcm_WordsPerBlock = 8;
|
||
|
||
// number of cachable ADPCM blocks (any blocks above the SPU2_DYN_MEMLINE)
|
||
static constexpr int pcm_BlockCount = 0x100000 / pcm_WordsPerBlock;
|
||
|
||
// 28 samples per decoded PCM block (as stored in our cache)
|
||
static constexpr int pcm_DecodedSamplesPerBlock = 28;
|
||
|
||
struct PcmCacheEntry
|
||
{
|
||
bool Validated;
|
||
s16 Sampledata[pcm_DecodedSamplesPerBlock];
|
||
s32 Prev1;
|
||
s32 Prev2;
|
||
};
|
||
|
||
extern PcmCacheEntry pcm_cache_data[pcm_BlockCount];
|
||
extern int g_counter_cache_hits;
|
||
extern int g_counter_cache_misses;
|
||
extern int g_counter_cache_ignores;
|