mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-12-16 04:09:39 +00:00
Merge pull request #14035 from Dentomologist/pauseandlock_refactoring
PauseAndLock Refactoring
This commit is contained in:
commit
91fd53a98c
@ -779,43 +779,46 @@ void SaveScreenShot(std::string_view name)
|
||||
g_frame_dumper->SaveScreenshot(fmt::format("{}{}.png", GenerateScreenshotFolderPath(), name));
|
||||
}
|
||||
|
||||
static bool PauseAndLock(Core::System& system, bool do_lock, bool unpause_on_unlock)
|
||||
static bool PauseAndLock(Core::System& system)
|
||||
{
|
||||
// WARNING: PauseAndLock is not fully threadsafe so is only valid on the Host Thread
|
||||
|
||||
if (!IsRunning(system))
|
||||
return true;
|
||||
|
||||
bool was_unpaused = true;
|
||||
if (do_lock)
|
||||
{
|
||||
// first pause the CPU
|
||||
// This acquires a wrapper mutex and converts the current thread into
|
||||
// First pause the CPU. This acquires a wrapper mutex and converts the current thread into
|
||||
// a temporary replacement CPU Thread.
|
||||
was_unpaused = system.GetCPU().PauseAndLock(true);
|
||||
}
|
||||
const bool was_unpaused = system.GetCPU().PauseAndLock();
|
||||
|
||||
// audio has to come after CPU, because CPU thread can wait for audio thread (m_throttle).
|
||||
system.GetDSP().GetDSPEmulator()->PauseAndLock(do_lock);
|
||||
system.GetDSP().GetDSPEmulator()->PauseAndLock();
|
||||
|
||||
// video has to come after CPU, because CPU thread can wait for video thread
|
||||
// (s_efbAccessRequested).
|
||||
system.GetFifo().PauseAndLock(do_lock, false);
|
||||
system.GetFifo().PauseAndLock();
|
||||
|
||||
ResetRumble();
|
||||
|
||||
// CPU is unlocked last because CPU::PauseAndLock contains the synchronization
|
||||
// mechanism that prevents CPU::Break from racing.
|
||||
if (!do_lock)
|
||||
{
|
||||
// The CPU is responsible for managing the Audio and FIFO state so we use its
|
||||
// mechanism to unpause them. If we unpaused the systems above when releasing
|
||||
// the locks then they could call CPU::Break which would require detecting it
|
||||
// and re-pausing with CPU::SetStepping.
|
||||
was_unpaused = system.GetCPU().PauseAndLock(false, unpause_on_unlock, true);
|
||||
return was_unpaused;
|
||||
}
|
||||
|
||||
return was_unpaused;
|
||||
static void RestoreStateAndUnlock(Core::System& system, const bool unpause_on_unlock)
|
||||
{
|
||||
// WARNING: RestoreStateAndUnlock is not fully threadsafe so is only valid on the Host Thread
|
||||
|
||||
if (!IsRunning(system))
|
||||
return;
|
||||
|
||||
system.GetDSP().GetDSPEmulator()->UnpauseAndUnlock();
|
||||
ResetRumble();
|
||||
|
||||
// CPU is unlocked last because CPU::RestoreStateAndUnlock contains the synchronization mechanism
|
||||
// that prevents CPU::Break from racing.
|
||||
//
|
||||
// The CPU is responsible for managing the Audio and FIFO state so we use its mechanism to unpause
|
||||
// them. If we unpaused the systems above when releasing the locks then they could call CPU::Break
|
||||
// which would require detecting it and re-pausing with CPU::SetStepping.
|
||||
system.GetCPU().RestoreStateAndUnlock(unpause_on_unlock);
|
||||
}
|
||||
|
||||
void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction<void()> function,
|
||||
@ -829,7 +832,7 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction<void()> funct
|
||||
}
|
||||
|
||||
// Pause the CPU (set it to stepping mode).
|
||||
const bool was_running = PauseAndLock(system, true, true);
|
||||
const bool was_running = PauseAndLock(system);
|
||||
|
||||
// Queue the job function.
|
||||
if (wait_for_completion)
|
||||
@ -847,7 +850,7 @@ void RunOnCPUThread(Core::System& system, Common::MoveOnlyFunction<void()> funct
|
||||
}
|
||||
|
||||
// Release the CPU thread, and let it execute the callback.
|
||||
PauseAndLock(system, false, was_running);
|
||||
RestoreStateAndUnlock(system, was_running);
|
||||
|
||||
// If we're waiting for completion, block until the event fires.
|
||||
if (wait_for_completion)
|
||||
@ -1064,13 +1067,13 @@ CPUThreadGuard::CPUThreadGuard(Core::System& system)
|
||||
: m_system(system), m_was_cpu_thread(IsCPUThread())
|
||||
{
|
||||
if (!m_was_cpu_thread)
|
||||
m_was_unpaused = PauseAndLock(system, true, true);
|
||||
m_was_unpaused = PauseAndLock(system);
|
||||
}
|
||||
|
||||
CPUThreadGuard::~CPUThreadGuard()
|
||||
{
|
||||
if (!m_was_cpu_thread)
|
||||
PauseAndLock(m_system, false, m_was_unpaused);
|
||||
RestoreStateAndUnlock(m_system, m_was_unpaused);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@ -22,7 +22,8 @@ public:
|
||||
virtual void Shutdown() = 0;
|
||||
|
||||
virtual void DoState(PointerWrap& p) = 0;
|
||||
virtual void PauseAndLock(bool do_lock) = 0;
|
||||
virtual void PauseAndLock() = 0;
|
||||
virtual void UnpauseAndUnlock() = 0;
|
||||
|
||||
virtual void DSP_WriteMailBoxHigh(bool cpu_mailbox, u16 value) = 0;
|
||||
virtual void DSP_WriteMailBoxLow(bool cpu_mailbox, u16 value) = 0;
|
||||
|
||||
@ -347,20 +347,14 @@ void CPUManager::Continue()
|
||||
Core::NotifyStateChanged(Core::State::Running);
|
||||
}
|
||||
|
||||
bool CPUManager::PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
|
||||
{
|
||||
// NOTE: This is protected by m_stepping_lock.
|
||||
static bool s_have_fake_cpu_thread = false;
|
||||
bool was_unpaused = false;
|
||||
|
||||
if (do_lock)
|
||||
bool CPUManager::PauseAndLock()
|
||||
{
|
||||
m_stepping_lock.lock();
|
||||
|
||||
std::unique_lock state_lock(m_state_change_lock);
|
||||
m_state_paused_and_locked = true;
|
||||
|
||||
was_unpaused = m_state == State::Running;
|
||||
const bool was_unpaused = m_state == State::Running;
|
||||
SetStateLocked(State::Stepping);
|
||||
|
||||
while (m_state_cpu_thread_active)
|
||||
@ -368,24 +362,25 @@ bool CPUManager::PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control
|
||||
m_state_cpu_idle_cvar.wait(state_lock);
|
||||
}
|
||||
|
||||
if (control_adjacent)
|
||||
RunAdjacentSystems(false);
|
||||
state_lock.unlock();
|
||||
|
||||
// NOTE: It would make more sense for Core::DeclareAsCPUThread() to keep a
|
||||
// depth counter instead of being a boolean.
|
||||
if (!Core::IsCPUThread())
|
||||
{
|
||||
s_have_fake_cpu_thread = true;
|
||||
m_have_fake_cpu_thread = true;
|
||||
Core::DeclareAsCPUThread();
|
||||
}
|
||||
|
||||
return was_unpaused;
|
||||
}
|
||||
else
|
||||
|
||||
void CPUManager::RestoreStateAndUnlock(const bool unpause_on_unlock)
|
||||
{
|
||||
// Only need the stepping lock for this
|
||||
if (s_have_fake_cpu_thread)
|
||||
if (m_have_fake_cpu_thread)
|
||||
{
|
||||
s_have_fake_cpu_thread = false;
|
||||
m_have_fake_cpu_thread = false;
|
||||
Core::UndeclareAsCPUThread();
|
||||
}
|
||||
|
||||
@ -395,20 +390,17 @@ bool CPUManager::PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control
|
||||
{
|
||||
m_state_system_request_stepping = false;
|
||||
}
|
||||
else if (unpause_on_unlock && SetStateLocked(State::Running))
|
||||
else if (unpause_on_unlock)
|
||||
{
|
||||
was_unpaused = true;
|
||||
SetStateLocked(State::Running);
|
||||
}
|
||||
m_state_paused_and_locked = false;
|
||||
m_state_cpu_cvar.notify_one();
|
||||
|
||||
if (control_adjacent)
|
||||
RunAdjacentSystems(m_state == State::Running);
|
||||
}
|
||||
m_stepping_lock.unlock();
|
||||
}
|
||||
return was_unpaused;
|
||||
}
|
||||
|
||||
void CPUManager::AddCPUThreadJob(Common::MoveOnlyFunction<void()> function)
|
||||
{
|
||||
|
||||
@ -86,16 +86,18 @@ public:
|
||||
// Strictly read-only. A lock is required to change the value.
|
||||
const State* GetStatePtr() const;
|
||||
|
||||
// Locks the CPU Thread (waiting for it to become idle).
|
||||
// While this lock is held, the CPU Thread will not perform any action so it is safe to access
|
||||
// PowerPC, CoreTiming, etc. without racing the CPU Thread.
|
||||
// Cannot be used recursively. Must be paired as PauseAndLock(true)/PauseAndLock(false).
|
||||
// Return value for do_lock == true is whether the state was State::Running or not.
|
||||
// Return value for do_lock == false is whether the state was changed *to* State::Running or not.
|
||||
// Cannot be used by System threads as it will deadlock. It is threadsafe otherwise.
|
||||
// "control_adjacent" causes PauseAndLock to behave like SetStepping by modifying the
|
||||
// state of the Audio and FIFO subsystems as well.
|
||||
bool PauseAndLock(bool do_lock, bool unpause_on_unlock = true, bool control_adjacent = false);
|
||||
// Locks the CPU Thread (waiting for it to become idle). While this lock is held, the CPU Thread
|
||||
// will not perform any action so it is safe to access PowerPC, CoreTiming, etc. without racing
|
||||
// the CPU Thread.
|
||||
//
|
||||
// Cannot be used recursively. Cannot be used by System threads as it will deadlock. It is
|
||||
// threadsafe otherwise.
|
||||
//
|
||||
// Each call to PauseAndLock must be paired with a call to RestoreStateAndUnlock. The return value
|
||||
// for PauseAndLock is whether the state was State::Running or not and should be passed as the
|
||||
// argument to RestoreStateAndUnlock.
|
||||
bool PauseAndLock();
|
||||
void RestoreStateAndUnlock(bool unpause_on_unlock);
|
||||
|
||||
// Adds a job to be executed during on the CPU thread. This should be combined with
|
||||
// PauseAndLock(), as while the CPU is in the run loop, it won't execute the function.
|
||||
@ -122,6 +124,8 @@ private:
|
||||
// deadlock because of the order inversion. (A -> X,Y; B -> Y,X; A waits for
|
||||
// B, B waits for A)
|
||||
std::mutex m_stepping_lock;
|
||||
// Protected by m_stepping_lock.
|
||||
bool m_have_fake_cpu_thread = false;
|
||||
|
||||
// Primary lock. Protects changing m_state, requesting instruction stepping and
|
||||
// pause-and-locking.
|
||||
|
||||
@ -242,7 +242,11 @@ u16 DSPHLE::DSP_ReadControlRegister()
|
||||
return m_dsp_control.Hex;
|
||||
}
|
||||
|
||||
void DSPHLE::PauseAndLock(bool do_lock)
|
||||
void DSPHLE::PauseAndLock()
|
||||
{
|
||||
}
|
||||
|
||||
void DSPHLE::UnpauseAndUnlock()
|
||||
{
|
||||
}
|
||||
} // namespace DSP::HLE
|
||||
|
||||
@ -34,7 +34,8 @@ public:
|
||||
void Shutdown() override;
|
||||
bool IsLLE() const override { return false; }
|
||||
void DoState(PointerWrap& p) override;
|
||||
void PauseAndLock(bool do_lock) override;
|
||||
void PauseAndLock() override;
|
||||
void UnpauseAndUnlock() override;
|
||||
|
||||
void DSP_WriteMailBoxHigh(bool cpu_mailbox, u16 value) override;
|
||||
void DSP_WriteMailBoxLow(bool cpu_mailbox, u16 value) override;
|
||||
|
||||
@ -287,13 +287,12 @@ u32 DSPLLE::DSP_UpdateRate()
|
||||
return 12600; // TO BE TWEAKED
|
||||
}
|
||||
|
||||
void DSPLLE::PauseAndLock(bool do_lock)
|
||||
{
|
||||
if (do_lock)
|
||||
void DSPLLE::PauseAndLock()
|
||||
{
|
||||
m_dsp_thread_mutex.lock();
|
||||
}
|
||||
else
|
||||
|
||||
void DSPLLE::UnpauseAndUnlock()
|
||||
{
|
||||
m_dsp_thread_mutex.unlock();
|
||||
|
||||
@ -304,5 +303,4 @@ void DSPLLE::PauseAndLock(bool do_lock)
|
||||
m_dsp_event.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace DSP::LLE
|
||||
|
||||
@ -26,7 +26,8 @@ public:
|
||||
void Shutdown() override;
|
||||
bool IsLLE() const override { return true; }
|
||||
void DoState(PointerWrap& p) override;
|
||||
void PauseAndLock(bool do_lock) override;
|
||||
void PauseAndLock() override;
|
||||
void UnpauseAndUnlock() override;
|
||||
|
||||
void DSP_WriteMailBoxHigh(bool cpu_mailbox, u16 value) override;
|
||||
void DSP_WriteMailBoxLow(bool cpu_mailbox, u16 value) override;
|
||||
|
||||
@ -106,9 +106,9 @@ static void RunWithGPUThreadInactive(std::function<void()> f)
|
||||
auto& system = Core::System::GetInstance();
|
||||
const bool was_running = Core::GetState(system) == Core::State::Running;
|
||||
auto& fifo = system.GetFifo();
|
||||
fifo.PauseAndLock(true, was_running);
|
||||
fifo.PauseAndLock();
|
||||
f();
|
||||
fifo.PauseAndLock(false, was_running);
|
||||
fifo.RestoreState(was_running);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -67,9 +67,7 @@ void FifoManager::DoState(PointerWrap& p)
|
||||
p.Do(m_syncing_suspended);
|
||||
}
|
||||
|
||||
void FifoManager::PauseAndLock(bool do_lock, bool unpause_on_unlock)
|
||||
{
|
||||
if (do_lock)
|
||||
void FifoManager::PauseAndLock()
|
||||
{
|
||||
SyncGPU(SyncGPUReason::Other);
|
||||
EmulatorState(false);
|
||||
@ -79,12 +77,12 @@ void FifoManager::PauseAndLock(bool do_lock, bool unpause_on_unlock)
|
||||
|
||||
m_gpu_mainloop.WaitYield(std::chrono::milliseconds(100), Host_YieldToUI);
|
||||
}
|
||||
else
|
||||
|
||||
void FifoManager::RestoreState(const bool was_running)
|
||||
{
|
||||
if (unpause_on_unlock)
|
||||
if (was_running)
|
||||
EmulatorState(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FifoManager::Init()
|
||||
{
|
||||
|
||||
@ -52,7 +52,8 @@ public:
|
||||
void Shutdown();
|
||||
void Prepare(); // Must be called from the CPU thread.
|
||||
void DoState(PointerWrap& f);
|
||||
void PauseAndLock(bool do_lock, bool unpause_on_unlock);
|
||||
void PauseAndLock();
|
||||
void RestoreState(bool was_running);
|
||||
void UpdateWantDeterminism(bool want);
|
||||
bool UseDeterministicGPUThread() const { return m_use_deterministic_gpu_thread; }
|
||||
bool UseSyncGPU() const { return m_config_sync_gpu; }
|
||||
|
||||
@ -64,13 +64,13 @@ void VideoConfig::Refresh()
|
||||
|
||||
const bool lock_gpu_thread = Core::IsRunning(system);
|
||||
if (lock_gpu_thread)
|
||||
system.GetFifo().PauseAndLock(true, false);
|
||||
system.GetFifo().PauseAndLock();
|
||||
|
||||
g_Config.Refresh();
|
||||
g_Config.VerifyValidity();
|
||||
|
||||
if (lock_gpu_thread)
|
||||
system.GetFifo().PauseAndLock(false, true);
|
||||
system.GetFifo().RestoreState(true);
|
||||
};
|
||||
|
||||
s_config_changed_callback_id =
|
||||
|
||||
Loading…
Reference in New Issue
Block a user