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:
parent
108f69eaa9
commit
a81631b58e
|
@ -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_
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -40,9 +40,11 @@
|
|||
|
||||
void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate)
|
||||
{
|
||||
// casting userdata seems less error-prone than indexing a static (creation order issues, etc.)
|
||||
CEXIMemoryCard *ptr = (CEXIMemoryCard*)userdata;
|
||||
ptr->Flush();
|
||||
// note that userdata is forbidden to be a pointer, due to the implemenation of EventDoState
|
||||
int card_index = (int)userdata;
|
||||
CEXIMemoryCard* pThis = (CEXIMemoryCard*)ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARD, card_index);
|
||||
if (pThis)
|
||||
pThis->Flush();
|
||||
}
|
||||
|
||||
CEXIMemoryCard::CEXIMemoryCard(const int index)
|
||||
|
@ -237,7 +239,7 @@ void CEXIMemoryCard::SetCS(int cs)
|
|||
// Page written to memory card, not just to buffer - let's schedule a flush 0.5b cycles into the future (1 sec)
|
||||
// But first we unschedule already scheduled flushes - no point in flushing once per page for a large write.
|
||||
CoreTiming::RemoveEvent(et_this_card);
|
||||
CoreTiming::ScheduleEvent(500000000, et_this_card, (u64)this);
|
||||
CoreTiming::ScheduleEvent(500000000, et_this_card, (u64)card_index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -423,6 +425,19 @@ void CEXIMemoryCard::TransferByte(u8 &byte)
|
|||
DEBUG_LOG(EXPANSIONINTERFACE, "EXI MEMCARD: < %02x", byte);
|
||||
}
|
||||
|
||||
void CEXIMemoryCard::PauseAndLock(bool doLock, bool unpauseOnUnlock)
|
||||
{
|
||||
if (doLock)
|
||||
{
|
||||
// we don't exactly have anything to pause,
|
||||
// but let's make sure the flush thread isn't running.
|
||||
if (flushThread.joinable())
|
||||
{
|
||||
flushThread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CEXIMemoryCard::OnAfterLoad()
|
||||
{
|
||||
|
||||
|
@ -452,5 +467,15 @@ void CEXIMemoryCard::DoState(PointerWrap &p)
|
|||
p.Do(card_id);
|
||||
p.Do(memory_card_size);
|
||||
p.DoArray(memory_card_content, memory_card_size);
|
||||
p.Do(card_index);
|
||||
}
|
||||
}
|
||||
|
||||
IEXIDevice* CEXIMemoryCard::FindDevice(TEXIDevices device_type, int customIndex)
|
||||
{
|
||||
if (device_type != m_deviceType)
|
||||
return NULL;
|
||||
if (customIndex != card_index)
|
||||
return NULL;
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ public:
|
|||
bool IsPresent();
|
||||
void DoState(PointerWrap &p);
|
||||
void OnAfterLoad();
|
||||
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
|
||||
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1);
|
||||
|
||||
private:
|
||||
// This is scheduled whenever a page write is issued. The this pointer is passed
|
||||
|
|
|
@ -229,8 +229,6 @@ static u8 g_SIBuffer[128];
|
|||
|
||||
void DoState(PointerWrap &p)
|
||||
{
|
||||
bool reloadOnState = SConfig::GetInstance().b_reloadMCOnState;
|
||||
|
||||
for(int i = 0; i < NUMBER_OF_CHANNELS; i++)
|
||||
{
|
||||
p.Do(g_Channel[i].m_InHi.Hex);
|
||||
|
@ -247,8 +245,7 @@ void DoState(PointerWrap &p)
|
|||
// if we had to create a temporary device, discard it if we're not loading.
|
||||
// also, if no movie is active, we'll assume the user wants to keep their current devices
|
||||
// instead of the ones they had when the savestate was created.
|
||||
if(p.GetMode() != PointerWrap::MODE_READ ||
|
||||
(reloadOnState && !Movie::IsRecordingInput() && !Movie::IsPlayingInput()))
|
||||
if(p.GetMode() != PointerWrap::MODE_READ)
|
||||
{
|
||||
delete pSaveDevice;
|
||||
}
|
||||
|
|
|
@ -725,9 +725,6 @@ void UpdateInterrupts()
|
|||
{
|
||||
ProcessorInterface::SetInterrupt(ProcessorInterface::INT_CAUSE_VI, false);
|
||||
}
|
||||
|
||||
if (m_InterruptRegister[1].IR_INT && m_InterruptRegister[1].IR_MASK)
|
||||
State::ProcessRequestedStates(1);
|
||||
}
|
||||
|
||||
u32 GetXFBAddressTop()
|
||||
|
|
|
@ -55,11 +55,7 @@ static unsigned char __LZO_MMODEL out[OUT_LEN];
|
|||
|
||||
static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);
|
||||
|
||||
static volatile bool g_op_in_progress = false;
|
||||
|
||||
static int ev_FileSave, ev_BufferSave, ev_FileLoad, ev_BufferLoad, ev_FileVerify, ev_BufferVerify;
|
||||
|
||||
static std::string g_current_filename, g_last_filename;
|
||||
static std::string g_last_filename;
|
||||
|
||||
static CallbackFunc g_onAfterLoadCb = NULL;
|
||||
|
||||
|
@ -68,16 +64,14 @@ static std::vector<u8> g_undo_load_buffer;
|
|||
static std::vector<u8> g_current_buffer;
|
||||
static int g_loadDepth = 0;
|
||||
|
||||
static std::mutex g_cs_undo_load_buffer;
|
||||
static std::mutex g_cs_current_buffer;
|
||||
static Common::Event g_compressAndDumpStateSyncEvent;
|
||||
|
||||
static std::thread g_save_thread;
|
||||
|
||||
static const u8 NUM_HOOKS = 2;
|
||||
static u8 waiting;
|
||||
static u8 waitingslot;
|
||||
static u64 lastCheckedStates[NUM_HOOKS];
|
||||
static u8 hook;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
static const int STATE_VERSION = 7;
|
||||
static const int STATE_VERSION = 8;
|
||||
|
||||
struct StateHeader
|
||||
{
|
||||
|
@ -121,8 +115,6 @@ void DoState(PointerWrap &p)
|
|||
p.DoMarker("Version");
|
||||
|
||||
// Begin with video backend, so that it gets a chance to clear it's caches and writeback modified things to RAM
|
||||
// Pause the video thread in multi-threaded mode
|
||||
g_video_backend->RunLoop(false);
|
||||
g_video_backend->DoState(p);
|
||||
p.DoMarker("video_backend");
|
||||
|
||||
|
@ -138,74 +130,78 @@ void DoState(PointerWrap &p)
|
|||
p.DoMarker("CoreTiming");
|
||||
Movie::DoState(p);
|
||||
p.DoMarker("Movie");
|
||||
|
||||
// Resume the video thread
|
||||
g_video_backend->RunLoop(true);
|
||||
}
|
||||
|
||||
void ResetCounters()
|
||||
void LoadFromBuffer(std::vector<u8>& buffer)
|
||||
{
|
||||
for (int i = 0; i < NUM_HOOKS; ++i)
|
||||
lastCheckedStates[i] = CoreTiming::GetTicks();
|
||||
}
|
||||
bool wasUnpaused = Core::PauseAndLock(true);
|
||||
|
||||
void LoadBufferStateCallback(u64 userdata, int cyclesLate)
|
||||
{
|
||||
u8* ptr = &g_current_buffer[0];
|
||||
u8* ptr = &buffer[0];
|
||||
PointerWrap p(&ptr, PointerWrap::MODE_READ);
|
||||
DoState(p);
|
||||
|
||||
Core::DisplayMessage("Loaded state", 2000);
|
||||
|
||||
g_op_in_progress = false;
|
||||
Core::PauseAndLock(false, wasUnpaused);
|
||||
}
|
||||
|
||||
void SaveBufferStateCallback(u64 userdata, int cyclesLate)
|
||||
void SaveToBuffer(std::vector<u8>& buffer)
|
||||
{
|
||||
bool wasUnpaused = Core::PauseAndLock(true);
|
||||
|
||||
u8* ptr = NULL;
|
||||
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
|
||||
|
||||
DoState(p);
|
||||
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
|
||||
g_current_buffer.resize(buffer_size);
|
||||
|
||||
ptr = &g_current_buffer[0];
|
||||
buffer.resize(buffer_size);
|
||||
|
||||
ptr = &buffer[0];
|
||||
p.SetMode(PointerWrap::MODE_WRITE);
|
||||
DoState(p);
|
||||
|
||||
g_op_in_progress = false;
|
||||
Core::PauseAndLock(false, wasUnpaused);
|
||||
}
|
||||
|
||||
void VerifyBufferStateCallback(u64 userdata, int cyclesLate)
|
||||
void VerifyBuffer(std::vector<u8>& buffer)
|
||||
{
|
||||
u8* ptr = &g_current_buffer[0];
|
||||
bool wasUnpaused = Core::PauseAndLock(true);
|
||||
|
||||
u8* ptr = &buffer[0];
|
||||
PointerWrap p(&ptr, PointerWrap::MODE_VERIFY);
|
||||
DoState(p);
|
||||
|
||||
Core::DisplayMessage("Verified state", 2000);
|
||||
|
||||
g_op_in_progress = false;
|
||||
Core::PauseAndLock(false, wasUnpaused);
|
||||
}
|
||||
|
||||
void CompressAndDumpState(const std::vector<u8>* save_arg)
|
||||
struct CompressAndDumpState_args
|
||||
{
|
||||
const u8* const buffer_data = &(*save_arg)[0];
|
||||
const size_t buffer_size = save_arg->size();
|
||||
std::vector<u8>* buffer_vector;
|
||||
std::mutex* buffer_mutex;
|
||||
std::string filename;
|
||||
};
|
||||
|
||||
void CompressAndDumpState(CompressAndDumpState_args save_args)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(*save_args.buffer_mutex);
|
||||
g_compressAndDumpStateSyncEvent.Set();
|
||||
|
||||
const u8* const buffer_data = &(*(save_args.buffer_vector))[0];
|
||||
const size_t buffer_size = (save_args.buffer_vector)->size();
|
||||
std::string& filename = save_args.filename;
|
||||
|
||||
// For easy debugging
|
||||
Common::SetCurrentThreadName("SaveState thread");
|
||||
|
||||
// Moving to last overwritten save-state
|
||||
if (File::Exists(g_current_filename))
|
||||
if (File::Exists(filename))
|
||||
{
|
||||
if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
|
||||
File::Delete((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"));
|
||||
|
||||
if (!File::Rename(g_current_filename, File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
|
||||
if (!File::Rename(filename, File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
|
||||
Core::DisplayMessage("Failed to move previous state to state undo backup", 1000);
|
||||
}
|
||||
|
||||
File::IOFile f(g_current_filename, "wb");
|
||||
File::IOFile f(filename, "wb");
|
||||
if (!f)
|
||||
{
|
||||
Core::DisplayMessage("Could not save state", 2000);
|
||||
|
@ -251,17 +247,13 @@ void CompressAndDumpState(const std::vector<u8>* save_arg)
|
|||
}
|
||||
|
||||
Core::DisplayMessage(StringFromFormat("Saved State to %s",
|
||||
g_current_filename.c_str()).c_str(), 2000);
|
||||
|
||||
g_op_in_progress = false;
|
||||
filename.c_str()).c_str(), 2000);
|
||||
}
|
||||
|
||||
void SaveFileStateCallback(u64 userdata, int cyclesLate)
|
||||
void SaveAs(const std::string& filename)
|
||||
{
|
||||
// Pause the core while we save the state
|
||||
CCPU::EnableStepping(true);
|
||||
|
||||
Flush();
|
||||
bool wasUnpaused = Core::PauseAndLock(true);
|
||||
|
||||
// Measure the size of the buffer.
|
||||
u8 *ptr = NULL;
|
||||
|
@ -270,26 +262,46 @@ void SaveFileStateCallback(u64 userdata, int cyclesLate)
|
|||
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
|
||||
|
||||
// Then actually do the write.
|
||||
g_current_buffer.resize(buffer_size);
|
||||
ptr = &g_current_buffer[0];
|
||||
p.SetMode(PointerWrap::MODE_WRITE);
|
||||
DoState(p);
|
||||
|
||||
if ((Movie::IsRecordingInput() || Movie::IsPlayingInput()) && !Movie::IsJustStartingRecordingInputFromSaveState())
|
||||
Movie::SaveRecording((g_current_filename + ".dtm").c_str());
|
||||
else if (!Movie::IsRecordingInput() && !Movie::IsPlayingInput())
|
||||
File::Delete(g_current_filename + ".dtm");
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_cs_current_buffer);
|
||||
g_current_buffer.resize(buffer_size);
|
||||
ptr = &g_current_buffer[0];
|
||||
p.SetMode(PointerWrap::MODE_WRITE);
|
||||
DoState(p);
|
||||
}
|
||||
|
||||
Core::DisplayMessage("Saving State...", 1000);
|
||||
if (p.GetMode() == PointerWrap::MODE_WRITE)
|
||||
{
|
||||
Core::DisplayMessage("Saving State...", 1000);
|
||||
if ((Movie::IsRecordingInput() || Movie::IsPlayingInput()) && !Movie::IsJustStartingRecordingInputFromSaveState())
|
||||
Movie::SaveRecording((filename + ".dtm").c_str());
|
||||
else if (!Movie::IsRecordingInput() && !Movie::IsPlayingInput())
|
||||
File::Delete(filename + ".dtm");
|
||||
|
||||
g_save_thread = std::thread(CompressAndDumpState, &g_current_buffer);
|
||||
CompressAndDumpState_args save_args;
|
||||
save_args.buffer_vector = &g_current_buffer;
|
||||
save_args.buffer_mutex = &g_cs_current_buffer;
|
||||
save_args.filename = filename;
|
||||
|
||||
Flush();
|
||||
g_save_thread = std::thread(CompressAndDumpState, save_args);
|
||||
g_compressAndDumpStateSyncEvent.Wait();
|
||||
|
||||
g_last_filename = filename;
|
||||
}
|
||||
else
|
||||
{
|
||||
// someone aborted the save by changing the mode?
|
||||
Core::DisplayMessage("Unable to Save : Internal DoState Error", 4000);
|
||||
}
|
||||
|
||||
// Resume the core and disable stepping
|
||||
CCPU::EnableStepping(false);
|
||||
Core::PauseAndLock(false, wasUnpaused);
|
||||
}
|
||||
|
||||
void LoadFileStateData(std::string& filename, std::vector<u8>& ret_data)
|
||||
void LoadFileStateData(const std::string& filename, std::vector<u8>& ret_data)
|
||||
{
|
||||
Flush();
|
||||
File::IOFile f(filename, "rb");
|
||||
if (!f)
|
||||
{
|
||||
|
@ -353,35 +365,54 @@ void LoadFileStateData(std::string& filename, std::vector<u8>& ret_data)
|
|||
ret_data.swap(buffer);
|
||||
}
|
||||
|
||||
void LoadFileStateCallback(u64 userdata, int cyclesLate)
|
||||
void LoadAs(const std::string& filename)
|
||||
{
|
||||
// Stop the core while we load the state
|
||||
CCPU::EnableStepping(true);
|
||||
bool wasUnpaused = Core::PauseAndLock(true);
|
||||
|
||||
#if defined _DEBUG && defined _WIN32
|
||||
// we use _CRTDBG_DELAY_FREE_MEM_DF (as a speed hack?),
|
||||
// but it was causing us to leak gigantic amounts of memory here,
|
||||
// enough that only a few savestates could be loaded before crashing,
|
||||
// so let's disable it temporarily.
|
||||
int tmpflag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
|
||||
if (g_loadDepth == 0)
|
||||
_CrtSetDbgFlag(tmpflag & ~_CRTDBG_DELAY_FREE_MEM_DF);
|
||||
#endif
|
||||
|
||||
g_loadDepth++;
|
||||
|
||||
Flush();
|
||||
|
||||
// Save temp buffer for undo load state
|
||||
// TODO: this should be controlled by a user option,
|
||||
// because it slows down every savestate load to provide an often-unused feature.
|
||||
SaveBufferStateCallback(userdata, cyclesLate);
|
||||
g_undo_load_buffer.swap(g_current_buffer);
|
||||
|
||||
std::vector<u8> buffer;
|
||||
LoadFileStateData(g_current_filename, buffer);
|
||||
|
||||
if (!buffer.empty())
|
||||
{
|
||||
u8 *ptr = &buffer[0];
|
||||
PointerWrap p(&ptr, PointerWrap::MODE_READ);
|
||||
DoState(p);
|
||||
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
|
||||
SaveToBuffer(g_undo_load_buffer);
|
||||
}
|
||||
|
||||
if (p.GetMode() == PointerWrap::MODE_READ)
|
||||
bool loaded = false;
|
||||
bool loadedSuccessfully = false;
|
||||
|
||||
// brackets here are so buffer gets freed ASAP
|
||||
{
|
||||
std::vector<u8> buffer;
|
||||
LoadFileStateData(filename, buffer);
|
||||
|
||||
if (!buffer.empty())
|
||||
{
|
||||
Core::DisplayMessage(StringFromFormat("Loaded state from %s", g_current_filename.c_str()).c_str(), 2000);
|
||||
if (File::Exists(g_current_filename + ".dtm"))
|
||||
Movie::LoadInput((g_current_filename + ".dtm").c_str());
|
||||
u8 *ptr = &buffer[0];
|
||||
PointerWrap p(&ptr, PointerWrap::MODE_READ);
|
||||
DoState(p);
|
||||
loaded = true;
|
||||
loadedSuccessfully = (p.GetMode() == PointerWrap::MODE_READ);
|
||||
}
|
||||
}
|
||||
|
||||
if (loaded)
|
||||
{
|
||||
if (loadedSuccessfully)
|
||||
{
|
||||
Core::DisplayMessage(StringFromFormat("Loaded state from %s", filename.c_str()).c_str(), 2000);
|
||||
if (File::Exists(filename + ".dtm"))
|
||||
Movie::LoadInput((filename + ".dtm").c_str());
|
||||
else if (!Movie::IsJustStartingRecordingInputFromSaveState())
|
||||
Movie::EndPlayInput(false);
|
||||
}
|
||||
|
@ -390,25 +421,27 @@ void LoadFileStateCallback(u64 userdata, int cyclesLate)
|
|||
// failed to load
|
||||
Core::DisplayMessage("Unable to Load : Can't load state from other revisions !", 4000);
|
||||
|
||||
// since we're probably in an inconsistent state now (and might crash or whatever), undo.
|
||||
// since we could be in an inconsistent state now (and might crash or whatever), undo.
|
||||
if (g_loadDepth < 2)
|
||||
UndoLoadState();
|
||||
}
|
||||
}
|
||||
|
||||
ResetCounters();
|
||||
|
||||
HW::OnAfterLoad();
|
||||
|
||||
g_op_in_progress = false;
|
||||
|
||||
if (g_onAfterLoadCb)
|
||||
g_onAfterLoadCb();
|
||||
|
||||
g_loadDepth--;
|
||||
|
||||
#if defined _DEBUG && defined _WIN32
|
||||
// restore _CRTDBG_DELAY_FREE_MEM_DF
|
||||
if (g_loadDepth == 0)
|
||||
_CrtSetDbgFlag(tmpflag);
|
||||
#endif
|
||||
|
||||
// resume dat core
|
||||
CCPU::EnableStepping(false);
|
||||
Core::PauseAndLock(false, wasUnpaused);
|
||||
}
|
||||
|
||||
void SetOnAfterLoadCallback(CallbackFunc callback)
|
||||
|
@ -416,12 +449,12 @@ void SetOnAfterLoadCallback(CallbackFunc callback)
|
|||
g_onAfterLoadCb = callback;
|
||||
}
|
||||
|
||||
void VerifyFileStateCallback(u64 userdata, int cyclesLate)
|
||||
void VerifyAt(const std::string& filename)
|
||||
{
|
||||
Flush();
|
||||
bool wasUnpaused = Core::PauseAndLock(true);
|
||||
|
||||
std::vector<u8> buffer;
|
||||
LoadFileStateData(g_current_filename, buffer);
|
||||
LoadFileStateData(filename, buffer);
|
||||
|
||||
if (!buffer.empty())
|
||||
{
|
||||
|
@ -430,29 +463,17 @@ void VerifyFileStateCallback(u64 userdata, int cyclesLate)
|
|||
DoState(p);
|
||||
|
||||
if (p.GetMode() == PointerWrap::MODE_VERIFY)
|
||||
Core::DisplayMessage(StringFromFormat("Verified state at %s", g_current_filename.c_str()).c_str(), 2000);
|
||||
Core::DisplayMessage(StringFromFormat("Verified state at %s", filename.c_str()).c_str(), 2000);
|
||||
else
|
||||
Core::DisplayMessage("Unable to Verify : Can't verify state from other revisions !", 4000);
|
||||
}
|
||||
|
||||
g_op_in_progress = false;
|
||||
Core::PauseAndLock(false, wasUnpaused);
|
||||
}
|
||||
|
||||
|
||||
void Init()
|
||||
{
|
||||
ev_FileLoad = CoreTiming::RegisterEvent("LoadState", &LoadFileStateCallback);
|
||||
ev_FileSave = CoreTiming::RegisterEvent("SaveState", &SaveFileStateCallback);
|
||||
ev_FileVerify = CoreTiming::RegisterEvent("VerifyState", &VerifyFileStateCallback);
|
||||
|
||||
ev_BufferLoad = CoreTiming::RegisterEvent("LoadBufferState", &LoadBufferStateCallback);
|
||||
ev_BufferSave = CoreTiming::RegisterEvent("SaveBufferState", &SaveBufferStateCallback);
|
||||
ev_BufferVerify = CoreTiming::RegisterEvent("VerifyBufferState", &VerifyBufferStateCallback);
|
||||
|
||||
waiting = STATE_NONE;
|
||||
waitingslot = 0;
|
||||
hook = 0;
|
||||
ResetCounters();
|
||||
|
||||
if (lzo_init() != LZO_E_OK)
|
||||
PanicAlertT("Internal LZO Error - lzo_init() failed");
|
||||
}
|
||||
|
@ -462,15 +483,15 @@ void Shutdown()
|
|||
Flush();
|
||||
|
||||
// swapping with an empty vector, rather than clear()ing
|
||||
// this gives a better guarantee to free the allocated memory right NOW
|
||||
// this gives a better guarantee to free the allocated memory right NOW (as opposed to, actually, never)
|
||||
{
|
||||
std::vector<u8> tmp;
|
||||
g_current_buffer.swap(tmp);
|
||||
std::lock_guard<std::mutex> lk(g_cs_current_buffer);
|
||||
std::vector<u8>().swap(g_current_buffer);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<u8> tmp;
|
||||
g_undo_load_buffer.swap(tmp);
|
||||
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
|
||||
std::vector<u8>().swap(g_undo_load_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,102 +501,14 @@ static std::string MakeStateFilename(int number)
|
|||
SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), number);
|
||||
}
|
||||
|
||||
void ScheduleFileEvent(const std::string &filename, int ev, bool immediate)
|
||||
{
|
||||
if (g_op_in_progress)
|
||||
Flush();
|
||||
if (g_op_in_progress)
|
||||
return;
|
||||
g_op_in_progress = true;
|
||||
|
||||
g_current_filename = filename;
|
||||
|
||||
if (immediate)
|
||||
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev);
|
||||
else
|
||||
CoreTiming::ScheduleEvent_Threadsafe(0, ev);
|
||||
}
|
||||
|
||||
void SaveAs(const std::string &filename, bool immediate)
|
||||
{
|
||||
g_last_filename = filename;
|
||||
ScheduleFileEvent(filename, ev_FileSave, immediate);
|
||||
}
|
||||
|
||||
void SaveAs(const std::string &filename)
|
||||
{
|
||||
SaveAs(filename, true);
|
||||
}
|
||||
|
||||
void LoadAs(const std::string &filename, bool immediate)
|
||||
{
|
||||
ScheduleFileEvent(filename, ev_FileLoad, immediate);
|
||||
}
|
||||
|
||||
void LoadAs(const std::string &filename)
|
||||
{
|
||||
LoadAs(filename, true);
|
||||
}
|
||||
|
||||
void VerifyAt(const std::string &filename)
|
||||
{
|
||||
ScheduleFileEvent(filename, ev_FileVerify, true);
|
||||
}
|
||||
|
||||
bool ProcessRequestedStates(int priority)
|
||||
{
|
||||
bool save = true;
|
||||
|
||||
if (hook == priority)
|
||||
{
|
||||
if (waiting == STATE_SAVE)
|
||||
{
|
||||
SaveAs(MakeStateFilename(waitingslot), false);
|
||||
waitingslot = 0;
|
||||
waiting = STATE_NONE;
|
||||
}
|
||||
else if (waiting == STATE_LOAD)
|
||||
{
|
||||
LoadAs(MakeStateFilename(waitingslot), false);
|
||||
waitingslot = 0;
|
||||
waiting = STATE_NONE;
|
||||
save = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Change hooks if the new hook gets called frequently (at least once a frame) and if the old
|
||||
// hook has not been called for the last 5 seconds
|
||||
if ((CoreTiming::GetTicks() - lastCheckedStates[priority]) < (VideoInterface::GetTicksPerFrame()))
|
||||
{
|
||||
lastCheckedStates[priority] = CoreTiming::GetTicks();
|
||||
if (hook < NUM_HOOKS && priority >= (hook + 1) &&
|
||||
(lastCheckedStates[priority] - lastCheckedStates[hook]) > (SystemTimers::GetTicksPerSecond() * 5))
|
||||
{
|
||||
hook++;
|
||||
}
|
||||
}
|
||||
else
|
||||
lastCheckedStates[priority] = CoreTiming::GetTicks();
|
||||
|
||||
return save;
|
||||
}
|
||||
|
||||
void Save(int slot)
|
||||
{
|
||||
if (waiting == STATE_NONE)
|
||||
{
|
||||
waiting = STATE_SAVE;
|
||||
waitingslot = slot;
|
||||
}
|
||||
SaveAs(MakeStateFilename(slot));
|
||||
}
|
||||
|
||||
void Load(int slot)
|
||||
{
|
||||
if (waiting == STATE_NONE)
|
||||
{
|
||||
waiting = STATE_LOAD;
|
||||
waitingslot = slot;
|
||||
}
|
||||
LoadAs(MakeStateFilename(slot));
|
||||
}
|
||||
|
||||
void Verify(int slot)
|
||||
|
@ -591,31 +524,6 @@ void LoadLastSaved()
|
|||
LoadAs(g_last_filename);
|
||||
}
|
||||
|
||||
void ScheduleBufferEvent(std::vector<u8>& buffer, int ev)
|
||||
{
|
||||
if (g_op_in_progress)
|
||||
return;
|
||||
g_op_in_progress = true;
|
||||
|
||||
g_current_buffer.swap(buffer);
|
||||
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev);
|
||||
}
|
||||
|
||||
void SaveToBuffer(std::vector<u8>& buffer)
|
||||
{
|
||||
ScheduleBufferEvent(buffer, ev_BufferSave);
|
||||
}
|
||||
|
||||
void LoadFromBuffer(std::vector<u8>& buffer)
|
||||
{
|
||||
ScheduleBufferEvent(buffer, ev_BufferLoad);
|
||||
}
|
||||
|
||||
void VerifyBuffer(std::vector<u8>& buffer)
|
||||
{
|
||||
ScheduleBufferEvent(buffer, ev_BufferVerify);
|
||||
}
|
||||
|
||||
void Flush()
|
||||
{
|
||||
// If already saving state, wait for it to finish
|
||||
|
@ -626,6 +534,7 @@ void Flush()
|
|||
// Load the last state before loading the state
|
||||
void UndoLoadState()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
|
||||
if (!g_undo_load_buffer.empty())
|
||||
LoadFromBuffer(g_undo_load_buffer);
|
||||
else
|
||||
|
|
|
@ -41,8 +41,6 @@ void Save(int slot);
|
|||
void Load(int slot);
|
||||
void Verify(int slot);
|
||||
|
||||
bool ProcessRequestedStates(int priority);
|
||||
|
||||
void SaveAs(const std::string &filename);
|
||||
void LoadAs(const std::string &filename);
|
||||
void VerifyAt(const std::string &filename);
|
||||
|
|
|
@ -1509,26 +1509,26 @@ void CFrame::OnSaveStateToFile(wxCommandEvent& WXUNUSED (event))
|
|||
|
||||
void CFrame::OnLoadLastState(wxCommandEvent& WXUNUSED (event))
|
||||
{
|
||||
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
||||
if (Core::IsRunningAndStarted())
|
||||
State::LoadLastSaved();
|
||||
}
|
||||
|
||||
void CFrame::OnUndoLoadState(wxCommandEvent& WXUNUSED (event))
|
||||
{
|
||||
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
||||
if (Core::IsRunningAndStarted())
|
||||
State::UndoLoadState();
|
||||
}
|
||||
|
||||
void CFrame::OnUndoSaveState(wxCommandEvent& WXUNUSED (event))
|
||||
{
|
||||
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
||||
if (Core::IsRunningAndStarted())
|
||||
State::UndoSaveState();
|
||||
}
|
||||
|
||||
|
||||
void CFrame::OnLoadState(wxCommandEvent& event)
|
||||
{
|
||||
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
||||
if (Core::IsRunningAndStarted())
|
||||
{
|
||||
int id = event.GetId();
|
||||
int slot = id - IDM_LOADSLOT1 + 1;
|
||||
|
@ -1538,7 +1538,7 @@ void CFrame::OnLoadState(wxCommandEvent& event)
|
|||
|
||||
void CFrame::OnSaveState(wxCommandEvent& event)
|
||||
{
|
||||
if (Core::GetState() != Core::CORE_UNINITIALIZED)
|
||||
if (Core::IsRunningAndStarted())
|
||||
{
|
||||
int id = event.GetId();
|
||||
int slot = id - IDM_SAVESLOT1 + 1;
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "ChunkFile.h"
|
||||
#include "Fifo.h"
|
||||
#include "HW/Memmap.h"
|
||||
#include "Core.h"
|
||||
|
||||
volatile bool g_bSkipCurrentFrame = false;
|
||||
extern u8* g_pVideoData;
|
||||
|
@ -34,22 +35,44 @@ namespace
|
|||
{
|
||||
static volatile bool GpuRunningState = false;
|
||||
static volatile bool EmuRunningState = false;
|
||||
static u8 *videoBuffer;
|
||||
static std::mutex m_csHWVidOccupied;
|
||||
// STATE_TO_SAVE
|
||||
static u8 *videoBuffer;
|
||||
static int size = 0;
|
||||
} // namespace
|
||||
|
||||
void Fifo_DoState(PointerWrap &p)
|
||||
{
|
||||
|
||||
p.DoArray(videoBuffer, FIFO_SIZE);
|
||||
p.Do(size);
|
||||
int pos = (int)(g_pVideoData - videoBuffer); // get offset
|
||||
p.Do(pos); // read or write offset (depends on the mode afaik)
|
||||
g_pVideoData = &videoBuffer[pos]; // overwrite g_pVideoData -> expected no change when load ss and change when save ss
|
||||
|
||||
p.Do(pos); // read or write offset (depending on the mode)
|
||||
if (p.GetMode() == PointerWrap::MODE_READ)
|
||||
{
|
||||
g_pVideoData = &videoBuffer[pos];
|
||||
g_bSkipCurrentFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Fifo_PauseAndLock(bool doLock, bool unpauseOnUnlock)
|
||||
{
|
||||
if (doLock)
|
||||
{
|
||||
EmulatorState(false);
|
||||
if (!Core::IsGPUThread())
|
||||
m_csHWVidOccupied.lock();
|
||||
_dbg_assert_(COMMON, !CommandProcessor::fifo.isGpuReadingData);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unpauseOnUnlock)
|
||||
EmulatorState(true);
|
||||
if (!Core::IsGPUThread())
|
||||
m_csHWVidOccupied.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Fifo_Init()
|
||||
{
|
||||
videoBuffer = (u8*)AllocateMemoryPages(FIFO_SIZE);
|
||||
|
@ -127,6 +150,7 @@ void ResetVideoBuffer()
|
|||
// Purpose: Keep the Core HW updated about the CPU-GPU distance
|
||||
void RunGpuLoop()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_csHWVidOccupied);
|
||||
GpuRunningState = true;
|
||||
SCPFifoStruct &fifo = CommandProcessor::fifo;
|
||||
|
||||
|
@ -178,12 +202,13 @@ void RunGpuLoop()
|
|||
Common::YieldCPU();
|
||||
else
|
||||
{
|
||||
// While the emu is paused, we still handle async request such as Savestates then sleep.
|
||||
// While the emu is paused, we still handle async requests then sleep.
|
||||
while (!EmuRunningState)
|
||||
{
|
||||
g_video_backend->PeekMessages();
|
||||
VideoFifo_CheckStateRequest();
|
||||
m_csHWVidOccupied.unlock();
|
||||
Common::SleepCurrentThread(1);
|
||||
m_csHWVidOccupied.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,9 @@ extern volatile bool g_bSkipCurrentFrame;
|
|||
|
||||
void Fifo_Init();
|
||||
void Fifo_Shutdown();
|
||||
|
||||
void Fifo_DoState(PointerWrap &f);
|
||||
void Fifo_PauseAndLock(bool doLock, bool unpauseOnUnlock);
|
||||
|
||||
void ReadDataFromFifo(u8* _uData, u32 len);
|
||||
|
||||
|
@ -45,6 +47,5 @@ void Fifo_SetRendering(bool bEnabled);
|
|||
|
||||
// Implemented by the Video Backend
|
||||
void VideoFifo_CheckAsyncRequest();
|
||||
void VideoFifo_CheckStateRequest();
|
||||
|
||||
#endif // _FIFO_H
|
||||
|
|
|
@ -169,8 +169,7 @@ u32 VideoBackendHardware::Video_AccessEFB(EFBAccessType type, u32 x, u32 y, u32
|
|||
return 0;
|
||||
}
|
||||
|
||||
static volatile u32 s_doStateRequested = false;
|
||||
|
||||
|
||||
void VideoBackendHardware::InitializeShared()
|
||||
{
|
||||
VideoCommon_Init();
|
||||
|
@ -183,52 +182,29 @@ void VideoBackendHardware::InitializeShared()
|
|||
s_AccessEFBResult = 0;
|
||||
}
|
||||
|
||||
static volatile struct
|
||||
{
|
||||
unsigned char **ptr;
|
||||
int mode;
|
||||
} s_doStateArgs;
|
||||
|
||||
// Depending on the threading mode (DC/SC) this can be called
|
||||
// from either the GPU thread or the CPU thread
|
||||
void VideoFifo_CheckStateRequest()
|
||||
{
|
||||
if (Common::AtomicLoadAcquire(s_doStateRequested))
|
||||
{
|
||||
// Clear all caches that touch RAM
|
||||
TextureCache::Invalidate(false);
|
||||
VertexLoaderManager::MarkAllDirty();
|
||||
|
||||
PointerWrap p(s_doStateArgs.ptr, s_doStateArgs.mode);
|
||||
VideoCommon_DoState(p);
|
||||
|
||||
// Refresh state.
|
||||
if (s_doStateArgs.mode == PointerWrap::MODE_READ)
|
||||
{
|
||||
BPReload();
|
||||
RecomputeCachedArraybases();
|
||||
}
|
||||
|
||||
Common::AtomicStoreRelease(s_doStateRequested, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Run from the CPU thread
|
||||
void VideoBackendHardware::DoState(PointerWrap& p)
|
||||
{
|
||||
s_doStateArgs.ptr = p.ptr;
|
||||
s_doStateArgs.mode = p.mode;
|
||||
Common::AtomicStoreRelease(s_doStateRequested, true);
|
||||
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread)
|
||||
// Clear all caches that touch RAM
|
||||
TextureCache::Invalidate(false);
|
||||
VertexLoaderManager::MarkAllDirty();
|
||||
|
||||
VideoCommon_DoState(p);
|
||||
|
||||
// Refresh state.
|
||||
if (p.GetMode() == PointerWrap::MODE_READ)
|
||||
{
|
||||
while (Common::AtomicLoadAcquire(s_doStateRequested) && !s_FifoShuttingDown)
|
||||
//Common::SleepCurrentThread(1);
|
||||
Common::YieldCPU();
|
||||
BPReload();
|
||||
RecomputeCachedArraybases();
|
||||
}
|
||||
else
|
||||
VideoFifo_CheckStateRequest();
|
||||
}
|
||||
|
||||
void VideoBackendHardware::PauseAndLock(bool doLock, bool unpauseOnUnlock)
|
||||
{
|
||||
Fifo_PauseAndLock(doLock, unpauseOnUnlock);
|
||||
}
|
||||
|
||||
|
||||
void VideoBackendHardware::RunLoop(bool enable)
|
||||
{
|
||||
VideoCommon_RunLoop(enable);
|
||||
|
|
|
@ -367,8 +367,6 @@ void UpdateFinishInterrupt(bool active)
|
|||
{
|
||||
ProcessorInterface::SetInterrupt(INT_CAUSE_PE_FINISH, active);
|
||||
interruptSetFinish = active;
|
||||
if (active)
|
||||
State::ProcessRequestedStates(0);
|
||||
}
|
||||
|
||||
// TODO(mb2): Refactor SetTokenINT_OnMainThread(u64 userdata, int cyclesLate).
|
||||
|
|
|
@ -45,6 +45,7 @@ namespace SW
|
|||
|
||||
static volatile bool fifoStateRun = false;
|
||||
static volatile bool emuRunningState = false;
|
||||
static std::mutex m_csSWVidOccupied;
|
||||
|
||||
|
||||
std::string VideoSoftware::GetName()
|
||||
|
@ -91,6 +92,24 @@ bool VideoSoftware::Initialize(void *&window_handle)
|
|||
|
||||
void VideoSoftware::DoState(PointerWrap&)
|
||||
{
|
||||
// NYI
|
||||
}
|
||||
|
||||
void VideoSoftware::PauseAndLock(bool doLock, bool unpauseOnUnlock)
|
||||
{
|
||||
if (doLock)
|
||||
{
|
||||
EmuStateChange(EMUSTATE_CHANGE_PAUSE);
|
||||
if (!Core::IsGPUThread())
|
||||
m_csSWVidOccupied.lock();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unpauseOnUnlock)
|
||||
EmuStateChange(EMUSTATE_CHANGE_PLAY);
|
||||
if (!Core::IsGPUThread())
|
||||
m_csSWVidOccupied.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSoftware::RunLoop(bool enable)
|
||||
|
@ -167,6 +186,7 @@ bool VideoSoftware::Video_Screenshot(const char *_szFilename)
|
|||
// -------------------------------
|
||||
void VideoSoftware::Video_EnterLoop()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(m_csSWVidOccupied);
|
||||
fifoStateRun = true;
|
||||
|
||||
while (fifoStateRun)
|
||||
|
@ -181,7 +201,9 @@ void VideoSoftware::Video_EnterLoop()
|
|||
while (!emuRunningState && fifoStateRun)
|
||||
{
|
||||
g_video_backend->PeekMessages();
|
||||
m_csSWVidOccupied.unlock();
|
||||
Common::SleepCurrentThread(1);
|
||||
m_csSWVidOccupied.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ class VideoSoftware : public VideoBackend
|
|||
|
||||
void EmuStateChange(EMUSTATE_CHANGE newState);
|
||||
|
||||
void DoState(PointerWrap &p);
|
||||
void RunLoop(bool enable);
|
||||
|
||||
void ShowConfig(void* parent);
|
||||
|
@ -48,6 +47,9 @@ class VideoSoftware : public VideoBackend
|
|||
|
||||
void UpdateFPSDisplay(const char*);
|
||||
unsigned int PeekMessages();
|
||||
|
||||
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
|
||||
void DoState(PointerWrap &p);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue