made savestates synchronous and immediate. this allows saving or loading while the emulator is paused, fixes issues where savestate hotkeys would get ignored if pressed too close together, might speed up savestates in some cases, and hopefully makes savestates more stable too.

the intent is to replace the haphazard scheduling and finger-crossing associated with saving/loading with the correct and minimal necessary wait for each thread to reach a known safe location before commencing the savestate operation, and for any already-paused components to not need to be resumed to do so.
This commit is contained in:
nitsuja
2012-05-26 13:09:38 +10:00
committed by skidau
parent 108f69eaa9
commit a81631b58e
33 changed files with 518 additions and 323 deletions
@@ -118,4 +118,23 @@ namespace AudioCommon
bool UseJIT() {
return ac_Config.m_EnableJIT;
}
void PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
if (soundStream)
{
// audio typically doesn't maintain its own "paused" state
// (that's already handled by the CPU and whatever else being paused)
// so it should be good enough to only lock/unlock here.
CMixer* pMixer = soundStream->GetMixer();
if (pMixer)
{
std::mutex& csMixing = pMixer->MixerCritical();
if (doLock)
csMixing.lock();
else
csMixing.unlock();
}
}
}
}
@@ -59,6 +59,7 @@ namespace AudioCommon
void ShutdownSoundStream();
std::vector<std::string> GetSoundBackends();
bool UseJIT();
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
}
#endif // _AUDIO_COMMON_H_
+4 -2
View File
@@ -37,7 +37,9 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples)
if (!samples)
return 0;
if (PowerPC::GetState() != 0)
std::lock_guard<std::mutex> lk(m_csMixing);
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
{
// Silence
memset(samples, 0, numSamples * 4);
@@ -164,7 +166,7 @@ void CMixer::PushSamples(const short *samples, unsigned int num_samples)
// The auto throttle function. This loop will put a ceiling on the CPU MHz.
while (num_samples + Common::AtomicLoad(m_numSamples) > MAX_SAMPLES)
{
if (*PowerPC::GetStatePtr() != 0)
if (*PowerPC::GetStatePtr() != PowerPC::CPU_RUNNING || soundStream->IsMuted())
break;
// Shortcut key for Throttle Skipping
if (Host_GetKeyState('\t'))
+3 -1
View File
@@ -19,6 +19,7 @@
#define _MIXER_H_
#include "WaveFile.h"
#include "StdMutex.h"
// 16 bit Stereo
#define MAX_SAMPLES (1024 * 8)
@@ -89,6 +90,7 @@ public:
}
}
std::mutex& MixerCritical() { return m_csMixing; }
protected:
unsigned int m_sampleRate;
@@ -110,7 +112,7 @@ protected:
u32 m_indexR;
bool m_AIplaying;
std::mutex m_csMixing;
private:
};
@@ -46,6 +46,7 @@ public:
virtual void Stop() {}
virtual void Update() {}
virtual void Clear(bool mute) { m_muted = mute; }
bool IsMuted() { return m_muted; }
virtual void StartLogAudio(const char *filename) {
if (! m_logAudio) {
m_logAudio = true;
+11 -3
View File
@@ -93,8 +93,6 @@ public:
virtual bool Initialize(void *&) = 0;
virtual void Shutdown() = 0;
virtual void DoState(PointerWrap &p) = 0;
virtual void RunLoop(bool enable) = 0;
virtual std::string GetName() = 0;
@@ -131,6 +129,14 @@ public:
static void PopulateList();
static void ClearList();
static void ActivateBackend(const std::string& name);
// waits until is paused and fully idle, and acquires a lock on that state.
// or, if doLock is false, releases a lock on that state and optionally unpauses.
// calls must be balanced and non-recursive (once with doLock true, then once with doLock false).
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) = 0;
// the implementation needs not do synchronization logic, because calls to it are surrounded by PauseAndLock now
virtual void DoState(PointerWrap &p) = 0;
};
extern std::vector<VideoBackend*> g_available_video_backends;
@@ -139,7 +145,6 @@ extern VideoBackend* g_video_backend;
// inherited by dx9/dx11/ogl backends
class VideoBackendHardware : public VideoBackend
{
void DoState(PointerWrap &p);
void RunLoop(bool enable);
bool Initialize(void *&) { InitializeShared(); return true; }
@@ -169,6 +174,9 @@ class VideoBackendHardware : public VideoBackend
writeFn16 Video_PEWrite16();
writeFn32 Video_PEWrite32();
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
void DoState(PointerWrap &p);
protected:
void InitializeShared();
};
+57 -3
View File
@@ -48,6 +48,7 @@
#include "HW/GPFifo.h"
#include "HW/AudioInterface.h"
#include "HW/VideoInterface.h"
#include "HW/EXI.h"
#include "HW/SystemTimers.h"
#include "IPC_HLE/WII_IPC_HLE_Device_usb.h"
@@ -58,6 +59,7 @@
#include "DSPEmulator.h"
#include "ConfigManager.h"
#include "VideoBackendBase.h"
#include "AudioCommon.h"
#include "OnScreenDisplay.h"
#ifdef _WIN32
#include "EmuWindow.h"
@@ -95,13 +97,15 @@ void Stop();
bool g_bStopping = false;
bool g_bHwInit = false;
bool g_bStarted = false;
bool g_bRealWiimote = false;
void *g_pWindowHandle = NULL;
std::string g_stateFileName;
std::thread g_EmuThread;
static std::thread g_cpu_thread;
static bool g_requestRefreshInfo;
static bool g_requestRefreshInfo = false;
static int g_pauseAndLockDepth = 0;
SCoreStartupParameter g_CoreStartupParameter;
@@ -160,16 +164,35 @@ bool IsRunning()
return (GetState() != CORE_UNINITIALIZED) || g_bHwInit;
}
bool IsRunningAndStarted()
{
return g_bStarted;
}
bool IsRunningInCurrentThread()
{
return IsRunning() && ((!g_cpu_thread.joinable()) || g_cpu_thread.get_id() == std::this_thread::get_id());
return IsRunning() && IsCPUThread();
}
bool IsCPUThread()
{
return ((!g_cpu_thread.joinable()) || g_cpu_thread.get_id() == std::this_thread::get_id());
return (g_cpu_thread.joinable() ? (g_cpu_thread.get_id() == std::this_thread::get_id()) : !g_bStarted);
}
bool IsGPUThread()
{
const SCoreStartupParameter& _CoreParameter =
SConfig::GetInstance().m_LocalCoreStartupParameter;
if (_CoreParameter.bCPUThread)
{
return (g_EmuThread.joinable() && (g_EmuThread.get_id() == std::this_thread::get_id()));
}
else
{
return IsCPUThread();
}
}
// This is called from the GUI thread. See the booting call schedule in
// BootManager.cpp
bool Init()
@@ -300,9 +323,13 @@ void CpuThread()
if (!g_stateFileName.empty())
State::LoadAs(g_stateFileName);
g_bStarted = true;
// Enter CPU run loop. When we leave it - we are done.
CCPU::Run();
g_bStarted = false;
return;
}
@@ -323,6 +350,8 @@ void FifoPlayerThread()
if (_CoreParameter.bLockThreads)
Common::SetCurrentThreadAffinity(1); // Force to first core
g_bStarted = true;
// Enter CPU run loop. When we leave it - we are done.
if (FifoPlayer::GetInstance().Open(_CoreParameter.m_strFilename))
{
@@ -330,6 +359,8 @@ void FifoPlayerThread()
FifoPlayer::GetInstance().Close();
}
g_bStarted = false;
return;
}
@@ -554,6 +585,26 @@ void RequestRefreshInfo()
g_requestRefreshInfo = true;
}
bool PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
// let's support recursive locking to simplify things on the caller's side,
// and let's do it at this outer level in case the individual systems don't support it.
if (doLock ? g_pauseAndLockDepth++ : --g_pauseAndLockDepth)
return true;
// first pause or unpause the cpu
bool wasUnpaused = CCPU::PauseAndLock(doLock, unpauseOnUnlock);
ExpansionInterface::PauseAndLock(doLock, unpauseOnUnlock);
// audio has to come after cpu, because cpu thread can wait for audio thread (m_throttle).
AudioCommon::PauseAndLock(doLock, unpauseOnUnlock);
DSP::GetDSPEmulator()->PauseAndLock(doLock, unpauseOnUnlock);
// video has to come after cpu, because cpu thread can wait for video thread (s_efbAccessRequested).
g_video_backend->PauseAndLock(doLock, unpauseOnUnlock);
return wasUnpaused;
}
// Apply Frame Limit and Display FPS info
// This should only be called from VI
void VideoThrottle()
@@ -583,6 +634,9 @@ void VideoThrottle()
g_requestRefreshInfo = false;
SCoreStartupParameter& _CoreParameter = SConfig::GetInstance().m_LocalCoreStartupParameter;
if (ElapseTime == 0)
ElapseTime = 1;
u32 FPS = Common::AtomicLoad(DrawnFrame) * 1000 / ElapseTime;
u32 VPS = DrawnVideo * 1000 / ElapseTime;
u32 Speed = DrawnVideo * (100 * 1000) / (VideoInterface::TargetRefreshRate * ElapseTime);
+9 -1
View File
@@ -54,9 +54,11 @@ void Stop();
std::string StopMessage(bool, std::string);
bool IsRunning();
bool IsRunningAndStarted(); // is running and the cpu loop has been entered
bool IsRunningInCurrentThread(); // this tells us whether we are running in the cpu thread.
bool IsCPUThread(); // this tells us whether we are the cpu thread.
bool IsGPUThread();
void SetState(EState _State);
EState GetState();
@@ -87,6 +89,12 @@ bool ShouldSkipFrame(int skipped);
void VideoThrottle();
void RequestRefreshInfo();
// waits until all systems are paused and fully idle, and acquires a lock on that state.
// or, if doLock is false, releases a lock on that state and optionally unpauses.
// calls must be balanced (once with doLock true, then once with doLock false) but may be recursive.
// the return value of the first call should be passed in as the second argument of the second call.
bool PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
#ifdef RERECORDING
void FrameUpdate();
+1
View File
@@ -32,6 +32,7 @@ public:
virtual void Shutdown() = 0;
virtual void DoState(PointerWrap &p) = 0;
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) = 0;
virtual void DSP_WriteMailBoxHigh(bool _CPUMailbox, unsigned short) = 0;
virtual void DSP_WriteMailBoxLow(bool _CPUMailbox, unsigned short) = 0;
+30 -1
View File
@@ -31,6 +31,7 @@ namespace
{
static Common::Event m_StepEvent;
static Common::Event *m_SyncEvent;
static std::mutex m_csCpuOccupied;
}
void CCPU::Init(int cpu_core)
@@ -47,6 +48,7 @@ void CCPU::Shutdown()
void CCPU::Run()
{
std::lock_guard<std::mutex> lk(m_csCpuOccupied);
Host_UpdateDisasmDialog();
while (true)
@@ -60,8 +62,12 @@ reswitch:
break;
case PowerPC::CPU_STEPPING:
m_StepEvent.Wait();
m_csCpuOccupied.unlock();
//1: wait for step command..
m_StepEvent.Wait();
m_csCpuOccupied.lock();
if (PowerPC::GetState() == PowerPC::CPU_POWERDOWN)
return;
if (PowerPC::GetState() != PowerPC::CPU_STEPPING)
@@ -132,3 +138,26 @@ void CCPU::Break()
{
EnableStepping(true);
}
bool CCPU::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
bool wasUnpaused = !IsStepping();
if (doLock)
{
// we can't use EnableStepping, that would causes deadlocks with both audio and video
PowerPC::Pause();
if (!Core::IsCPUThread())
m_csCpuOccupied.lock();
}
else
{
if (unpauseOnUnlock)
{
PowerPC::Start();
m_StepEvent.Set();
}
if (!Core::IsCPUThread())
m_csCpuOccupied.unlock();
}
return wasUnpaused;
}
+8
View File
@@ -54,6 +54,14 @@ public:
// is stepping ?
static bool IsStepping();
// waits until is stepping and is ready for a command (paused and fully idle), and acquires a lock on that state.
// or, if doLock is false, releases a lock on that state and optionally re-disables stepping.
// calls must be balanced and non-recursive (once with doLock true, then once with doLock false).
// intended (but not required) to be called from another thread,
// e.g. when the GUI thread wants to make sure everything is paused so that it can create a savestate.
// the return value is whether the cpu was unpaused before the call.
static bool PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
};
#endif
+35 -8
View File
@@ -130,7 +130,24 @@ void DSPHLE::SwapUCode(u32 _crc)
void DSPHLE::DoState(PointerWrap &p)
{
bool prevInitMixer = m_InitMixer;
p.Do(m_InitMixer);
if (prevInitMixer != m_InitMixer && p.GetMode() == PointerWrap::MODE_READ)
{
if (m_InitMixer)
{
InitMixer();
AudioCommon::PauseAndLock(true);
}
else
{
AudioCommon::PauseAndLock(false);
soundStream->Stop();
delete soundStream;
soundStream = NULL;
}
}
p.Do(m_DSPControl);
p.Do(m_dspState);
@@ -230,6 +247,17 @@ void DSPHLE::DSP_WriteMailBoxLow(bool _CPUMailbox, unsigned short _Value)
}
}
void DSPHLE::InitMixer()
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
delete soundStream;
soundStream = AudioCommon::InitSoundStream(new HLEMixer(this, AISampleRate, DACSampleRate, ac_Config.iFrequency), m_hWnd);
if(!soundStream) PanicAlert("Error starting up sound stream");
// Mixer is initialized
m_InitMixer = true;
}
// Other DSP fuctions
u16 DSPHLE::DSP_WriteControlRegister(unsigned short _Value)
{
@@ -238,14 +266,7 @@ u16 DSPHLE::DSP_WriteControlRegister(unsigned short _Value)
{
if (!Temp.DSPHalt && Temp.DSPInit)
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
soundStream = AudioCommon::InitSoundStream(
new HLEMixer(this, AISampleRate, DACSampleRate, ac_Config.iFrequency), m_hWnd);
if(!soundStream) PanicAlert("Error starting up sound stream");
// Mixer is initialized
m_InitMixer = true;
InitMixer();
}
}
@@ -295,3 +316,9 @@ void DSPHLE::DSP_ClearAudioBuffer(bool mute)
if (soundStream)
soundStream->Clear(mute);
}
void DSPHLE::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
if (doLock || unpauseOnUnlock)
DSP_ClearAudioBuffer(doLock);
}
+2
View File
@@ -34,6 +34,7 @@ public:
virtual bool IsLLE() { return false; }
virtual void DoState(PointerWrap &p);
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
virtual void DSP_WriteMailBoxHigh(bool _CPUMailbox, unsigned short);
virtual void DSP_WriteMailBoxLow(bool _CPUMailbox, unsigned short);
@@ -55,6 +56,7 @@ public:
private:
void SendMailToDSP(u32 _uMail);
void InitMixer();
// Declarations and definitions
void *m_hWnd;
+45 -7
View File
@@ -79,6 +79,24 @@ void DSPLLE::DoState(PointerWrap &p)
p.DoArray(g_dsp.dram, DSP_DRAM_SIZE);
p.Do(cyclesLeft);
p.Do(m_cycle_count);
bool prevInitMixer = m_InitMixer;
p.Do(m_InitMixer);
if (prevInitMixer != m_InitMixer && p.GetMode() == PointerWrap::MODE_READ)
{
if (m_InitMixer)
{
InitMixer();
AudioCommon::PauseAndLock(true);
}
else
{
AudioCommon::PauseAndLock(false);
soundStream->Stop();
delete soundStream;
soundStream = NULL;
}
}
}
// Regular thread
@@ -110,7 +128,9 @@ void DSPLLE::dsp_thread(DSPLLE *dsp_lle)
while (dsp_lle->m_bIsRunning)
{
int cycles = (int)dsp_lle->m_cycle_count;
if (cycles > 0) {
if (cycles > 0)
{
std::lock_guard<std::mutex> lk(dsp_lle->m_csDSPThreadActive);
if (dspjit)
{
DSPCore_RunCycles(cycles);
@@ -179,6 +199,17 @@ void DSPLLE::Shutdown()
DSPCore_Shutdown();
}
void DSPLLE::InitMixer()
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
delete soundStream;
soundStream = AudioCommon::InitSoundStream(new CMixer(AISampleRate, DACSampleRate, ac_Config.iFrequency), m_hWnd);
if(!soundStream) PanicAlert("Error starting up sound stream");
// Mixer is initialized
m_InitMixer = true;
}
u16 DSPLLE::DSP_WriteControlRegister(u16 _uFlag)
{
UDSPControl Temp(_uFlag);
@@ -186,12 +217,7 @@ u16 DSPLLE::DSP_WriteControlRegister(u16 _uFlag)
{
if (!Temp.DSPHalt)
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
soundStream = AudioCommon::InitSoundStream(new CMixer(AISampleRate, DACSampleRate, ac_Config.iFrequency), m_hWnd);
if(!soundStream) PanicAlert("Error starting up sound stream");
// Mixer is initialized
m_InitMixer = true;
InitMixer();
}
}
DSPInterpreter::WriteCR(_uFlag);
@@ -334,3 +360,15 @@ void DSPLLE::DSP_ClearAudioBuffer(bool mute)
if (soundStream)
soundStream->Clear(mute);
}
void DSPLLE::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
if (doLock || unpauseOnUnlock)
DSP_ClearAudioBuffer(doLock);
if (doLock)
m_csDSPThreadActive.lock();
else
m_csDSPThreadActive.unlock();
}
+3
View File
@@ -32,6 +32,7 @@ public:
virtual bool IsLLE() { return true; }
virtual void DoState(PointerWrap &p);
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
virtual void DSP_WriteMailBoxHigh(bool _CPUMailbox, unsigned short);
virtual void DSP_WriteMailBoxLow(bool _CPUMailbox, unsigned short);
@@ -46,8 +47,10 @@ public:
private:
static void dsp_thread(DSPLLE* lpParameter);
void InitMixer();
std::thread m_hDSPThread;
std::mutex m_csDSPThreadActive;
bool m_InitMixer;
void *m_hWnd;
bool m_bWii;
+18
View File
@@ -74,6 +74,13 @@ void OnAfterLoad()
g_Channels[c]->OnAfterLoad();
}
void PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
for (int c = 0; c < NUM_CHANNELS; ++c)
g_Channels[c]->PauseAndLock(doLock, unpauseOnUnlock);
}
void ChangeDeviceCallback(u64 userdata, int cyclesLate)
{
u8 channel = (u8)(userdata >> 32);
@@ -91,6 +98,17 @@ void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 devi
CoreTiming::ScheduleEvent_Threadsafe(500000000, changeDevice, ((u64)channel << 32) | ((u64)device_type << 16) | device_num);
}
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex)
{
for (int i = 0; i < NUM_CHANNELS; ++i)
{
IEXIDevice* device = g_Channels[i]->FindDevice(device_type, customIndex);
if (device)
return device;
}
return NULL;
}
// Unused (?!)
void Update()
{
+2
View File
@@ -29,12 +29,14 @@ void Init();
void Shutdown();
void DoState(PointerWrap &p);
void OnAfterLoad();
void PauseAndLock(bool doLock, bool unpauseOnUnlock);
void Update();
void UpdateInterrupts();
void ChangeDeviceCallback(u64 userdata, int cyclesLate);
void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num);
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1);
void Read32(u32& _uReturnValue, const u32 _iAddress);
void Write32(const u32 _iValue, const u32 _iAddress);
+16
View File
@@ -315,3 +315,19 @@ void CEXIChannel::OnAfterLoad()
m_pDevices[d]->OnAfterLoad();
}
void CEXIChannel::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
for (int d = 0; d < NUM_DEVICES; ++d)
m_pDevices[d]->PauseAndLock(doLock, unpauseOnUnlock);
}
IEXIDevice* CEXIChannel::FindDevice(TEXIDevices device_type, int customIndex)
{
for (int d = 0; d < NUM_DEVICES; ++d)
{
IEXIDevice* device = m_pDevices[d]->FindDevice(device_type, customIndex);
if (device)
return device;
}
return NULL;
}
+2
View File
@@ -113,6 +113,7 @@ private:
public:
// get device
IEXIDevice* GetDevice(const u8 _CHIP_SELECT);
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1);
CEXIChannel(u32 ChannelId);
~CEXIChannel();
@@ -131,6 +132,7 @@ public:
void UpdateInterrupts();
void DoState(PointerWrap &p);
void OnAfterLoad();
void PauseAndLock(bool doLock, bool unpauseOnUnlock);
// This should only be used to transition interrupts from SP1 to Channel 2
void SetEXIINT(bool exiint) { m_Status.EXIINT = !!exiint; }
+2
View File
@@ -54,6 +54,8 @@ public:
virtual void SetCS(int) {}
virtual void DoState(PointerWrap&) {}
virtual void OnAfterLoad() {}
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) {}
virtual IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1) { return (device_type == m_deviceType) ? this : NULL; }
// Update
virtual void Update() {}

Some files were not shown because too many files have changed in this diff Show More