DSP: Convert accelerator to a C++ class

Slightly cleaner, allows DSP accelerator behaviour to be
added to both HLE and LLE pretty easily, and makes the accelerator
easier to unit test.

I chose to include all accelerator state as private members, and
to expose state that is accessible via registers with getters/setters.
It's more verbose, yes, but it makes it very clear what is part of
the accelerator state and what isn't (e.g. coefs).

This works quite well for registers, since the accelerator can do
whatever it wants internally. For example, the start/end/current
addresses are masked -- having a getter/setter makes it easier to
enforce the mask.
This commit is contained in:
Léo Lam 2017-09-20 10:52:57 +02:00
commit 003dba5275
7 changed files with 256 additions and 143 deletions

View file

@ -13,6 +13,7 @@
#endif
#include <functional>
#include <memory>
#include "Common/CommonTypes.h"
#include "Common/MathUtil.h"
@ -163,57 +164,33 @@ void DumpPB(const PB_TYPE& pb)
#endif
// Simulated accelerator state.
static u32 acc_loop_addr, acc_end_addr;
static u32* acc_cur_addr;
static PB_TYPE* acc_pb;
static bool acc_end_reached;
// Sets up the simulated accelerator.
void AcceleratorSetup(PB_TYPE* pb, u32* cur_addr)
class HLEAccelerator final : public Accelerator
{
acc_pb = pb;
// Masking occurs for the start and end addresses as soon as the registers are written to.
acc_loop_addr = HILO_TO_32(pb->audio_addr.loop_addr) & 0x3fffffff;
acc_end_addr = HILO_TO_32(pb->audio_addr.end_addr) & 0x3fffffff;
acc_cur_addr = cur_addr;
// It also happens for the current address, but with a different mask.
*acc_cur_addr &= 0xbfffffff;
acc_end_reached = false;
}
// Reads a sample from the accelerator. Also handles looping and
// disabling streams that reached the end (this is done by an exception raised
// by the accelerator on real hardware).
u16 AcceleratorGetSample()
{
// See below for explanations about acc_end_reached.
if (acc_end_reached)
return 0;
auto end_address_reached = [] {
// loop back to loop_addr.
*acc_cur_addr = acc_loop_addr;
protected:
void OnEndException() override
{
if (acc_pb->audio_addr.looping)
{
// Set the ADPCM info to continue processing at loop_addr.
//
// For some reason, yn1 and yn2 aren't set if the voice is not of
// stream type. This is what the AX UCode does and I don't really
// know why.
acc_pb->adpcm.pred_scale = acc_pb->adpcm_loop_info.pred_scale;
SetPredScale(acc_pb->adpcm_loop_info.pred_scale);
if (!acc_pb->is_stream)
{
acc_pb->adpcm.yn1 = acc_pb->adpcm_loop_info.yn1;
acc_pb->adpcm.yn2 = acc_pb->adpcm_loop_info.yn2;
SetYn1(acc_pb->adpcm_loop_info.yn1);
SetYn2(acc_pb->adpcm_loop_info.yn2);
}
#ifdef AX_GC
else
{
// Refresh YN1 and YN2. This indirectly causes the accelerator to resume reads.
SetYn1(GetYn1());
SetYn2(GetYn2());
#ifdef AX_GC
// If we're streaming, increment the loop counter.
acc_pb->loop_counter++;
}
#endif
}
}
else
{
@ -229,11 +206,38 @@ u16 AcceleratorGetSample()
acc_end_reached = true;
#endif
}
};
}
return ReadAccelerator(acc_loop_addr, acc_end_addr, acc_cur_addr,
acc_pb->audio_addr.sample_format, &acc_pb->adpcm.yn1, &acc_pb->adpcm.yn2,
&acc_pb->adpcm.pred_scale, acc_pb->adpcm.coefs, end_address_reached);
u8 ReadMemory(u32 address) override { return ReadARAM(address); }
void WriteMemory(u32 address, u8 value) override { WriteARAM(value, address); }
};
static std::unique_ptr<Accelerator> s_accelerator = std::make_unique<HLEAccelerator>();
// Sets up the simulated accelerator.
void AcceleratorSetup(PB_TYPE* pb)
{
acc_pb = pb;
s_accelerator->SetStartAddress(HILO_TO_32(pb->audio_addr.loop_addr));
s_accelerator->SetEndAddress(HILO_TO_32(pb->audio_addr.end_addr));
s_accelerator->SetCurrentAddress(HILO_TO_32(pb->audio_addr.cur_addr));
s_accelerator->SetSampleFormat(pb->audio_addr.sample_format);
s_accelerator->SetYn1(pb->adpcm.yn1);
s_accelerator->SetYn2(pb->adpcm.yn2);
s_accelerator->SetPredScale(pb->adpcm.pred_scale);
acc_end_reached = false;
}
// Reads a sample from the accelerator. Also handles looping and
// disabling streams that reached the end (this is done by an exception raised
// by the accelerator on real hardware).
u16 AcceleratorGetSample()
{
// See below for explanations about acc_end_reached.
if (acc_end_reached)
return 0;
return s_accelerator->Read(acc_pb->adpcm.coefs);
}
// Reads samples from the input callback, resamples them to <count> samples at
@ -375,8 +379,7 @@ u32 ResampleAudio(std::function<s16(u32)> input_callback, s16* output, u32 count
// if required.
void GetInputSamples(PB_TYPE& pb, s16* samples, u16 count, const s16* coeffs)
{
u32 cur_addr = HILO_TO_32(pb.audio_addr.cur_addr);
AcceleratorSetup(&pb, &cur_addr);
AcceleratorSetup(&pb);
if (coeffs)
coeffs += pb.coef_select * 0x200;
@ -385,9 +388,12 @@ void GetInputSamples(PB_TYPE& pb, s16* samples, u16 count, const s16* coeffs)
pb.src.cur_addr_frac, HILO_TO_32(pb.src.ratio), pb.src_type, coeffs);
pb.src.cur_addr_frac = (curr_pos & 0xFFFF);
// Update current position in the PB.
pb.audio_addr.cur_addr_hi = static_cast<u16>(cur_addr >> 16) & 0xbfff;
pb.audio_addr.cur_addr_lo = static_cast<u16>(cur_addr);
// Update current position, YN1, YN2 and pred scale in the PB.
pb.audio_addr.cur_addr_hi = static_cast<u16>(s_accelerator->GetCurrentAddress() >> 16);
pb.audio_addr.cur_addr_lo = static_cast<u16>(s_accelerator->GetCurrentAddress());
pb.adpcm.yn1 = s_accelerator->GetYn1();
pb.adpcm.yn2 = s_accelerator->GetYn2();
pb.adpcm.pred_scale = s_accelerator->GetPredScale();
}
// Add samples to an output buffer, with optional volume ramping.

View file

@ -17,6 +17,7 @@
#include "Common/Thread.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/DSP/DSPAccelerator.h"
#include "Core/DSP/DSPCaptureLogger.h"
#include "Core/DSP/DSPCore.h"
#include "Core/DSP/DSPHWInterface.h"
@ -71,6 +72,7 @@ void DSPLLE::DoState(PointerWrap& p)
p.Do(g_dsp.step_counter);
p.DoArray(g_dsp.ifx_regs);
g_dsp.accelerator->DoState(p);
p.Do(g_dsp.mbox[0]);
p.Do(g_dsp.mbox[1]);
Common::UnWriteProtectMemory(g_dsp.iram, DSP_IRAM_BYTE_SIZE, false);