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

the intent is to replace the haphazard scheduling and finger-crossing associated with saving/loading with the correct and minimal necessary wait for each thread to reach a known safe location before commencing the savestate operation, and for any already-paused components to not need to be resumed to do so.
This commit is contained in:
nitsuja 2011-12-30 20:16:12 -08:00 committed by skidau
parent 108f69eaa9
commit a81631b58e
33 changed files with 518 additions and 323 deletions

View File

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

View File

@ -59,6 +59,7 @@ namespace AudioCommon
void ShutdownSoundStream();
std::vector<std::string> GetSoundBackends();
bool UseJIT();
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
}
#endif // _AUDIO_COMMON_H_

View File

@ -37,7 +37,9 @@ unsigned int CMixer::Mix(short* samples, unsigned int numSamples)
if (!samples)
return 0;
if (PowerPC::GetState() != 0)
std::lock_guard<std::mutex> lk(m_csMixing);
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
{
// Silence
memset(samples, 0, numSamples * 4);
@ -164,7 +166,7 @@ void CMixer::PushSamples(const short *samples, unsigned int num_samples)
// The auto throttle function. This loop will put a ceiling on the CPU MHz.
while (num_samples + Common::AtomicLoad(m_numSamples) > MAX_SAMPLES)
{
if (*PowerPC::GetStatePtr() != 0)
if (*PowerPC::GetStatePtr() != PowerPC::CPU_RUNNING || soundStream->IsMuted())
break;
// Shortcut key for Throttle Skipping
if (Host_GetKeyState('\t'))

View File

@ -19,6 +19,7 @@
#define _MIXER_H_
#include "WaveFile.h"
#include "StdMutex.h"
// 16 bit Stereo
#define MAX_SAMPLES (1024 * 8)
@ -89,6 +90,7 @@ public:
}
}
std::mutex& MixerCritical() { return m_csMixing; }
protected:
unsigned int m_sampleRate;
@ -110,7 +112,7 @@ protected:
u32 m_indexR;
bool m_AIplaying;
std::mutex m_csMixing;
private:
};

View File

@ -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;

View File

@ -93,8 +93,6 @@ public:
virtual bool Initialize(void *&) = 0;
virtual void Shutdown() = 0;
virtual void DoState(PointerWrap &p) = 0;
virtual void RunLoop(bool enable) = 0;
virtual std::string GetName() = 0;
@ -131,6 +129,14 @@ public:
static void PopulateList();
static void ClearList();
static void ActivateBackend(const std::string& name);
// waits until is paused and fully idle, and acquires a lock on that state.
// or, if doLock is false, releases a lock on that state and optionally unpauses.
// calls must be balanced and non-recursive (once with doLock true, then once with doLock false).
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) = 0;
// the implementation needs not do synchronization logic, because calls to it are surrounded by PauseAndLock now
virtual void DoState(PointerWrap &p) = 0;
};
extern std::vector<VideoBackend*> g_available_video_backends;
@ -139,7 +145,6 @@ extern VideoBackend* g_video_backend;
// inherited by dx9/dx11/ogl backends
class VideoBackendHardware : public VideoBackend
{
void DoState(PointerWrap &p);
void RunLoop(bool enable);
bool Initialize(void *&) { InitializeShared(); return true; }
@ -169,6 +174,9 @@ class VideoBackendHardware : public VideoBackend
writeFn16 Video_PEWrite16();
writeFn32 Video_PEWrite32();
void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
void DoState(PointerWrap &p);
protected:
void InitializeShared();
};

View File

@ -48,6 +48,7 @@
#include "HW/GPFifo.h"
#include "HW/AudioInterface.h"
#include "HW/VideoInterface.h"
#include "HW/EXI.h"
#include "HW/SystemTimers.h"
#include "IPC_HLE/WII_IPC_HLE_Device_usb.h"
@ -58,6 +59,7 @@
#include "DSPEmulator.h"
#include "ConfigManager.h"
#include "VideoBackendBase.h"
#include "AudioCommon.h"
#include "OnScreenDisplay.h"
#ifdef _WIN32
#include "EmuWindow.h"
@ -95,13 +97,15 @@ void Stop();
bool g_bStopping = false;
bool g_bHwInit = false;
bool g_bStarted = false;
bool g_bRealWiimote = false;
void *g_pWindowHandle = NULL;
std::string g_stateFileName;
std::thread g_EmuThread;
static std::thread g_cpu_thread;
static bool g_requestRefreshInfo;
static bool g_requestRefreshInfo = false;
static int g_pauseAndLockDepth = 0;
SCoreStartupParameter g_CoreStartupParameter;
@ -160,16 +164,35 @@ bool IsRunning()
return (GetState() != CORE_UNINITIALIZED) || g_bHwInit;
}
bool IsRunningAndStarted()
{
return g_bStarted;
}
bool IsRunningInCurrentThread()
{
return IsRunning() && ((!g_cpu_thread.joinable()) || g_cpu_thread.get_id() == std::this_thread::get_id());
return IsRunning() && IsCPUThread();
}
bool IsCPUThread()
{
return ((!g_cpu_thread.joinable()) || g_cpu_thread.get_id() == std::this_thread::get_id());
return (g_cpu_thread.joinable() ? (g_cpu_thread.get_id() == std::this_thread::get_id()) : !g_bStarted);
}
bool IsGPUThread()
{
const SCoreStartupParameter& _CoreParameter =
SConfig::GetInstance().m_LocalCoreStartupParameter;
if (_CoreParameter.bCPUThread)
{
return (g_EmuThread.joinable() && (g_EmuThread.get_id() == std::this_thread::get_id()));
}
else
{
return IsCPUThread();
}
}
// This is called from the GUI thread. See the booting call schedule in
// BootManager.cpp
bool Init()
@ -300,9 +323,13 @@ void CpuThread()
if (!g_stateFileName.empty())
State::LoadAs(g_stateFileName);
g_bStarted = true;
// Enter CPU run loop. When we leave it - we are done.
CCPU::Run();
g_bStarted = false;
return;
}
@ -323,6 +350,8 @@ void FifoPlayerThread()
if (_CoreParameter.bLockThreads)
Common::SetCurrentThreadAffinity(1); // Force to first core
g_bStarted = true;
// Enter CPU run loop. When we leave it - we are done.
if (FifoPlayer::GetInstance().Open(_CoreParameter.m_strFilename))
{
@ -330,6 +359,8 @@ void FifoPlayerThread()
FifoPlayer::GetInstance().Close();
}
g_bStarted = false;
return;
}
@ -554,6 +585,26 @@ void RequestRefreshInfo()
g_requestRefreshInfo = true;
}
bool PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
// let's support recursive locking to simplify things on the caller's side,
// and let's do it at this outer level in case the individual systems don't support it.
if (doLock ? g_pauseAndLockDepth++ : --g_pauseAndLockDepth)
return true;
// first pause or unpause the cpu
bool wasUnpaused = CCPU::PauseAndLock(doLock, unpauseOnUnlock);
ExpansionInterface::PauseAndLock(doLock, unpauseOnUnlock);
// audio has to come after cpu, because cpu thread can wait for audio thread (m_throttle).
AudioCommon::PauseAndLock(doLock, unpauseOnUnlock);
DSP::GetDSPEmulator()->PauseAndLock(doLock, unpauseOnUnlock);
// video has to come after cpu, because cpu thread can wait for video thread (s_efbAccessRequested).
g_video_backend->PauseAndLock(doLock, unpauseOnUnlock);
return wasUnpaused;
}
// Apply Frame Limit and Display FPS info
// This should only be called from VI
void VideoThrottle()
@ -583,6 +634,9 @@ void VideoThrottle()
g_requestRefreshInfo = false;
SCoreStartupParameter& _CoreParameter = SConfig::GetInstance().m_LocalCoreStartupParameter;
if (ElapseTime == 0)
ElapseTime = 1;
u32 FPS = Common::AtomicLoad(DrawnFrame) * 1000 / ElapseTime;
u32 VPS = DrawnVideo * 1000 / ElapseTime;
u32 Speed = DrawnVideo * (100 * 1000) / (VideoInterface::TargetRefreshRate * ElapseTime);

View File

@ -54,9 +54,11 @@ void Stop();
std::string StopMessage(bool, std::string);
bool IsRunning();
bool IsRunningAndStarted(); // is running and the cpu loop has been entered
bool IsRunningInCurrentThread(); // this tells us whether we are running in the cpu thread.
bool IsCPUThread(); // this tells us whether we are the cpu thread.
bool IsGPUThread();
void SetState(EState _State);
EState GetState();
@ -87,6 +89,12 @@ bool ShouldSkipFrame(int skipped);
void VideoThrottle();
void RequestRefreshInfo();
// waits until all systems are paused and fully idle, and acquires a lock on that state.
// or, if doLock is false, releases a lock on that state and optionally unpauses.
// calls must be balanced (once with doLock true, then once with doLock false) but may be recursive.
// the return value of the first call should be passed in as the second argument of the second call.
bool PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
#ifdef RERECORDING
void FrameUpdate();

View File

@ -32,6 +32,7 @@ public:
virtual void Shutdown() = 0;
virtual void DoState(PointerWrap &p) = 0;
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) = 0;
virtual void DSP_WriteMailBoxHigh(bool _CPUMailbox, unsigned short) = 0;
virtual void DSP_WriteMailBoxLow(bool _CPUMailbox, unsigned short) = 0;

View File

@ -31,6 +31,7 @@ namespace
{
static Common::Event m_StepEvent;
static Common::Event *m_SyncEvent;
static std::mutex m_csCpuOccupied;
}
void CCPU::Init(int cpu_core)
@ -47,6 +48,7 @@ void CCPU::Shutdown()
void CCPU::Run()
{
std::lock_guard<std::mutex> lk(m_csCpuOccupied);
Host_UpdateDisasmDialog();
while (true)
@ -60,8 +62,12 @@ reswitch:
break;
case PowerPC::CPU_STEPPING:
m_StepEvent.Wait();
m_csCpuOccupied.unlock();
//1: wait for step command..
m_StepEvent.Wait();
m_csCpuOccupied.lock();
if (PowerPC::GetState() == PowerPC::CPU_POWERDOWN)
return;
if (PowerPC::GetState() != PowerPC::CPU_STEPPING)
@ -132,3 +138,26 @@ void CCPU::Break()
{
EnableStepping(true);
}
bool CCPU::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
bool wasUnpaused = !IsStepping();
if (doLock)
{
// we can't use EnableStepping, that would causes deadlocks with both audio and video
PowerPC::Pause();
if (!Core::IsCPUThread())
m_csCpuOccupied.lock();
}
else
{
if (unpauseOnUnlock)
{
PowerPC::Start();
m_StepEvent.Set();
}
if (!Core::IsCPUThread())
m_csCpuOccupied.unlock();
}
return wasUnpaused;
}

View File

@ -54,6 +54,14 @@ public:
// is stepping ?
static bool IsStepping();
// waits until is stepping and is ready for a command (paused and fully idle), and acquires a lock on that state.
// or, if doLock is false, releases a lock on that state and optionally re-disables stepping.
// calls must be balanced and non-recursive (once with doLock true, then once with doLock false).
// intended (but not required) to be called from another thread,
// e.g. when the GUI thread wants to make sure everything is paused so that it can create a savestate.
// the return value is whether the cpu was unpaused before the call.
static bool PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
};
#endif

View File

@ -130,7 +130,24 @@ void DSPHLE::SwapUCode(u32 _crc)
void DSPHLE::DoState(PointerWrap &p)
{
bool prevInitMixer = m_InitMixer;
p.Do(m_InitMixer);
if (prevInitMixer != m_InitMixer && p.GetMode() == PointerWrap::MODE_READ)
{
if (m_InitMixer)
{
InitMixer();
AudioCommon::PauseAndLock(true);
}
else
{
AudioCommon::PauseAndLock(false);
soundStream->Stop();
delete soundStream;
soundStream = NULL;
}
}
p.Do(m_DSPControl);
p.Do(m_dspState);
@ -230,6 +247,17 @@ void DSPHLE::DSP_WriteMailBoxLow(bool _CPUMailbox, unsigned short _Value)
}
}
void DSPHLE::InitMixer()
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
delete soundStream;
soundStream = AudioCommon::InitSoundStream(new HLEMixer(this, AISampleRate, DACSampleRate, ac_Config.iFrequency), m_hWnd);
if(!soundStream) PanicAlert("Error starting up sound stream");
// Mixer is initialized
m_InitMixer = true;
}
// Other DSP fuctions
u16 DSPHLE::DSP_WriteControlRegister(unsigned short _Value)
{
@ -238,14 +266,7 @@ u16 DSPHLE::DSP_WriteControlRegister(unsigned short _Value)
{
if (!Temp.DSPHalt && Temp.DSPInit)
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
soundStream = AudioCommon::InitSoundStream(
new HLEMixer(this, AISampleRate, DACSampleRate, ac_Config.iFrequency), m_hWnd);
if(!soundStream) PanicAlert("Error starting up sound stream");
// Mixer is initialized
m_InitMixer = true;
InitMixer();
}
}
@ -295,3 +316,9 @@ void DSPHLE::DSP_ClearAudioBuffer(bool mute)
if (soundStream)
soundStream->Clear(mute);
}
void DSPHLE::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
if (doLock || unpauseOnUnlock)
DSP_ClearAudioBuffer(doLock);
}

View File

@ -34,6 +34,7 @@ public:
virtual bool IsLLE() { return false; }
virtual void DoState(PointerWrap &p);
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
virtual void DSP_WriteMailBoxHigh(bool _CPUMailbox, unsigned short);
virtual void DSP_WriteMailBoxLow(bool _CPUMailbox, unsigned short);
@ -55,6 +56,7 @@ public:
private:
void SendMailToDSP(u32 _uMail);
void InitMixer();
// Declarations and definitions
void *m_hWnd;

View File

@ -79,6 +79,24 @@ void DSPLLE::DoState(PointerWrap &p)
p.DoArray(g_dsp.dram, DSP_DRAM_SIZE);
p.Do(cyclesLeft);
p.Do(m_cycle_count);
bool prevInitMixer = m_InitMixer;
p.Do(m_InitMixer);
if (prevInitMixer != m_InitMixer && p.GetMode() == PointerWrap::MODE_READ)
{
if (m_InitMixer)
{
InitMixer();
AudioCommon::PauseAndLock(true);
}
else
{
AudioCommon::PauseAndLock(false);
soundStream->Stop();
delete soundStream;
soundStream = NULL;
}
}
}
// Regular thread
@ -110,7 +128,9 @@ void DSPLLE::dsp_thread(DSPLLE *dsp_lle)
while (dsp_lle->m_bIsRunning)
{
int cycles = (int)dsp_lle->m_cycle_count;
if (cycles > 0) {
if (cycles > 0)
{
std::lock_guard<std::mutex> lk(dsp_lle->m_csDSPThreadActive);
if (dspjit)
{
DSPCore_RunCycles(cycles);
@ -179,6 +199,17 @@ void DSPLLE::Shutdown()
DSPCore_Shutdown();
}
void DSPLLE::InitMixer()
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
delete soundStream;
soundStream = AudioCommon::InitSoundStream(new CMixer(AISampleRate, DACSampleRate, ac_Config.iFrequency), m_hWnd);
if(!soundStream) PanicAlert("Error starting up sound stream");
// Mixer is initialized
m_InitMixer = true;
}
u16 DSPLLE::DSP_WriteControlRegister(u16 _uFlag)
{
UDSPControl Temp(_uFlag);
@ -186,12 +217,7 @@ u16 DSPLLE::DSP_WriteControlRegister(u16 _uFlag)
{
if (!Temp.DSPHalt)
{
unsigned int AISampleRate, DACSampleRate;
AudioInterface::Callback_GetSampleRate(AISampleRate, DACSampleRate);
soundStream = AudioCommon::InitSoundStream(new CMixer(AISampleRate, DACSampleRate, ac_Config.iFrequency), m_hWnd);
if(!soundStream) PanicAlert("Error starting up sound stream");
// Mixer is initialized
m_InitMixer = true;
InitMixer();
}
}
DSPInterpreter::WriteCR(_uFlag);
@ -334,3 +360,15 @@ void DSPLLE::DSP_ClearAudioBuffer(bool mute)
if (soundStream)
soundStream->Clear(mute);
}
void DSPLLE::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
if (doLock || unpauseOnUnlock)
DSP_ClearAudioBuffer(doLock);
if (doLock)
m_csDSPThreadActive.lock();
else
m_csDSPThreadActive.unlock();
}

View File

@ -32,6 +32,7 @@ public:
virtual bool IsLLE() { return true; }
virtual void DoState(PointerWrap &p);
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true);
virtual void DSP_WriteMailBoxHigh(bool _CPUMailbox, unsigned short);
virtual void DSP_WriteMailBoxLow(bool _CPUMailbox, unsigned short);
@ -46,8 +47,10 @@ public:
private:
static void dsp_thread(DSPLLE* lpParameter);
void InitMixer();
std::thread m_hDSPThread;
std::mutex m_csDSPThreadActive;
bool m_InitMixer;
void *m_hWnd;
bool m_bWii;

View File

@ -74,6 +74,13 @@ void OnAfterLoad()
g_Channels[c]->OnAfterLoad();
}
void PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
for (int c = 0; c < NUM_CHANNELS; ++c)
g_Channels[c]->PauseAndLock(doLock, unpauseOnUnlock);
}
void ChangeDeviceCallback(u64 userdata, int cyclesLate)
{
u8 channel = (u8)(userdata >> 32);
@ -91,6 +98,17 @@ void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 devi
CoreTiming::ScheduleEvent_Threadsafe(500000000, changeDevice, ((u64)channel << 32) | ((u64)device_type << 16) | device_num);
}
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex)
{
for (int i = 0; i < NUM_CHANNELS; ++i)
{
IEXIDevice* device = g_Channels[i]->FindDevice(device_type, customIndex);
if (device)
return device;
}
return NULL;
}
// Unused (?!)
void Update()
{

View File

@ -29,12 +29,14 @@ void Init();
void Shutdown();
void DoState(PointerWrap &p);
void OnAfterLoad();
void PauseAndLock(bool doLock, bool unpauseOnUnlock);
void Update();
void UpdateInterrupts();
void ChangeDeviceCallback(u64 userdata, int cyclesLate);
void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num);
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1);
void Read32(u32& _uReturnValue, const u32 _iAddress);
void Write32(const u32 _iValue, const u32 _iAddress);

View File

@ -315,3 +315,19 @@ void CEXIChannel::OnAfterLoad()
m_pDevices[d]->OnAfterLoad();
}
void CEXIChannel::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
for (int d = 0; d < NUM_DEVICES; ++d)
m_pDevices[d]->PauseAndLock(doLock, unpauseOnUnlock);
}
IEXIDevice* CEXIChannel::FindDevice(TEXIDevices device_type, int customIndex)
{
for (int d = 0; d < NUM_DEVICES; ++d)
{
IEXIDevice* device = m_pDevices[d]->FindDevice(device_type, customIndex);
if (device)
return device;
}
return NULL;
}

View File

@ -113,6 +113,7 @@ private:
public:
// get device
IEXIDevice* GetDevice(const u8 _CHIP_SELECT);
IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1);
CEXIChannel(u32 ChannelId);
~CEXIChannel();
@ -131,6 +132,7 @@ public:
void UpdateInterrupts();
void DoState(PointerWrap &p);
void OnAfterLoad();
void PauseAndLock(bool doLock, bool unpauseOnUnlock);
// This should only be used to transition interrupts from SP1 to Channel 2
void SetEXIINT(bool exiint) { m_Status.EXIINT = !!exiint; }

View File

@ -54,6 +54,8 @@ public:
virtual void SetCS(int) {}
virtual void DoState(PointerWrap&) {}
virtual void OnAfterLoad() {}
virtual void PauseAndLock(bool doLock, bool unpauseOnUnlock=true) {}
virtual IEXIDevice* FindDevice(TEXIDevices device_type, int customIndex=-1) { return (device_type == m_deviceType) ? this : NULL; }
// Update
virtual void Update() {}

View File

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

View File

@ -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

View File

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

View File

@ -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()

View File

@ -55,11 +55,7 @@ static unsigned char __LZO_MMODEL out[OUT_LEN];
static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);
static volatile bool g_op_in_progress = false;
static int ev_FileSave, ev_BufferSave, ev_FileLoad, ev_BufferLoad, ev_FileVerify, ev_BufferVerify;
static std::string g_current_filename, g_last_filename;
static std::string g_last_filename;
static CallbackFunc g_onAfterLoadCb = NULL;
@ -68,16 +64,14 @@ static std::vector<u8> g_undo_load_buffer;
static std::vector<u8> g_current_buffer;
static int g_loadDepth = 0;
static std::mutex g_cs_undo_load_buffer;
static std::mutex g_cs_current_buffer;
static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
static const u8 NUM_HOOKS = 2;
static u8 waiting;
static u8 waitingslot;
static u64 lastCheckedStates[NUM_HOOKS];
static u8 hook;
// Don't forget to increase this after doing changes on the savestate system
static const int STATE_VERSION = 7;
static const int STATE_VERSION = 8;
struct StateHeader
{
@ -121,8 +115,6 @@ void DoState(PointerWrap &p)
p.DoMarker("Version");
// Begin with video backend, so that it gets a chance to clear it's caches and writeback modified things to RAM
// Pause the video thread in multi-threaded mode
g_video_backend->RunLoop(false);
g_video_backend->DoState(p);
p.DoMarker("video_backend");
@ -138,74 +130,78 @@ void DoState(PointerWrap &p)
p.DoMarker("CoreTiming");
Movie::DoState(p);
p.DoMarker("Movie");
// Resume the video thread
g_video_backend->RunLoop(true);
}
void ResetCounters()
void LoadFromBuffer(std::vector<u8>& buffer)
{
for (int i = 0; i < NUM_HOOKS; ++i)
lastCheckedStates[i] = CoreTiming::GetTicks();
}
bool wasUnpaused = Core::PauseAndLock(true);
void LoadBufferStateCallback(u64 userdata, int cyclesLate)
{
u8* ptr = &g_current_buffer[0];
u8* ptr = &buffer[0];
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(p);
Core::DisplayMessage("Loaded state", 2000);
g_op_in_progress = false;
Core::PauseAndLock(false, wasUnpaused);
}
void SaveBufferStateCallback(u64 userdata, int cyclesLate)
void SaveToBuffer(std::vector<u8>& buffer)
{
bool wasUnpaused = Core::PauseAndLock(true);
u8* ptr = NULL;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
DoState(p);
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
g_current_buffer.resize(buffer_size);
ptr = &g_current_buffer[0];
buffer.resize(buffer_size);
ptr = &buffer[0];
p.SetMode(PointerWrap::MODE_WRITE);
DoState(p);
g_op_in_progress = false;
Core::PauseAndLock(false, wasUnpaused);
}
void VerifyBufferStateCallback(u64 userdata, int cyclesLate)
void VerifyBuffer(std::vector<u8>& buffer)
{
u8* ptr = &g_current_buffer[0];
bool wasUnpaused = Core::PauseAndLock(true);
u8* ptr = &buffer[0];
PointerWrap p(&ptr, PointerWrap::MODE_VERIFY);
DoState(p);
Core::DisplayMessage("Verified state", 2000);
g_op_in_progress = false;
Core::PauseAndLock(false, wasUnpaused);
}
void CompressAndDumpState(const std::vector<u8>* save_arg)
struct CompressAndDumpState_args
{
const u8* const buffer_data = &(*save_arg)[0];
const size_t buffer_size = save_arg->size();
std::vector<u8>* buffer_vector;
std::mutex* buffer_mutex;
std::string filename;
};
void CompressAndDumpState(CompressAndDumpState_args save_args)
{
std::lock_guard<std::mutex> lk(*save_args.buffer_mutex);
g_compressAndDumpStateSyncEvent.Set();
const u8* const buffer_data = &(*(save_args.buffer_vector))[0];
const size_t buffer_size = (save_args.buffer_vector)->size();
std::string& filename = save_args.filename;
// For easy debugging
Common::SetCurrentThreadName("SaveState thread");
// Moving to last overwritten save-state
if (File::Exists(g_current_filename))
if (File::Exists(filename))
{
if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
File::Delete((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"));
if (!File::Rename(g_current_filename, File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
if (!File::Rename(filename, File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
Core::DisplayMessage("Failed to move previous state to state undo backup", 1000);
}
File::IOFile f(g_current_filename, "wb");
File::IOFile f(filename, "wb");
if (!f)
{
Core::DisplayMessage("Could not save state", 2000);
@ -251,17 +247,13 @@ void CompressAndDumpState(const std::vector<u8>* save_arg)
}
Core::DisplayMessage(StringFromFormat("Saved State to %s",
g_current_filename.c_str()).c_str(), 2000);
g_op_in_progress = false;
filename.c_str()).c_str(), 2000);
}
void SaveFileStateCallback(u64 userdata, int cyclesLate)
void SaveAs(const std::string& filename)
{
// Pause the core while we save the state
CCPU::EnableStepping(true);
Flush();
bool wasUnpaused = Core::PauseAndLock(true);
// Measure the size of the buffer.
u8 *ptr = NULL;
@ -270,26 +262,46 @@ void SaveFileStateCallback(u64 userdata, int cyclesLate)
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
// Then actually do the write.
g_current_buffer.resize(buffer_size);
ptr = &g_current_buffer[0];
p.SetMode(PointerWrap::MODE_WRITE);
DoState(p);
if ((Movie::IsRecordingInput() || Movie::IsPlayingInput()) && !Movie::IsJustStartingRecordingInputFromSaveState())
Movie::SaveRecording((g_current_filename + ".dtm").c_str());
else if (!Movie::IsRecordingInput() && !Movie::IsPlayingInput())
File::Delete(g_current_filename + ".dtm");
{
std::lock_guard<std::mutex> lk(g_cs_current_buffer);
g_current_buffer.resize(buffer_size);
ptr = &g_current_buffer[0];
p.SetMode(PointerWrap::MODE_WRITE);
DoState(p);
}
Core::DisplayMessage("Saving State...", 1000);
if (p.GetMode() == PointerWrap::MODE_WRITE)
{
Core::DisplayMessage("Saving State...", 1000);
if ((Movie::IsRecordingInput() || Movie::IsPlayingInput()) && !Movie::IsJustStartingRecordingInputFromSaveState())
Movie::SaveRecording((filename + ".dtm").c_str());
else if (!Movie::IsRecordingInput() && !Movie::IsPlayingInput())
File::Delete(filename + ".dtm");
g_save_thread = std::thread(CompressAndDumpState, &g_current_buffer);
CompressAndDumpState_args save_args;
save_args.buffer_vector = &g_current_buffer;
save_args.buffer_mutex = &g_cs_current_buffer;
save_args.filename = filename;
Flush();
g_save_thread = std::thread(CompressAndDumpState, save_args);
g_compressAndDumpStateSyncEvent.Wait();
g_last_filename = filename;
}
else
{
// someone aborted the save by changing the mode?
Core::DisplayMessage("Unable to Save : Internal DoState Error", 4000);
}
// Resume the core and disable stepping
CCPU::EnableStepping(false);
Core::PauseAndLock(false, wasUnpaused);
}
void LoadFileStateData(std::string& filename, std::vector<u8>& ret_data)
void LoadFileStateData(const std::string& filename, std::vector<u8>& ret_data)
{
Flush();
File::IOFile f(filename, "rb");
if (!f)
{
@ -353,35 +365,54 @@ void LoadFileStateData(std::string& filename, std::vector<u8>& ret_data)
ret_data.swap(buffer);
}
void LoadFileStateCallback(u64 userdata, int cyclesLate)
void LoadAs(const std::string& filename)
{
// Stop the core while we load the state
CCPU::EnableStepping(true);
bool wasUnpaused = Core::PauseAndLock(true);
#if defined _DEBUG && defined _WIN32
// we use _CRTDBG_DELAY_FREE_MEM_DF (as a speed hack?),
// but it was causing us to leak gigantic amounts of memory here,
// enough that only a few savestates could be loaded before crashing,
// so let's disable it temporarily.
int tmpflag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
if (g_loadDepth == 0)
_CrtSetDbgFlag(tmpflag & ~_CRTDBG_DELAY_FREE_MEM_DF);
#endif
g_loadDepth++;
Flush();
// Save temp buffer for undo load state
// TODO: this should be controlled by a user option,
// because it slows down every savestate load to provide an often-unused feature.
SaveBufferStateCallback(userdata, cyclesLate);
g_undo_load_buffer.swap(g_current_buffer);
std::vector<u8> buffer;
LoadFileStateData(g_current_filename, buffer);
if (!buffer.empty())
{
u8 *ptr = &buffer[0];
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(p);
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
SaveToBuffer(g_undo_load_buffer);
}
if (p.GetMode() == PointerWrap::MODE_READ)
bool loaded = false;
bool loadedSuccessfully = false;
// brackets here are so buffer gets freed ASAP
{
std::vector<u8> buffer;
LoadFileStateData(filename, buffer);
if (!buffer.empty())
{
Core::DisplayMessage(StringFromFormat("Loaded state from %s", g_current_filename.c_str()).c_str(), 2000);
if (File::Exists(g_current_filename + ".dtm"))
Movie::LoadInput((g_current_filename + ".dtm").c_str());
u8 *ptr = &buffer[0];
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(p);
loaded = true;
loadedSuccessfully = (p.GetMode() == PointerWrap::MODE_READ);
}
}
if (loaded)
{
if (loadedSuccessfully)
{
Core::DisplayMessage(StringFromFormat("Loaded state from %s", filename.c_str()).c_str(), 2000);
if (File::Exists(filename + ".dtm"))
Movie::LoadInput((filename + ".dtm").c_str());
else if (!Movie::IsJustStartingRecordingInputFromSaveState())
Movie::EndPlayInput(false);
}
@ -390,25 +421,27 @@ void LoadFileStateCallback(u64 userdata, int cyclesLate)
// failed to load
Core::DisplayMessage("Unable to Load : Can't load state from other revisions !", 4000);
// since we're probably in an inconsistent state now (and might crash or whatever), undo.
// since we could be in an inconsistent state now (and might crash or whatever), undo.
if (g_loadDepth < 2)
UndoLoadState();
}
}
ResetCounters();
HW::OnAfterLoad();
g_op_in_progress = false;
if (g_onAfterLoadCb)
g_onAfterLoadCb();
g_loadDepth--;
#if defined _DEBUG && defined _WIN32
// restore _CRTDBG_DELAY_FREE_MEM_DF
if (g_loadDepth == 0)
_CrtSetDbgFlag(tmpflag);
#endif
// resume dat core
CCPU::EnableStepping(false);
Core::PauseAndLock(false, wasUnpaused);
}
void SetOnAfterLoadCallback(CallbackFunc callback)
@ -416,12 +449,12 @@ void SetOnAfterLoadCallback(CallbackFunc callback)
g_onAfterLoadCb = callback;
}
void VerifyFileStateCallback(u64 userdata, int cyclesLate)
void VerifyAt(const std::string& filename)
{
Flush();
bool wasUnpaused = Core::PauseAndLock(true);
std::vector<u8> buffer;
LoadFileStateData(g_current_filename, buffer);
LoadFileStateData(filename, buffer);
if (!buffer.empty())
{
@ -430,29 +463,17 @@ void VerifyFileStateCallback(u64 userdata, int cyclesLate)
DoState(p);
if (p.GetMode() == PointerWrap::MODE_VERIFY)
Core::DisplayMessage(StringFromFormat("Verified state at %s", g_current_filename.c_str()).c_str(), 2000);
Core::DisplayMessage(StringFromFormat("Verified state at %s", filename.c_str()).c_str(), 2000);
else
Core::DisplayMessage("Unable to Verify : Can't verify state from other revisions !", 4000);
}
g_op_in_progress = false;
Core::PauseAndLock(false, wasUnpaused);
}
void Init()
{
ev_FileLoad = CoreTiming::RegisterEvent("LoadState", &LoadFileStateCallback);
ev_FileSave = CoreTiming::RegisterEvent("SaveState", &SaveFileStateCallback);
ev_FileVerify = CoreTiming::RegisterEvent("VerifyState", &VerifyFileStateCallback);
ev_BufferLoad = CoreTiming::RegisterEvent("LoadBufferState", &LoadBufferStateCallback);
ev_BufferSave = CoreTiming::RegisterEvent("SaveBufferState", &SaveBufferStateCallback);
ev_BufferVerify = CoreTiming::RegisterEvent("VerifyBufferState", &VerifyBufferStateCallback);
waiting = STATE_NONE;
waitingslot = 0;
hook = 0;
ResetCounters();
if (lzo_init() != LZO_E_OK)
PanicAlertT("Internal LZO Error - lzo_init() failed");
}
@ -462,15 +483,15 @@ void Shutdown()
Flush();
// swapping with an empty vector, rather than clear()ing
// this gives a better guarantee to free the allocated memory right NOW
// this gives a better guarantee to free the allocated memory right NOW (as opposed to, actually, never)
{
std::vector<u8> tmp;
g_current_buffer.swap(tmp);
std::lock_guard<std::mutex> lk(g_cs_current_buffer);
std::vector<u8>().swap(g_current_buffer);
}
{
std::vector<u8> tmp;
g_undo_load_buffer.swap(tmp);
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
std::vector<u8>().swap(g_undo_load_buffer);
}
}
@ -480,102 +501,14 @@ static std::string MakeStateFilename(int number)
SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), number);
}
void ScheduleFileEvent(const std::string &filename, int ev, bool immediate)
{
if (g_op_in_progress)
Flush();
if (g_op_in_progress)
return;
g_op_in_progress = true;
g_current_filename = filename;
if (immediate)
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev);
else
CoreTiming::ScheduleEvent_Threadsafe(0, ev);
}
void SaveAs(const std::string &filename, bool immediate)
{
g_last_filename = filename;
ScheduleFileEvent(filename, ev_FileSave, immediate);
}
void SaveAs(const std::string &filename)
{
SaveAs(filename, true);
}
void LoadAs(const std::string &filename, bool immediate)
{
ScheduleFileEvent(filename, ev_FileLoad, immediate);
}
void LoadAs(const std::string &filename)
{
LoadAs(filename, true);
}
void VerifyAt(const std::string &filename)
{
ScheduleFileEvent(filename, ev_FileVerify, true);
}
bool ProcessRequestedStates(int priority)
{
bool save = true;
if (hook == priority)
{
if (waiting == STATE_SAVE)
{
SaveAs(MakeStateFilename(waitingslot), false);
waitingslot = 0;
waiting = STATE_NONE;
}
else if (waiting == STATE_LOAD)
{
LoadAs(MakeStateFilename(waitingslot), false);
waitingslot = 0;
waiting = STATE_NONE;
save = false;
}
}
// Change hooks if the new hook gets called frequently (at least once a frame) and if the old
// hook has not been called for the last 5 seconds
if ((CoreTiming::GetTicks() - lastCheckedStates[priority]) < (VideoInterface::GetTicksPerFrame()))
{
lastCheckedStates[priority] = CoreTiming::GetTicks();
if (hook < NUM_HOOKS && priority >= (hook + 1) &&
(lastCheckedStates[priority] - lastCheckedStates[hook]) > (SystemTimers::GetTicksPerSecond() * 5))
{
hook++;
}
}
else
lastCheckedStates[priority] = CoreTiming::GetTicks();
return save;
}
void Save(int slot)
{
if (waiting == STATE_NONE)
{
waiting = STATE_SAVE;
waitingslot = slot;
}
SaveAs(MakeStateFilename(slot));
}
void Load(int slot)
{
if (waiting == STATE_NONE)
{
waiting = STATE_LOAD;
waitingslot = slot;
}
LoadAs(MakeStateFilename(slot));
}
void Verify(int slot)
@ -591,31 +524,6 @@ void LoadLastSaved()
LoadAs(g_last_filename);
}
void ScheduleBufferEvent(std::vector<u8>& buffer, int ev)
{
if (g_op_in_progress)
return;
g_op_in_progress = true;
g_current_buffer.swap(buffer);
CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev);
}
void SaveToBuffer(std::vector<u8>& buffer)
{
ScheduleBufferEvent(buffer, ev_BufferSave);
}
void LoadFromBuffer(std::vector<u8>& buffer)
{
ScheduleBufferEvent(buffer, ev_BufferLoad);
}
void VerifyBuffer(std::vector<u8>& buffer)
{
ScheduleBufferEvent(buffer, ev_BufferVerify);
}
void Flush()
{
// If already saving state, wait for it to finish
@ -626,6 +534,7 @@ void Flush()
// Load the last state before loading the state
void UndoLoadState()
{
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
if (!g_undo_load_buffer.empty())
LoadFromBuffer(g_undo_load_buffer);
else

View File

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

View File

@ -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;

View File

@ -26,6 +26,7 @@
#include "ChunkFile.h"
#include "Fifo.h"
#include "HW/Memmap.h"
#include "Core.h"
volatile bool g_bSkipCurrentFrame = false;
extern u8* g_pVideoData;
@ -34,22 +35,44 @@ namespace
{
static volatile bool GpuRunningState = false;
static volatile bool EmuRunningState = false;
static u8 *videoBuffer;
static std::mutex m_csHWVidOccupied;
// STATE_TO_SAVE
static u8 *videoBuffer;
static int size = 0;
} // namespace
void Fifo_DoState(PointerWrap &p)
{
p.DoArray(videoBuffer, FIFO_SIZE);
p.Do(size);
int pos = (int)(g_pVideoData - videoBuffer); // get offset
p.Do(pos); // read or write offset (depends on the mode afaik)
g_pVideoData = &videoBuffer[pos]; // overwrite g_pVideoData -> expected no change when load ss and change when save ss
p.Do(pos); // read or write offset (depending on the mode)
if (p.GetMode() == PointerWrap::MODE_READ)
{
g_pVideoData = &videoBuffer[pos];
g_bSkipCurrentFrame = false;
}
}
void Fifo_PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
if (doLock)
{
EmulatorState(false);
if (!Core::IsGPUThread())
m_csHWVidOccupied.lock();
_dbg_assert_(COMMON, !CommandProcessor::fifo.isGpuReadingData);
}
else
{
if (unpauseOnUnlock)
EmulatorState(true);
if (!Core::IsGPUThread())
m_csHWVidOccupied.unlock();
}
}
void Fifo_Init()
{
videoBuffer = (u8*)AllocateMemoryPages(FIFO_SIZE);
@ -127,6 +150,7 @@ void ResetVideoBuffer()
// Purpose: Keep the Core HW updated about the CPU-GPU distance
void RunGpuLoop()
{
std::lock_guard<std::mutex> lk(m_csHWVidOccupied);
GpuRunningState = true;
SCPFifoStruct &fifo = CommandProcessor::fifo;
@ -178,12 +202,13 @@ void RunGpuLoop()
Common::YieldCPU();
else
{
// While the emu is paused, we still handle async request such as Savestates then sleep.
// While the emu is paused, we still handle async requests then sleep.
while (!EmuRunningState)
{
g_video_backend->PeekMessages();
VideoFifo_CheckStateRequest();
m_csHWVidOccupied.unlock();
Common::SleepCurrentThread(1);
m_csHWVidOccupied.lock();
}
}
}

View File

@ -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

View File

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

View File

@ -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).

View File

@ -45,6 +45,7 @@ namespace SW
static volatile bool fifoStateRun = false;
static volatile bool emuRunningState = false;
static std::mutex m_csSWVidOccupied;
std::string VideoSoftware::GetName()
@ -91,6 +92,24 @@ bool VideoSoftware::Initialize(void *&window_handle)
void VideoSoftware::DoState(PointerWrap&)
{
// NYI
}
void VideoSoftware::PauseAndLock(bool doLock, bool unpauseOnUnlock)
{
if (doLock)
{
EmuStateChange(EMUSTATE_CHANGE_PAUSE);
if (!Core::IsGPUThread())
m_csSWVidOccupied.lock();
}
else
{
if (unpauseOnUnlock)
EmuStateChange(EMUSTATE_CHANGE_PLAY);
if (!Core::IsGPUThread())
m_csSWVidOccupied.unlock();
}
}
void VideoSoftware::RunLoop(bool enable)
@ -167,6 +186,7 @@ bool VideoSoftware::Video_Screenshot(const char *_szFilename)
// -------------------------------
void VideoSoftware::Video_EnterLoop()
{
std::lock_guard<std::mutex> lk(m_csSWVidOccupied);
fifoStateRun = true;
while (fifoStateRun)
@ -181,7 +201,9 @@ void VideoSoftware::Video_EnterLoop()
while (!emuRunningState && fifoStateRun)
{
g_video_backend->PeekMessages();
m_csSWVidOccupied.unlock();
Common::SleepCurrentThread(1);
m_csSWVidOccupied.lock();
}
}
}

View File

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