Skip to content

Commit

Permalink
System: Use task queue for saving states/screenshots/gpudumps
Browse files Browse the repository at this point in the history
System shutdown no longer needs to block. Gets rid of the slight
hitch when shutting down and saving state with the Big Picture UI.
  • Loading branch information
stenzek committed Jan 3, 2025
1 parent 5476015 commit db14824
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 49 deletions.
4 changes: 1 addition & 3 deletions src/core/gpu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2103,7 +2103,7 @@ void GPU::StopRecordingGPUDump()
Host::AddIconOSDMessage(
osd_key, ICON_EMOJI_CAMERA_WITH_FLASH,
fmt::format(TRANSLATE_FS("GPU", "Compressing GPU trace '{}'..."), Path::GetFileName(source_path)), 60.0f);
System::QueueTaskOnThread(
System::QueueAsyncTask(
[compress_mode, source_path = std::move(source_path), osd_key = std::move(osd_key)]() mutable {
Error error;
if (GPUDump::Recorder::Compress(source_path, compress_mode, &error))
Expand All @@ -2123,8 +2123,6 @@ void GPU::StopRecordingGPUDump()
error.GetDescription()),
Host::OSD_ERROR_DURATION);
}

System::RemoveSelfFromTaskThreads();
});
}

Expand Down
7 changes: 3 additions & 4 deletions src/core/gpu_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1494,12 +1494,11 @@ void GPUBackend::RenderScreenshotToFile(const std::string_view path, DisplayScre

if (compress_on_thread)
{
System::QueueTaskOnThread([width, height, path = std::move(path), fp = fp.release(), quality,
flip_y = g_gpu_device->UsesLowerLeftOrigin(), image = std::move(image),
osd_key = std::move(osd_key)]() mutable {
System::QueueAsyncTask([width, height, path = std::move(path), fp = fp.release(), quality,
flip_y = g_gpu_device->UsesLowerLeftOrigin(), image = std::move(image),
osd_key = std::move(osd_key)]() mutable {
CompressAndWriteTextureToFile(width, height, std::move(path), FileSystem::ManagedCFilePtr(fp), quality, true,
flip_y, std::move(image), std::move(osd_key));
System::RemoveSelfFromTaskThreads();
});
}
else
Expand Down
81 changes: 45 additions & 36 deletions src/core/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include "common/memmap.h"
#include "common/path.h"
#include "common/string_util.h"
#include "common/task_queue.h"
#include "common/timer.h"

#include "IconsEmoji.h"
Expand Down Expand Up @@ -114,6 +115,8 @@ SystemBootParameters::~SystemBootParameters() = default;

namespace System {

static constexpr u32 NUM_ASYNC_WORKER_THREADS = 2;

static constexpr float PRE_FRAME_SLEEP_UPDATE_INTERVAL = 1.0f;
static constexpr const char FALLBACK_EXE_NAME[] = "PSX.EXE";
static constexpr u32 MAX_SKIPPED_DUPLICATE_FRAME_COUNT = 2; // 20fps minimum
Expand Down Expand Up @@ -319,8 +322,11 @@ struct ALIGN_TO_CACHE_LINE StateVars
// Used to track play time. We use a monotonic timer here, in case of clock changes.
u64 session_start_time = 0;

std::deque<std::thread> task_threads;
std::mutex task_threads_mutex;
// internal async task counters
std::atomic_uint32_t outstanding_save_state_tasks{0};

// async task pool
TaskQueue async_task_queue;

#ifdef ENABLE_SOCKET_MULTIPLEXER
std::unique_ptr<SocketMultiplexer> socket_multiplexer;
Expand Down Expand Up @@ -511,6 +517,8 @@ bool System::CPUThreadInitialize(Error* error)

LogStartupInformation();

s_state.async_task_queue.SetWorkerCount(NUM_ASYNC_WORKER_THREADS);

GPUThread::Internal::ProcessStartup();

if (g_settings.achievements_enabled)
Expand All @@ -534,6 +542,8 @@ void System::CPUThreadShutdown()

InputManager::CloseSources();

s_state.async_task_queue.SetWorkerCount(0);

#ifdef _WIN32
CoUninitialize();
#endif
Expand Down Expand Up @@ -1932,8 +1942,6 @@ void System::DestroySystem()
if (s_state.state == State::Shutdown)
return;

JoinTaskThreads();

if (s_state.media_capture)
StopMediaCapture();

Expand Down Expand Up @@ -2820,6 +2828,8 @@ bool System::LoadState(const char* path, Error* error, bool save_undo_state)
return true;
}

FlushSaveStates();

Timer load_timer;

auto fp = FileSystem::OpenManagedCFile(path, "rb", error);
Expand Down Expand Up @@ -3143,8 +3153,12 @@ bool System::SaveState(std::string path, Error* error, bool backup_existing_save
Host::AddIconOSDMessage(osd_key, ICON_EMOJI_FLOPPY_DISK,
fmt::format(TRANSLATE_FS("System", "Saving state to '{}'."), Path::GetFileName(path)), 60.0f);

QueueTaskOnThread([path = std::move(path), buffer = std::move(buffer), osd_key = std::move(osd_key),
backup_existing_save, compression = g_settings.save_state_compression]() {
// ensure multiple saves to the same path do not overlap
FlushSaveStates();

s_state.outstanding_save_state_tasks.fetch_add(1, std::memory_order_acq_rel);
s_state.async_task_queue.SubmitTask([path = std::move(path), buffer = std::move(buffer), osd_key = std::move(osd_key),
backup_existing_save, compression = g_settings.save_state_compression]() {
INFO_LOG("Saving state to '{}'...", path);

Error lerror;
Expand Down Expand Up @@ -3175,6 +3189,13 @@ bool System::SaveState(std::string path, Error* error, bool backup_existing_save
}

VERBOSE_LOG("Saving state took {:.2f} msec", lsave_timer.GetTimeMilliseconds());

s_state.outstanding_save_state_tasks.fetch_sub(1, std::memory_order_acq_rel);

// don't display a resume state saved message in FSUI
if (!IsValid())
return;

if (result)
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_FLOPPY_DISK,
Expand All @@ -3188,13 +3209,17 @@ bool System::SaveState(std::string path, Error* error, bool backup_existing_save
Path::GetFileName(path), lerror.GetDescription()),
Host::OSD_ERROR_DURATION);
}

System::RemoveSelfFromTaskThreads();
});

return true;
}

void System::FlushSaveStates()
{
while (s_state.outstanding_save_state_tasks.load(std::memory_order_acquire) > 0)
WaitForAllAsyncTasks();
}

bool System::SaveStateToBuffer(SaveStateBuffer* buffer, Error* error, u32 screenshot_size /* = 256 */)
{
buffer->title = s_state.running_game_title;
Expand Down Expand Up @@ -5451,6 +5476,8 @@ std::vector<SaveStateInfo> System::GetAvailableSaveStates(std::string_view seria
si.push_back(SaveStateInfo{std::move(path), sd.ModificationTime, static_cast<s32>(slot), global});
};

FlushSaveStates();

if (!serial.empty())
{
add_path(GetGameSaveStateFileName(serial, -1), -1, false);
Expand All @@ -5469,6 +5496,8 @@ std::optional<SaveStateInfo> System::GetSaveStateInfo(std::string_view serial, s
const bool global = serial.empty();
std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(serial, slot);

FlushSaveStates();

FILESYSTEM_STAT_DATA sd;
if (!FileSystem::StatFile(path.c_str(), &sd))
return std::nullopt;
Expand All @@ -5480,6 +5509,8 @@ std::optional<ExtendedSaveStateInfo> System::GetExtendedSaveStateInfo(const char
{
std::optional<ExtendedSaveStateInfo> ssi;

FlushSaveStates();

Error error;
auto fp = FileSystem::OpenManagedCFile(path, "rb", &error);
if (fp)
Expand Down Expand Up @@ -5618,6 +5649,8 @@ std::string System::GetGameMemoryCardPath(std::string_view serial, std::string_v

std::string System::GetMostRecentResumeSaveStatePath()
{
FlushSaveStates();

std::vector<FILESYSTEM_FIND_DATA> files;
if (!FileSystem::FindFiles(EmuFolders::SaveStates.c_str(), "*resume.sav", FILESYSTEM_FIND_FILES, &files) ||
files.empty())
Expand Down Expand Up @@ -5832,38 +5865,14 @@ u64 System::GetSessionPlayedTime()
return static_cast<u64>(std::round(Timer::ConvertValueToSeconds(ctime - s_state.session_start_time)));
}

void System::QueueTaskOnThread(std::function<void()> task)
{
const std::unique_lock lock(s_state.task_threads_mutex);
s_state.task_threads.emplace_back(std::move(task));
}

void System::RemoveSelfFromTaskThreads()
void System::QueueAsyncTask(std::function<void()> function)
{
const auto this_id = std::this_thread::get_id();
const std::unique_lock lock(s_state.task_threads_mutex);
for (auto it = s_state.task_threads.begin(); it != s_state.task_threads.end(); ++it)
{
if (it->get_id() == this_id)
{
it->detach();
s_state.task_threads.erase(it);
break;
}
}
s_state.async_task_queue.SubmitTask(std::move(function));
}

void System::JoinTaskThreads()
void System::WaitForAllAsyncTasks()
{
std::unique_lock lock(s_state.task_threads_mutex);
while (!s_state.task_threads.empty())
{
std::thread save_thread(std::move(s_state.task_threads.front()));
s_state.task_threads.pop_front();
lock.unlock();
save_thread.join();
lock.lock();
}
s_state.async_task_queue.WaitForAll();
}

SocketMultiplexer* System::GetSocketMultiplexer()
Expand Down
9 changes: 8 additions & 1 deletion src/core/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ std::string GetCheatFileName();
/// Powers off the system, optionally saving the resume state.
void ShutdownSystem(bool save_resume_state);

/// Waits for all asynchronous state saves to complete.
void FlushSaveStates();

/// Returns true if an undo load state exists.
bool CanUndoLoadState();

Expand Down Expand Up @@ -421,7 +424,11 @@ void CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64* ram_us
void ClearMemorySaveStates(bool reallocate_resources, bool recycle_textures);
void SetRunaheadReplayFlag();

/// Shared socket multiplexer, used by PINE/GDB/etc.
/// Asynchronous work tasks, complete on worker thread.
void QueueAsyncTask(std::function<void()> function);
void WaitForAllAsyncTasks();

/// Shared socket multiplexer.
SocketMultiplexer* GetSocketMultiplexer();
void ReleaseSocketMultiplexer();

Expand Down
5 changes: 0 additions & 5 deletions src/core/system_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ const Threading::ThreadHandle& GetCPUThreadHandle();
/// Polls input, updates subsystems which are present while paused/inactive.
void IdlePollUpdate();

/// Task threads, asynchronous work which will block system shutdown.
void QueueTaskOnThread(std::function<void()> task);
void RemoveSelfFromTaskThreads();
void JoinTaskThreads();

} // namespace System

namespace Host {
Expand Down
2 changes: 2 additions & 0 deletions src/duckstation-qt/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,8 @@ std::shared_ptr<SystemBootParameters> MainWindow::getSystemBootParameters(std::s

std::optional<bool> MainWindow::promptForResumeState(const std::string& save_state_path)
{
System::FlushSaveStates();

FILESYSTEM_STAT_DATA sd;
if (save_state_path.empty() || !FileSystem::StatFile(save_state_path.c_str(), &sd))
return false;
Expand Down

0 comments on commit db14824

Please sign in to comment.