From a81631b58e3a77f8d088c574fc048f3fb37a7c51 Mon Sep 17 00:00:00 2001 From: nitsuja Date: Fri, 30 Dec 2011 20:16:12 -0800 Subject: [PATCH] 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. --- Source/Core/AudioCommon/Src/AudioCommon.cpp | 19 + Source/Core/AudioCommon/Src/AudioCommon.h | 1 + Source/Core/AudioCommon/Src/Mixer.cpp | 6 +- Source/Core/AudioCommon/Src/Mixer.h | 4 +- Source/Core/AudioCommon/Src/SoundStream.h | 1 + Source/Core/Common/Src/VideoBackendBase.h | 14 +- Source/Core/Core/Src/Core.cpp | 60 ++- Source/Core/Core/Src/Core.h | 10 +- Source/Core/Core/Src/DSPEmulator.h | 1 + Source/Core/Core/Src/HW/CPU.cpp | 31 +- Source/Core/Core/Src/HW/CPU.h | 8 + Source/Core/Core/Src/HW/DSPHLE/DSPHLE.cpp | 43 ++- Source/Core/Core/Src/HW/DSPHLE/DSPHLE.h | 2 + Source/Core/Core/Src/HW/DSPLLE/DSPLLE.cpp | 52 ++- Source/Core/Core/Src/HW/DSPLLE/DSPLLE.h | 3 + Source/Core/Core/Src/HW/EXI.cpp | 18 + Source/Core/Core/Src/HW/EXI.h | 2 + Source/Core/Core/Src/HW/EXI_Channel.cpp | 16 + Source/Core/Core/Src/HW/EXI_Channel.h | 2 + Source/Core/Core/Src/HW/EXI_Device.h | 2 + .../Core/Core/Src/HW/EXI_DeviceMemoryCard.cpp | 33 +- .../Core/Core/Src/HW/EXI_DeviceMemoryCard.h | 2 + Source/Core/Core/Src/HW/SI.cpp | 5 +- Source/Core/Core/Src/HW/VideoInterface.cpp | 3 - Source/Core/Core/Src/State.cpp | 363 +++++++----------- Source/Core/Core/Src/State.h | 2 - Source/Core/DolphinWX/Src/FrameTools.cpp | 10 +- Source/Core/VideoCommon/Src/Fifo.cpp | 39 +- Source/Core/VideoCommon/Src/Fifo.h | 3 +- Source/Core/VideoCommon/Src/MainBase.cpp | 58 +-- Source/Core/VideoCommon/Src/PixelEngine.cpp | 2 - .../Plugin_VideoSoftware/Src/SWmain.cpp | 22 ++ .../Plugin_VideoSoftware/Src/VideoBackend.h | 4 +- 33 files changed, 518 insertions(+), 323 deletions(-) diff --git a/Source/Core/AudioCommon/Src/AudioCommon.cpp b/Source/Core/AudioCommon/Src/AudioCommon.cpp index fa4d134860..b73757cad5 100644 --- a/Source/Core/AudioCommon/Src/AudioCommon.cpp +++ b/Source/Core/AudioCommon/Src/AudioCommon.cpp @@ -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(); + } + } + } } diff --git a/Source/Core/AudioCommon/Src/AudioCommon.h b/Source/Core/AudioCommon/Src/AudioCommon.h index d9fe1ab88a..e7a111b0a1 100644 --- a/Source/Core/AudioCommon/Src/AudioCommon.h +++ b/Source/Core/AudioCommon/Src/AudioCommon.h @@ -59,6 +59,7 @@ namespace AudioCommon void ShutdownSoundStream(); std::vector GetSoundBackends(); bool UseJIT(); + void PauseAndLock(bool doLock, bool unpauseOnUnlock=true); } #endif // _AUDIO_COMMON_H_ diff --git a/Source/Core/AudioCommon/Src/Mixer.cpp b/Source/Core/AudioCommon/Src/Mixer.cpp index 8340df8b59..b55fd56c97 100644 --- a/Source/Core/AudioCommon/Src/Mixer.cpp +++ b/Source/Core/AudioCommon/Src/Mixer.cpp @@ -37,7 +37,9 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples) if (!samples) return 0; - if (PowerPC::GetState() != 0) + std::lock_guard 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')) diff --git a/Source/Core/AudioCommon/Src/Mixer.h b/Source/Core/AudioCommon/Src/Mixer.h index f850b58add..da456d5bf7 100644 --- a/Source/Core/AudioCommon/Src/Mixer.h +++ b/Source/Core/AudioCommon/Src/Mixer.h @@ -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: }; diff --git a/Source/Core/AudioCommon/Src/SoundStream.h b/Source/Core/AudioCommon/Src/SoundStream.h index 4b7cba9116..bb53c0f74d 100644 --- a/Source/Core/AudioCommon/Src/SoundStream.h +++ b/Source/Core/AudioCommon/Src/SoundStream.h @@ -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; diff --git a/Source/Core/Common/Src/VideoBackendBase.h b/Source/Core/Common/Src/VideoBackendBase.h index 96f639832b..d09288ebc7 100644 --- a/Source/Core/Common/Src/VideoBackendBase.h +++ b/Source/Core/Common/Src/VideoBackendBase.h @@ -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 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(); }; diff --git a/Source/Core/Core/Src/Core.cpp b/Source/Core/Core/Src/Core.cpp index cffaef6c27..ec3d0e8f70 100644 --- a/Source/Core/Core/Src/Core.cpp +++ b/Source/Core/Core/Src/Core.cpp @@ -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); diff --git a/Source/Core/Core/Src/Core.h b/Source/Core/Core/Src/Core.h index 8bfccc13b5..448bd55bb6 100644 --- a/Source/Core/Core/Src/Core.h +++ b/Source/Core/Core/Src/Core.h @@ -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(); diff --git a/Source/Core/Core/Src/DSPEmulator.h b/Source/Core/Core/Src/DSPEmulator.h index 657185a13d..30de180cf3 100644 --- a/Source/Core/Core/Src/DSPEmulator.h +++ b/Source/Core/Core/Src/DSPEmulator.h @@ -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; diff --git a/Source/Core/Core/Src/HW/CPU.cpp b/Source/Core/Core/Src/HW/CPU.cpp index 0f461b0fc5..f760abcec6 100644 --- a/Source/Core/Core/Src/HW/CPU.cpp +++ b/Source/Core/Core/Src/HW/CPU.cpp @@ -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 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; +} diff --git a/Source/Core/Core/Src/HW/CPU.h b/Source/Core/Core/Src/HW/CPU.h index 0d35fa793f..188ef2ff0f 100644 --- a/Source/Core/Core/Src/HW/CPU.h +++ b/Source/Core/Core/Src/HW/CPU.h @@ -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 diff --git a/Source/Core/Core/Src/HW/DSPHLE/DSPHLE.cpp b/Source/Core/Core/Src/HW/DSPHLE/DSPHLE.cpp index 65362a19a0..66ee4bef84 100644 --- a/Source/Core/Core/Src/HW/DSPHLE/DSPHLE.cpp +++ b/Source/Core/Core/Src/HW/DSPHLE/DSPHLE.cpp @@ -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); +} diff --git a/Source/Core/Core/Src/HW/DSPHLE/DSPHLE.h b/Source/Core/Core/Src/HW/DSPHLE/DSPHLE.h index 7b5d6f1eb5..6ad20115b4 100644 --- a/Source/Core/Core/Src/HW/DSPHLE/DSPHLE.h +++ b/Source/Core/Core/Src/HW/DSPHLE/DSPHLE.h @@ -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; diff --git a/Source/Core/Core/Src/HW/DSPLLE/DSPLLE.cpp b/Source/Core/Core/Src/HW/DSPLLE/DSPLLE.cpp index c3b6a2d674..fbd568c136 100644 --- a/Source/Core/Core/Src/HW/DSPLLE/DSPLLE.cpp +++ b/Source/Core/Core/Src/HW/DSPLLE/DSPLLE.cpp @@ -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 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(); +} + diff --git a/Source/Core/Core/Src/HW/DSPLLE/DSPLLE.h b/Source/Core/Core/Src/HW/DSPLLE/DSPLLE.h index 5fff3d423b..456d038c96 100644 --- a/Source/Core/Core/Src/HW/DSPLLE/DSPLLE.h +++ b/Source/Core/Core/Src/HW/DSPLLE/DSPLLE.h @@ -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; diff --git a/Source/Core/Core/Src/HW/EXI.cpp b/Source/Core/Core/Src/HW/EXI.cpp index bfc5c28819..4153331ee2 100644 --- a/Source/Core/Core/Src/HW/EXI.cpp +++ b/Source/Core/Core/Src/HW/EXI.cpp @@ -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() { diff --git a/Source/Core/Core/Src/HW/EXI.h b/Source/Core/Core/Src/HW/EXI.h index 1b913b7da1..2c4c9ef6dd 100644 --- a/Source/Core/Core/Src/HW/EXI.h +++ b/Source/Core/Core/Src/HW/EXI.h @@ -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); diff --git a/Source/Core/Core/Src/HW/EXI_Channel.cpp b/Source/Core/Core/Src/HW/EXI_Channel.cpp index 91cb691546..382d83c1aa 100644 --- a/Source/Core/Core/Src/HW/EXI_Channel.cpp +++ b/Source/Core/Core/Src/HW/EXI_Channel.cpp @@ -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; +} diff --git a/Source/Core/Core/Src/HW/EXI_Channel.h b/Source/Core/Core/Src/HW/EXI_Channel.h index 21925f52e5..4012e56896 100644 --- a/Source/Core/Core/Src/HW/EXI_Channel.h +++ b/Source/Core/Core/Src/HW/EXI_Channel.h @@ -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; } diff --git a/Source/Core/Core/Src/HW/EXI_Device.h b/Source/Core/Core/Src/HW/EXI_Device.h index 8cb2f99a64..9ba03e3073 100644 --- a/Source/Core/Core/Src/HW/EXI_Device.h +++ b/Source/Core/Core/Src/HW/EXI_Device.h @@ -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() {} diff --git a/Source/Core/Core/Src/HW/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/Src/HW/EXI_DeviceMemoryCard.cpp index 14a155a0ff..fbd1691834 100644 --- a/Source/Core/Core/Src/HW/EXI_DeviceMemoryCard.cpp +++ b/Source/Core/Core/Src/HW/EXI_DeviceMemoryCard.cpp @@ -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; +} diff --git a/Source/Core/Core/Src/HW/EXI_DeviceMemoryCard.h b/Source/Core/Core/Src/HW/EXI_DeviceMemoryCard.h index 5da8c63183..fb2302b8df 100644 --- a/Source/Core/Core/Src/HW/EXI_DeviceMemoryCard.h +++ b/Source/Core/Core/Src/HW/EXI_DeviceMemoryCard.h @@ -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 diff --git a/Source/Core/Core/Src/HW/SI.cpp b/Source/Core/Core/Src/HW/SI.cpp index 7250a003b3..e4263a0e16 100644 --- a/Source/Core/Core/Src/HW/SI.cpp +++ b/Source/Core/Core/Src/HW/SI.cpp @@ -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; } diff --git a/Source/Core/Core/Src/HW/VideoInterface.cpp b/Source/Core/Core/Src/HW/VideoInterface.cpp index 677f30921f..cc2ce18ce0 100644 --- a/Source/Core/Core/Src/HW/VideoInterface.cpp +++ b/Source/Core/Core/Src/HW/VideoInterface.cpp @@ -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() diff --git a/Source/Core/Core/Src/State.cpp b/Source/Core/Core/Src/State.cpp index 3f85bbd79e..a5d6e5b063 100644 --- a/Source/Core/Core/Src/State.cpp +++ b/Source/Core/Core/Src/State.cpp @@ -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 g_undo_load_buffer; static std::vector 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& 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& buffer) { + bool wasUnpaused = Core::PauseAndLock(true); + u8* ptr = NULL; PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); DoState(p); const size_t buffer_size = reinterpret_cast(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& 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* save_arg) +struct CompressAndDumpState_args { - const u8* const buffer_data = &(*save_arg)[0]; - const size_t buffer_size = save_arg->size(); + std::vector* buffer_vector; + std::mutex* buffer_mutex; + std::string filename; +}; + +void CompressAndDumpState(CompressAndDumpState_args save_args) +{ + std::lock_guard 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* 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(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 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& ret_data) +void LoadFileStateData(const std::string& filename, std::vector& ret_data) { + Flush(); File::IOFile f(filename, "rb"); if (!f) { @@ -353,35 +365,54 @@ void LoadFileStateData(std::string& filename, std::vector& 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 buffer; - LoadFileStateData(g_current_filename, buffer); - - if (!buffer.empty()) { - u8 *ptr = &buffer[0]; - PointerWrap p(&ptr, PointerWrap::MODE_READ); - DoState(p); + std::lock_guard 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 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 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 tmp; - g_current_buffer.swap(tmp); + std::lock_guard lk(g_cs_current_buffer); + std::vector().swap(g_current_buffer); } { - std::vector tmp; - g_undo_load_buffer.swap(tmp); + std::lock_guard lk(g_cs_undo_load_buffer); + std::vector().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& 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& buffer) -{ - ScheduleBufferEvent(buffer, ev_BufferSave); -} - -void LoadFromBuffer(std::vector& buffer) -{ - ScheduleBufferEvent(buffer, ev_BufferLoad); -} - -void VerifyBuffer(std::vector& 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 lk(g_cs_undo_load_buffer); if (!g_undo_load_buffer.empty()) LoadFromBuffer(g_undo_load_buffer); else diff --git a/Source/Core/Core/Src/State.h b/Source/Core/Core/Src/State.h index 048ad726ee..0cc0fb502e 100644 --- a/Source/Core/Core/Src/State.h +++ b/Source/Core/Core/Src/State.h @@ -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); diff --git a/Source/Core/DolphinWX/Src/FrameTools.cpp b/Source/Core/DolphinWX/Src/FrameTools.cpp index a987ddebcd..dbe4dd65e4 100644 --- a/Source/Core/DolphinWX/Src/FrameTools.cpp +++ b/Source/Core/DolphinWX/Src/FrameTools.cpp @@ -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; diff --git a/Source/Core/VideoCommon/Src/Fifo.cpp b/Source/Core/VideoCommon/Src/Fifo.cpp index 2e60055c97..18f2d42450 100644 --- a/Source/Core/VideoCommon/Src/Fifo.cpp +++ b/Source/Core/VideoCommon/Src/Fifo.cpp @@ -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 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(); } } } diff --git a/Source/Core/VideoCommon/Src/Fifo.h b/Source/Core/VideoCommon/Src/Fifo.h index e73434a19f..75e7d782f5 100644 --- a/Source/Core/VideoCommon/Src/Fifo.h +++ b/Source/Core/VideoCommon/Src/Fifo.h @@ -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 diff --git a/Source/Core/VideoCommon/Src/MainBase.cpp b/Source/Core/VideoCommon/Src/MainBase.cpp index 0b1258a662..85fbc3d8e9 100644 --- a/Source/Core/VideoCommon/Src/MainBase.cpp +++ b/Source/Core/VideoCommon/Src/MainBase.cpp @@ -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); diff --git a/Source/Core/VideoCommon/Src/PixelEngine.cpp b/Source/Core/VideoCommon/Src/PixelEngine.cpp index 03a3a7547a..a488e52b42 100644 --- a/Source/Core/VideoCommon/Src/PixelEngine.cpp +++ b/Source/Core/VideoCommon/Src/PixelEngine.cpp @@ -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). diff --git a/Source/Plugins/Plugin_VideoSoftware/Src/SWmain.cpp b/Source/Plugins/Plugin_VideoSoftware/Src/SWmain.cpp index c591bd0957..3c6fdcca38 100644 --- a/Source/Plugins/Plugin_VideoSoftware/Src/SWmain.cpp +++ b/Source/Plugins/Plugin_VideoSoftware/Src/SWmain.cpp @@ -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 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(); } } } diff --git a/Source/Plugins/Plugin_VideoSoftware/Src/VideoBackend.h b/Source/Plugins/Plugin_VideoSoftware/Src/VideoBackend.h index 7c52e34291..c0309c95c1 100644 --- a/Source/Plugins/Plugin_VideoSoftware/Src/VideoBackend.h +++ b/Source/Plugins/Plugin_VideoSoftware/Src/VideoBackend.h @@ -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); }; }