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); }; }