Merge pull request #6321 from stenzek/efb-savestates

Support saving EFB and texture cache in save states
This commit is contained in:
Connor McLaughlin
2019-07-25 13:50:57 +10:00
committed by GitHub
28 changed files with 835 additions and 165 deletions
+5
View File
@@ -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
+18
View File
@@ -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,
+42
View File
@@ -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()
+4
View File
@@ -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);
+29 -2
View File
@@ -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
+5
View File
@@ -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
+119 -102
View File
@@ -63,7 +63,7 @@ static AfterLoadCallbackFunc s_on_after_load_callback;
// Temporary undo state buffer
static std::vector<u8> g_undo_load_buffer;
static std::vector<u8> g_current_buffer;
static int g_loadDepth = 0;
static bool s_load_or_save_in_progress;
static std::mutex g_cs_undo_load_buffer;
static std::mutex g_cs_current_buffer;
@@ -72,7 +72,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 110; // Last changed in PR 8036
static const u32 STATE_VERSION = 111; // Last changed in PR 6321
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,
@@ -170,6 +170,11 @@ static void DoState(PointerWrap& p)
return;
}
// Movie must be done before the video backend, because the window is redrawn in the video backend
// state load, and the frame number must be up-to-date.
Movie::DoState(p);
p.DoMarker("Movie");
// Begin with video backend, so that it gets a chance to clear its caches and writeback modified
// things to RAM
g_video_backend->DoState(p);
@@ -186,8 +191,6 @@ static void DoState(PointerWrap& p)
if (SConfig::GetInstance().bWii)
Wiimote::DoState(p);
p.DoMarker("Wiimote");
Movie::DoState(p);
p.DoMarker("Movie");
Gecko::DoState(p);
p.DoMarker("Gecko");
@@ -204,27 +207,31 @@ void LoadFromBuffer(std::vector<u8>& buffer)
return;
}
Core::RunAsCPUThread([&] {
u8* ptr = &buffer[0];
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(p);
});
Core::RunOnCPUThread(
[&] {
u8* ptr = &buffer[0];
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(p);
},
true);
}
void SaveToBuffer(std::vector<u8>& buffer)
{
Core::RunAsCPUThread([&] {
u8* ptr = nullptr;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
Core::RunOnCPUThread(
[&] {
u8* ptr = nullptr;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
DoState(p);
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
buffer.resize(buffer_size);
DoState(p);
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
buffer.resize(buffer_size);
ptr = &buffer[0];
p.SetMode(PointerWrap::MODE_WRITE);
DoState(p);
});
ptr = &buffer[0];
p.SetMode(PointerWrap::MODE_WRITE);
DoState(p);
},
true);
}
// return state number not in map
@@ -381,42 +388,51 @@ static void CompressAndDumpState(CompressAndDumpState_args save_args)
void SaveAs(const std::string& filename, bool wait)
{
Core::RunAsCPUThread([&] {
// Measure the size of the buffer.
u8* ptr = nullptr;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
DoState(p);
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
if (s_load_or_save_in_progress)
return;
// Then actually do the write.
{
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);
}
s_load_or_save_in_progress = true;
if (p.GetMode() == PointerWrap::MODE_WRITE)
{
Core::DisplayMessage("Saving State...", 1000);
Core::RunOnCPUThread(
[&] {
// Measure the size of the buffer.
u8* ptr = nullptr;
PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
DoState(p);
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
CompressAndDumpState_args save_args;
save_args.buffer_vector = &g_current_buffer;
save_args.buffer_mutex = &g_cs_current_buffer;
save_args.filename = filename;
save_args.wait = wait;
// Then actually do the write.
{
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);
}
Flush();
g_save_thread = std::thread(CompressAndDumpState, save_args);
g_compressAndDumpStateSyncEvent.Wait();
}
else
{
// someone aborted the save by changing the mode?
Core::DisplayMessage("Unable to save: Internal DoState Error", 4000);
}
});
if (p.GetMode() == PointerWrap::MODE_WRITE)
{
Core::DisplayMessage("Saving State...", 1000);
CompressAndDumpState_args save_args;
save_args.buffer_vector = &g_current_buffer;
save_args.buffer_mutex = &g_cs_current_buffer;
save_args.filename = filename;
save_args.wait = wait;
Flush();
g_save_thread = std::thread(CompressAndDumpState, save_args);
g_compressAndDumpStateSyncEvent.Wait();
}
else
{
// someone aborted the save by changing the mode?
Core::DisplayMessage("Unable to save: Internal DoState Error", 4000);
}
},
true);
s_load_or_save_in_progress = false;
}
bool ReadHeader(const std::string& filename, StateHeader& header)
@@ -515,7 +531,7 @@ static void LoadFileStateData(const std::string& filename, std::vector<u8>& ret_
void LoadAs(const std::string& filename)
{
if (!Core::IsRunning())
if (!Core::IsRunning() || s_load_or_save_in_progress)
{
return;
}
@@ -525,64 +541,65 @@ void LoadAs(const std::string& filename)
return;
}
Core::RunAsCPUThread([&] {
g_loadDepth++;
s_load_or_save_in_progress = true;
// Save temp buffer for undo load state
if (!Movie::IsJustStartingRecordingInputFromSaveState())
{
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
SaveToBuffer(g_undo_load_buffer);
if (Movie::IsMovieActive())
Movie::SaveRecording(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
else if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm"))
File::Delete(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
}
Core::RunOnCPUThread(
[&] {
// Save temp buffer for undo load state
if (!Movie::IsJustStartingRecordingInputFromSaveState())
{
std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
SaveToBuffer(g_undo_load_buffer);
if (Movie::IsMovieActive())
Movie::SaveRecording(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
else if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm"))
File::Delete(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
}
bool loaded = false;
bool loadedSuccessfully = false;
bool loaded = false;
bool loadedSuccessfully = false;
// brackets here are so buffer gets freed ASAP
{
std::vector<u8> buffer;
LoadFileStateData(filename, buffer);
// brackets here are so buffer gets freed ASAP
{
std::vector<u8> buffer;
LoadFileStateData(filename, buffer);
if (!buffer.empty())
{
u8* ptr = &buffer[0];
PointerWrap p(&ptr, PointerWrap::MODE_READ);
DoState(p);
loaded = true;
loadedSuccessfully = (p.GetMode() == PointerWrap::MODE_READ);
}
}
if (!buffer.empty())
{
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()), 2000);
if (File::Exists(filename + ".dtm"))
Movie::LoadInput(filename + ".dtm");
else if (!Movie::IsJustStartingRecordingInputFromSaveState() &&
!Movie::IsJustStartingPlayingInputFromSaveState())
Movie::EndPlayInput(false);
}
else
{
Core::DisplayMessage("The savestate could not be loaded", OSD::Duration::NORMAL);
if (loaded)
{
if (loadedSuccessfully)
{
Core::DisplayMessage(StringFromFormat("Loaded state from %s", filename.c_str()), 2000);
if (File::Exists(filename + ".dtm"))
Movie::LoadInput(filename + ".dtm");
else if (!Movie::IsJustStartingRecordingInputFromSaveState() &&
!Movie::IsJustStartingPlayingInputFromSaveState())
Movie::EndPlayInput(false);
}
else
{
Core::DisplayMessage("The savestate could not be loaded", OSD::Duration::NORMAL);
// since we could be in an inconsistent state now (and might crash or whatever), undo.
if (g_loadDepth < 2)
UndoLoadState();
}
}
// since we could be in an inconsistent state now (and might crash or whatever), undo.
UndoLoadState();
}
}
if (s_on_after_load_callback)
s_on_after_load_callback();
if (s_on_after_load_callback)
s_on_after_load_callback();
},
true);
g_loadDepth--;
});
s_load_or_save_in_progress = false;
}
void SetOnAfterLoadCallback(AfterLoadCallbackFunc callback)
@@ -100,10 +100,13 @@ void HacksWidget::CreateWidgets()
m_disable_bounding_box =
new GraphicsBool(tr("Disable Bounding Box"), Config::GFX_HACK_BBOX_ENABLE, true);
m_vertex_rounding = new GraphicsBool(tr("Vertex Rounding"), Config::GFX_HACK_VERTEX_ROUDING);
m_save_texture_cache_state =
new GraphicsBool(tr("Save Texture Cache to State"), Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE);
other_layout->addWidget(m_fast_depth_calculation, 0, 0);
other_layout->addWidget(m_disable_bounding_box, 0, 1);
other_layout->addWidget(m_vertex_rounding, 1, 0);
other_layout->addWidget(m_save_texture_cache_state, 1, 1);
main_layout->addWidget(efb_box);
main_layout->addWidget(texture_cache_box);
@@ -244,6 +247,10 @@ void HacksWidget::AddDescriptions()
static const char TR_DISABLE_BOUNDINGBOX_DESCRIPTION[] =
QT_TR_NOOP("Disables bounding box emulation.\n\nThis may improve GPU performance "
"significantly, but some games will break.\n\nIf unsure, leave this checked.");
static const char TR_SAVE_TEXTURE_CACHE_TO_STATE_DESCRIPTION[] = QT_TR_NOOP(
"Includes the contents of the embedded frame buffer (EFB) and upscaled EFB copies "
"in save states. Fixes missing and/or non-upscaled textures/objects when loading "
"states at the cost of additional save/load time.\n\nIf unsure, leave this checked.");
static const char TR_VERTEX_ROUNDING_DESCRIPTION[] =
QT_TR_NOOP("Rounds 2D vertices to whole pixels.\n\nFixes graphical problems in some games at "
"higher internal resolutions. This setting has no effect when native internal "
@@ -259,6 +266,7 @@ void HacksWidget::AddDescriptions()
AddDescription(m_gpu_texture_decoding, TR_GPU_DECODING_DESCRIPTION);
AddDescription(m_fast_depth_calculation, TR_FAST_DEPTH_CALC_DESCRIPTION);
AddDescription(m_disable_bounding_box, TR_DISABLE_BOUNDINGBOX_DESCRIPTION);
AddDescription(m_save_texture_cache_state, TR_SAVE_TEXTURE_CACHE_TO_STATE_DESCRIPTION);
AddDescription(m_vertex_rounding, TR_VERTEX_ROUNDING_DESCRIPTION);
}
@@ -42,6 +42,7 @@ private:
QCheckBox* m_fast_depth_calculation;
QCheckBox* m_disable_bounding_box;
QCheckBox* m_vertex_rounding;
QCheckBox* m_save_texture_cache_state;
QCheckBox* m_defer_efb_copies;
void CreateWidgets();
@@ -11,6 +11,7 @@
#include "VideoCommon/VertexManagerBase.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoState.h"
AsyncRequests AsyncRequests::s_singleton;
@@ -154,6 +155,10 @@ void AsyncRequests::HandleEvent(const AsyncRequests::Event& e)
case Event::PERF_QUERY:
g_perf_query->FlushResults();
break;
case Event::DO_SAVE_STATE:
VideoCommon_DoState(*e.do_save_state.p);
break;
}
}
+7
View File
@@ -13,6 +13,7 @@
#include "Common/Flag.h"
struct EfbPokeData;
class PointerWrap;
class AsyncRequests
{
@@ -28,6 +29,7 @@ public:
SWAP_EVENT,
BBOX_READ,
PERF_QUERY,
DO_SAVE_STATE,
} type;
u64 time;
@@ -64,6 +66,11 @@ public:
struct
{
} perf_query;
struct
{
PointerWrap* p;
} do_save_state;
};
};
-3
View File
@@ -68,9 +68,6 @@ static void BPWritten(const BPCmd& bp)
----------------------------------------------------------------------------------------------------------------
*/
// check for invalid state, else unneeded configuration are built
g_video_backend->CheckInvalidState();
if (((s32*)&bpmem)[bp.address] == bp.newvalue)
{
if (!(bp.address == BPMEM_TRIGGER_EFB_COPY || bp.address == BPMEM_CLEARBBOX1 ||
+3 -5
View File
@@ -299,14 +299,15 @@ void RunGpuLoop()
[] {
const SConfig& param = SConfig::GetInstance();
// Run events from the CPU thread.
AsyncRequests::GetInstance()->PullEvents();
// Do nothing while paused
if (!s_emu_running_state.IsSet())
return;
if (s_use_deterministic_gpu_thread)
{
AsyncRequests::GetInstance()->PullEvents();
// All the fifo/CP stuff is on the CPU. We just need to run the opcode decoder.
u8* seen_ptr = s_video_buffer_seen_ptr;
u8* write_ptr = s_video_buffer_write_ptr;
@@ -321,9 +322,6 @@ void RunGpuLoop()
else
{
CommandProcessor::SCPFifoStruct& fifo = CommandProcessor::fifo;
AsyncRequests::GetInstance()->PullEvents();
CommandProcessor::SetCPStatusFromGPU();
// check if we are able to run this buffer
@@ -7,8 +7,10 @@
#include "VideoCommon/FramebufferShaderGen.h"
#include "VideoCommon/VertexManagerBase.h"
#include "Common/ChunkFile.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Core/Config/GraphicsSettings.h"
#include "VideoCommon/AbstractFramebuffer.h"
#include "VideoCommon/AbstractPipeline.h"
#include "VideoCommon/AbstractShader.h"
@@ -464,6 +466,20 @@ bool FramebufferManager::CompileReadbackPipelines()
return false;
}
// EFB restore pipeline
auto restore_shader = g_renderer->CreateShaderFromSource(
ShaderStage::Pixel, FramebufferShaderGen::GenerateEFBRestorePixelShader());
if (!restore_shader)
return false;
config.framebuffer_state = GetEFBFramebufferState();
config.framebuffer_state.per_sample_shading = false;
config.vertex_shader = g_shader_cache->GetScreenQuadVertexShader();
config.pixel_shader = restore_shader.get();
m_efb_restore_pipeline = g_renderer->CreatePipeline(config);
if (!m_efb_restore_pipeline)
return false;
return true;
}
@@ -842,3 +858,107 @@ void FramebufferManager::DestroyPokePipelines()
m_color_poke_pipeline.reset();
m_poke_vertex_format.reset();
}
void FramebufferManager::DoState(PointerWrap& p)
{
FlushEFBPokes();
bool save_efb_state = Config::Get(Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE);
p.Do(save_efb_state);
if (!save_efb_state)
return;
if (p.GetMode() == PointerWrap::MODE_WRITE || p.GetMode() == PointerWrap::MODE_MEASURE)
DoSaveState(p);
else
DoLoadState(p);
}
void FramebufferManager::DoSaveState(PointerWrap& p)
{
// For multisampling, we need to resolve first before we can save.
// This won't be bit-exact when loading, which could cause interesting rendering side-effects for
// a frame. But whatever, MSAA doesn't exactly behave that well anyway.
AbstractTexture* color_texture = ResolveEFBColorTexture(m_efb_color_texture->GetRect());
AbstractTexture* depth_texture = ResolveEFBDepthTexture(m_efb_depth_texture->GetRect());
// We don't want to save these as rendertarget textures, just the data itself when deserializing.
const TextureConfig color_texture_config(color_texture->GetWidth(), color_texture->GetHeight(),
color_texture->GetLevels(), color_texture->GetLayers(),
1, GetEFBColorFormat(), 0);
g_texture_cache->SerializeTexture(color_texture, color_texture_config, p);
if (GetEFBDepthFormat() == AbstractTextureFormat::D32F)
{
const TextureConfig depth_texture_config(
depth_texture->GetWidth(), depth_texture->GetHeight(), depth_texture->GetLevels(),
depth_texture->GetLayers(), 1,
AbstractTexture::GetColorFormatForDepthFormat(GetEFBDepthFormat()), 0);
g_texture_cache->SerializeTexture(depth_texture, depth_texture_config, p);
}
else
{
// If the EFB is backed by a D24S8 texture, we first have to convert it to R32F.
const TextureConfig temp_texture_config(depth_texture->GetWidth(), depth_texture->GetHeight(),
depth_texture->GetLevels(), depth_texture->GetLayers(),
1, AbstractTextureFormat::R32F,
AbstractTextureFlag_RenderTarget);
std::unique_ptr<AbstractTexture> temp_texture = g_renderer->CreateTexture(temp_texture_config);
std::unique_ptr<AbstractFramebuffer> temp_fb =
g_renderer->CreateFramebuffer(temp_texture.get(), nullptr);
if (temp_texture && temp_fb)
{
g_renderer->ScaleTexture(temp_fb.get(), temp_texture->GetRect(), depth_texture,
depth_texture->GetRect());
const TextureConfig depth_texture_config(
depth_texture->GetWidth(), depth_texture->GetHeight(), depth_texture->GetLevels(),
depth_texture->GetLayers(), 1, temp_texture->GetFormat(), 0);
g_texture_cache->SerializeTexture(depth_texture, depth_texture_config, p);
}
else
{
PanicAlert("Failed to create temp texture for depth saving");
g_texture_cache->SerializeTexture(color_texture, color_texture_config, p);
}
}
}
void FramebufferManager::DoLoadState(PointerWrap& p)
{
// Invalidate any peek cache tiles.
InvalidatePeekCache(true);
// Deserialize the color and depth textures. This could fail.
auto color_tex = g_texture_cache->DeserializeTexture(p);
auto depth_tex = g_texture_cache->DeserializeTexture(p);
// If the stereo mode is different in the save state, throw it away.
if (!color_tex || !depth_tex ||
color_tex->texture->GetLayers() != m_efb_color_texture->GetLayers())
{
WARN_LOG(VIDEO, "Failed to deserialize EFB contents. Clearing instead.");
g_renderer->SetAndClearFramebuffer(
m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}},
g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f : 0.0f);
return;
}
// Size differences are okay here, since the linear filtering will downscale/upscale it.
// Depth buffer is always point sampled, since we don't want to interpolate depth values.
const bool rescale = color_tex->texture->GetWidth() != m_efb_color_texture->GetWidth() ||
color_tex->texture->GetHeight() != m_efb_color_texture->GetHeight();
// Draw the deserialized textures over the EFB.
g_renderer->BeginUtilityDrawing();
g_renderer->SetAndDiscardFramebuffer(m_efb_framebuffer.get());
g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect());
g_renderer->SetPipeline(m_efb_restore_pipeline.get());
g_renderer->SetTexture(0, color_tex->texture.get());
g_renderer->SetTexture(1, depth_tex->texture.get());
g_renderer->SetSamplerState(0, rescale ? RenderState::GetLinearSamplerState() :
RenderState::GetPointSamplerState());
g_renderer->SetSamplerState(1, RenderState::GetPointSamplerState());
g_renderer->Draw(0, 3);
g_renderer->EndUtilityDrawing();
}
@@ -17,6 +17,7 @@
#include "VideoCommon/TextureConfig.h"
class NativeVertexFormat;
class PointerWrap;
enum class EFBReinterpretType
{
@@ -95,6 +96,9 @@ public:
void PokeEFBDepth(u32 x, u32 y, float depth);
void FlushEFBPokes();
// Save state load/save.
void DoState(PointerWrap& p);
protected:
struct EFBPokeVertex
{
@@ -145,6 +149,9 @@ protected:
void DrawPokeVertices(const EFBPokeVertex* vertices, u32 vertex_count,
const AbstractPipeline* pipeline);
void DoLoadState(PointerWrap& p);
void DoSaveState(PointerWrap& p);
std::unique_ptr<AbstractTexture> m_efb_color_texture;
std::unique_ptr<AbstractTexture> m_efb_convert_color_texture;
std::unique_ptr<AbstractTexture> m_efb_depth_texture;
@@ -156,6 +163,9 @@ protected:
std::unique_ptr<AbstractFramebuffer> m_efb_depth_resolve_framebuffer;
std::unique_ptr<AbstractPipeline> m_efb_depth_resolve_pipeline;
// Pipeline for restoring the contents of the EFB from a save state
std::unique_ptr<AbstractPipeline> m_efb_restore_pipeline;
// Format conversion shaders
std::array<std::unique_ptr<AbstractPipeline>, 6> m_format_conversion_pipelines;
@@ -644,4 +644,24 @@ std::string GenerateTextureReinterpretShader(TextureFormat from_format, TextureF
return ss.str();
}
std::string GenerateEFBRestorePixelShader()
{
std::stringstream ss;
EmitSamplerDeclarations(ss, 0, 2, false);
EmitPixelMainDeclaration(ss, 1, 0, "float4",
GetAPIType() == APIType::D3D ? "out float depth : SV_Depth, " : "");
ss << "{\n";
ss << " float3 coords = float3(v_tex0.x, "
<< (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin ? "1.0 - " : "")
<< "v_tex0.y, v_tex0.z);\n";
ss << " ocol0 = ";
EmitSampleTexture(ss, 0, "coords");
ss << ";\n";
ss << " " << (GetAPIType() == APIType::D3D ? "depth" : "gl_FragDepth") << " = ";
EmitSampleTexture(ss, 1, "coords");
ss << ".r;\n";
ss << "}\n";
return ss.str();
}
} // namespace FramebufferShaderGen
@@ -30,5 +30,6 @@ std::string GenerateEFBPokeVertexShader();
std::string GenerateColorPixelShader();
std::string GenerateFormatConversionShader(EFBReinterpretType convtype, u32 samples);
std::string GenerateTextureReinterpretShader(TextureFormat from_format, TextureFormat to_format);
std::string GenerateEFBRestorePixelShader();
} // namespace FramebufferShaderGen

Some files were not shown because too many files have changed in this diff Show More