Skip to content

Commit

Permalink
Merge pull request #6321 from stenzek/efb-savestates
Browse files Browse the repository at this point in the history
Support saving EFB and texture cache in save states
  • Loading branch information
stenzek committed Jul 25, 2019
2 parents d82e0e0 + 2d4d61e commit ac9912b
Show file tree
Hide file tree
Showing 28 changed files with 847 additions and 177 deletions.
5 changes: 5 additions & 0 deletions Data/Sys/GameSettings/NAL.ini
Expand Up @@ -14,3 +14,8 @@


[Video_Stereoscopy] [Video_Stereoscopy]
StereoConvergence = 5000 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
18 changes: 18 additions & 0 deletions Data/Sys/GameSettings/NAT.ini
@@ -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
2 changes: 2 additions & 0 deletions Source/Core/Core/Config/GraphicsSettings.cpp
Expand Up @@ -91,6 +91,8 @@ const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS{
{System::GFX, "Settings", "ShaderCompilerThreads"}, 1}; {System::GFX, "Settings", "ShaderCompilerThreads"}, 1};
const ConfigInfo<int> GFX_SHADER_PRECOMPILER_THREADS{ const ConfigInfo<int> GFX_SHADER_PRECOMPILER_THREADS{
{System::GFX, "Settings", "ShaderPrecompilerThreads"}, 1}; {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_ZCOMPLOC{{System::GFX, "Settings", "SWZComploc"}, true};
const ConfigInfo<bool> GFX_SW_ZFREEZE{{System::GFX, "Settings", "SWZFreeze"}, true}; const ConfigInfo<bool> GFX_SW_ZFREEZE{{System::GFX, "Settings", "SWZFreeze"}, true};
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/Config/GraphicsSettings.h
Expand Up @@ -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<ShaderCompilationMode> GFX_SHADER_COMPILATION_MODE;
extern const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS; extern const ConfigInfo<int> GFX_SHADER_COMPILER_THREADS;
extern const ConfigInfo<int> GFX_SHADER_PRECOMPILER_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_ZCOMPLOC;
extern const ConfigInfo<bool> GFX_SW_ZFREEZE; extern const ConfigInfo<bool> GFX_SW_ZFREEZE;
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
Expand Up @@ -90,6 +90,7 @@ bool IsSettingSaveable(const Config::ConfigLocation& config_location)
Config::GFX_SHADER_COMPILATION_MODE.location, Config::GFX_SHADER_COMPILATION_MODE.location,
Config::GFX_SHADER_COMPILER_THREADS.location, Config::GFX_SHADER_COMPILER_THREADS.location,
Config::GFX_SHADER_PRECOMPILER_THREADS.location, Config::GFX_SHADER_PRECOMPILER_THREADS.location,
Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE.location,


Config::GFX_SW_ZCOMPLOC.location, Config::GFX_SW_ZCOMPLOC.location,
Config::GFX_SW_ZFREEZE.location, Config::GFX_SW_ZFREEZE.location,
Expand Down
42 changes: 42 additions & 0 deletions Source/Core/Core/Core.cpp
Expand Up @@ -21,6 +21,7 @@
#include "Common/CPUDetect.h" #include "Common/CPUDetect.h"
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/Event.h"
#include "Common/FileUtil.h" #include "Common/FileUtil.h"
#include "Common/Flag.h" #include "Common/Flag.h"
#include "Common/Logging/LogManager.h" #include "Common/Logging/LogManager.h"
Expand Down Expand Up @@ -110,6 +111,7 @@ struct HostJob
}; };
static std::mutex s_host_jobs_lock; static std::mutex s_host_jobs_lock;
static std::queue<HostJob> s_host_jobs_queue; 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; static thread_local bool tls_is_cpu_thread = false;


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


HW::Init(); HW::Init();

Common::ScopeGuard hw_guard{[] { Common::ScopeGuard hw_guard{[] {
// We must set up this flag before executing HW::Shutdown() // We must set up this flag before executing HW::Shutdown()
s_hardware_initialized = false; s_hardware_initialized = false;
Expand Down Expand Up @@ -771,6 +774,45 @@ void RunAsCPUThread(std::function<void()> function)
PauseAndLock(false, was_unpaused); 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 // Display FPS info
// This should only be called from VI // This should only be called from VI
void VideoThrottle() void VideoThrottle()
Expand Down
4 changes: 4 additions & 0 deletions Source/Core/Core/Core.h
Expand Up @@ -82,6 +82,10 @@ void UpdateTitle();
// This should only be called from the CPU thread or the host thread. // This should only be called from the CPU thread or the host thread.
void RunAsCPUThread(std::function<void()> function); 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 // for calling back into UI code without introducing a dependency on it in core
using StateChangedCallbackFunc = std::function<void(Core::State)>; using StateChangedCallbackFunc = std::function<void(Core::State)>;
void SetOnStateChangedCallback(StateChangedCallbackFunc callback); void SetOnStateChangedCallback(StateChangedCallbackFunc callback);
Expand Down
31 changes: 29 additions & 2 deletions Source/Core/Core/HW/CPU.cpp
Expand Up @@ -6,6 +6,7 @@


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


#include "AudioCommon/AudioCommon.h" #include "AudioCommon/AudioCommon.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
Expand Down Expand Up @@ -44,6 +45,7 @@ static bool s_state_paused_and_locked = false;
static bool s_state_system_request_stepping = false; static bool s_state_system_request_stepping = false;
static bool s_state_cpu_step_instruction = false; static bool s_state_cpu_step_instruction = false;
static Common::Event* s_state_cpu_step_instruction_sync = nullptr; 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) void Init(PowerPC::CPUCore cpu_core)
{ {
Expand All @@ -60,6 +62,9 @@ void Shutdown()
// Requires holding s_state_change_lock // Requires holding s_state_change_lock
static void FlushStepSyncEventLocked() static void FlushStepSyncEventLocked()
{ {
if (!s_state_cpu_step_instruction)
return;

if (s_state_cpu_step_instruction_sync) if (s_state_cpu_step_instruction_sync)
{ {
s_state_cpu_step_instruction_sync->Set(); s_state_cpu_step_instruction_sync->Set();
Expand All @@ -68,12 +73,25 @@ static void FlushStepSyncEventLocked()
s_state_cpu_step_instruction = false; 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() void Run()
{ {
std::unique_lock<std::mutex> state_lock(s_state_change_lock); std::unique_lock<std::mutex> state_lock(s_state_change_lock);
while (s_state != State::PowerDown) while (s_state != State::PowerDown)
{ {
s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; }); s_state_cpu_cvar.wait(state_lock, [] { return !s_state_paused_and_locked; });
ExecutePendingJobs(state_lock);


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


case State::Stepping: case State::Stepping:
// Wait for step command. // Wait for step command.
s_state_cpu_cvar.wait(state_lock, s_state_cpu_cvar.wait(state_lock, [&state_lock] {
[] { return s_state_cpu_step_instruction || !IsStepping(); }); ExecutePendingJobs(state_lock);
return s_state_cpu_step_instruction || !IsStepping();
});
if (!IsStepping()) if (!IsStepping())
{ {
// Signal event if the mode changes. // Signal event if the mode changes.
Expand Down Expand Up @@ -330,4 +350,11 @@ bool PauseAndLock(bool do_lock, bool unpause_on_unlock, bool control_adjacent)
} }
return was_unpaused; 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 } // namespace CPU
5 changes: 5 additions & 0 deletions Source/Core/Core/HW/CPU.h
Expand Up @@ -3,6 +3,7 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.


#pragma once #pragma once
#include <functional>


namespace Common namespace Common
{ {
Expand Down Expand Up @@ -74,4 +75,8 @@ const State* GetStatePtr();
// "control_adjacent" causes PauseAndLock to behave like EnableStepping by modifying the // "control_adjacent" causes PauseAndLock to behave like EnableStepping by modifying the
// state of the Audio and FIFO subsystems as well. // state of the Audio and FIFO subsystems as well.
bool PauseAndLock(bool do_lock, bool unpause_on_unlock = true, bool control_adjacent = false); 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 } // namespace CPU

0 comments on commit ac9912b

Please sign in to comment.