Skip to content
Permalink
Browse files

Merge pull request #6321 from stenzek/efb-savestates

Support saving EFB and texture cache in save states
  • Loading branch information...
stenzek committed Jul 25, 2019
2 parents d82e0e0 + 2d4d61e commit ac9912bad3ad35b1ef27151ba724a3424c110acc
@@ -14,3 +14,8 @@

[Video_Stereoscopy]
StereoConvergence = 5000

[Video_Settings]
# This game creates a large number of EFB copies at different addresses, resulting
# in a large texture cache which takes considerable time to save.
SaveTextureCacheToState = False
@@ -0,0 +1,18 @@
# NATJ01, NATP01, NATE01 - Mario Tennis (Virtual Console)

[Core]
# Values set here will override the main Dolphin settings.

[OnLoad]
# Add memory patches to be loaded once on boot here.

[OnFrame]
# Add memory patches to be applied every frame here.

[ActionReplay]
# Add action replay cheats here.

[Video_Settings]
# This game creates a large number of EFB copies at different addresses, resulting
# in a large texture cache which takes considerable time to save.
SaveTextureCacheToState = False
@@ -91,6 +91,8 @@ const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS{
{System::GFX, "Settings", "ShaderCompilerThreads"}, 1};
const ConfigInfo<int> GFX_SHADER_PRECOMPILER_THREADS{
{System::GFX, "Settings", "ShaderPrecompilerThreads"}, 1};
const ConfigInfo<bool> GFX_SAVE_TEXTURE_CACHE_TO_STATE{
{System::GFX, "Settings", "SaveTextureCacheToState"}, true};

const ConfigInfo<bool> GFX_SW_ZCOMPLOC{{System::GFX, "Settings", "SWZComploc"}, true};
const ConfigInfo<bool> GFX_SW_ZFREEZE{{System::GFX, "Settings", "SWZFreeze"}, true};
@@ -67,6 +67,7 @@ extern const ConfigInfo<bool> GFX_WAIT_FOR_SHADERS_BEFORE_STARTING;
extern const ConfigInfo<ShaderCompilationMode> GFX_SHADER_COMPILATION_MODE;
extern const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS;
extern const ConfigInfo<int> GFX_SHADER_PRECOMPILER_THREADS;
extern const ConfigInfo<bool> GFX_SAVE_TEXTURE_CACHE_TO_STATE;

extern const ConfigInfo<bool> GFX_SW_ZCOMPLOC;
extern const ConfigInfo<bool> GFX_SW_ZFREEZE;
@@ -90,6 +90,7 @@ bool IsSettingSaveable(const Config::ConfigLocation& config_location)
Config::GFX_SHADER_COMPILATION_MODE.location,
Config::GFX_SHADER_COMPILER_THREADS.location,
Config::GFX_SHADER_PRECOMPILER_THREADS.location,
Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE.location,

Config::GFX_SW_ZCOMPLOC.location,
Config::GFX_SW_ZFREEZE.location,
@@ -21,6 +21,7 @@
#include "Common/CPUDetect.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/FileUtil.h"
#include "Common/Flag.h"
#include "Common/Logging/LogManager.h"
@@ -110,6 +111,7 @@ struct HostJob
};
static std::mutex s_host_jobs_lock;
static std::queue<HostJob> s_host_jobs_queue;
static Common::Event s_cpu_thread_job_finished;

static thread_local bool tls_is_cpu_thread = false;

@@ -433,6 +435,7 @@ static void EmuThread(std::unique_ptr<BootParameters> boot, WindowSystemInfo wsi
Common::ScopeGuard movie_guard{Movie::Shutdown};

HW::Init();

Common::ScopeGuard hw_guard{[] {
// We must set up this flag before executing HW::Shutdown()
s_hardware_initialized = false;
@@ -771,6 +774,45 @@ void RunAsCPUThread(std::function<void()> function)
PauseAndLock(false, was_unpaused);
}

void RunOnCPUThread(std::function<void()> function, bool wait_for_completion)
{
// If the CPU thread is not running, assume there is no active CPU thread we can race against.
if (!IsRunning() || IsCPUThread())
{
function();
return;
}

// Pause the CPU (set it to stepping mode).
const bool was_running = PauseAndLock(true, true);

// Queue the job function.
if (wait_for_completion)
{
// Trigger the event after executing the function.
s_cpu_thread_job_finished.Reset();
CPU::AddCPUThreadJob([&function]() {
function();
s_cpu_thread_job_finished.Set();
});
}
else
{
CPU::AddCPUThreadJob(std::move(function));
}

// Release the CPU thread, and let it execute the callback.
PauseAndLock(false, was_running);

// If we're waiting for completion, block until the event fires.
if (wait_for_completion)
{
// Periodically yield to the UI thread, so we don't deadlock.
while (!s_cpu_thread_job_finished.WaitFor(std::chrono::milliseconds(10)))
Host_YieldToUI();
}
}

// Display FPS info
// This should only be called from VI
void VideoThrottle()
@@ -82,6 +82,10 @@ void UpdateTitle();
// This should only be called from the CPU thread or the host thread.
void RunAsCPUThread(std::function<void()> function);

// Run a function on the CPU thread, asynchronously.
// This is only valid to call from the host thread, since it uses PauseAndLock() internally.
void RunOnCPUThread(std::function<void()> function, bool wait_for_completion);

// for calling back into UI code without introducing a dependency on it in core
using StateChangedCallbackFunc = std::function<void(Core::State)>;
void SetOnStateChangedCallback(StateChangedCallbackFunc callback);
@@ -6,6 +6,7 @@

#include <condition_variable>
#include <mutex>
#include <queue>

#include "AudioCommon/AudioCommon.h"
#include "Common/CommonTypes.h"
@@ -44,6 +45,7 @@ static bool s_state_paused_and_locked = false;
static bool s_state_system_request_stepping = false;
static bool s_state_cpu_step_instruction = false;
static Common::Event* s_state_cpu_step_instruction_sync = nullptr;
static std::queue<std::function<void()>> s_pending_jobs;

void Init(PowerPC::CPUCore cpu_core)
{
@@ -60,6 +62,9 @@ void Shutdown()
// Requires holding s_state_change_lock
static void FlushStepSyncEventLocked()
{
if (!s_state_cpu_step_instruction)
return;

if (s_state_cpu_step_instruction_sync)
{
s_state_cpu_step_instruction_sync->Set();
@@ -68,12 +73,25 @@ static void FlushStepSyncEventLocked()
s_state_cpu_step_instruction = false;
}

static void ExecutePendingJobs(std::unique_lock<std::mutex>& state_lock)
{
while (!s_pending_jobs.empty())
{
auto callback = s_pending_jobs.front();
s_pending_jobs.pop();
state_lock.unlock();
callback();
state_lock.lock();
}
}

void Run()
{
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
while (s_state != State::PowerDown)
{
s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; });
ExecutePendingJobs(state_lock);

switch (s_state)
{
@@ -108,8 +126,10 @@ void Run()

case State::Stepping:
// Wait for step command.
s_state_cpu_cvar.wait(state_lock,
[] { return s_state_cpu_step_instruction || !IsStepping(); });
s_state_cpu_cvar.wait(state_lock, [&state_lock] {
ExecutePendingJobs(state_lock);
return s_state_cpu_step_instruction || !IsStepping();
});
if (!IsStepping())
{
// Signal event if the mode changes.
@@ -330,4 +350,11 @@ bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
}
return was_unpaused;
}

void AddCPUThreadJob(std::function<void()> function)
{
std::unique_lock<std::mutex> state_lock(s_state_change_lock);
s_pending_jobs.push(std::move(function));
}

} // namespace CPU
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.

#pragma once
#include <functional>

namespace Common
{
@@ -74,4 +75,8 @@ const State* GetStatePtr();
// "control_adjacent" causes PauseAndLock to behave like EnableStepping 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);

// 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.
void AddCPUThreadJob(std::function<void()> function);
} // namespace CPU

0 comments on commit ac9912b

Please sign in to comment.
You can’t perform that action at this time.