System: Add 'Task Threads'

Used for saving screenshots/GPU dumps.
This commit is contained in:
Stenzek 2024-10-28 02:38:06 +10:00
parent 0dc78e4c23
commit bfadd608fb
No known key found for this signature in database
4 changed files with 117 additions and 112 deletions

View File

@ -61,9 +61,6 @@ static TimingEvent s_frame_done_event(
"Frame Done", 1, 1, [](void* param, TickCount ticks, TickCount ticks_late) { g_gpu->FrameDoneEvent(ticks); },
nullptr);
static std::deque<std::thread> s_screenshot_threads;
static std::mutex s_screenshot_threads_mutex;
// #define PSX_GPU_STATS
#ifdef PSX_GPU_STATS
static u64 s_active_gpu_cycles = 0;
@ -75,9 +72,7 @@ static constexpr GPUTexture::Format DISPLAY_INTERNAL_POSTFX_FORMAT = GPUTexture:
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
u8 quality, bool clear_alpha, bool flip_y, std::vector<u32> texture_data,
u32 texture_data_stride, GPUTexture::Format texture_format,
bool display_osd_message, bool use_thread);
static void RemoveSelfFromScreenshotThreads();
static void JoinScreenshotThreads();
std::string osd_key);
GPU::GPU()
{
@ -92,7 +87,6 @@ GPU::~GPU()
s_frame_done_event.Deactivate();
StopRecordingGPUDump();
JoinScreenshotThreads();
DestroyDeinterlaceTextures();
g_gpu_device->RecycleTexture(std::move(m_chroma_smoothing_texture));
}
@ -2424,120 +2418,61 @@ void GPU::CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rota
bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
u8 quality, bool clear_alpha, bool flip_y, std::vector<u32> texture_data,
u32 texture_data_stride, GPUTexture::Format texture_format, bool display_osd_message,
bool use_thread)
u32 texture_data_stride, GPUTexture::Format texture_format, std::string osd_key)
{
std::string osd_key;
if (display_osd_message)
bool result;
const char* extension = std::strrchr(filename.c_str(), '.');
if (extension)
{
// Use a 60 second timeout to give it plenty of time to actually save.
osd_key = fmt::format("ScreenshotSaver_{}", filename);
Host::AddIconOSDMessage(osd_key, ICON_EMOJI_CAMERA_WITH_FLASH,
fmt::format(TRANSLATE_FS("GPU", "Saving screenshot to '{}'."), Path::GetFileName(filename)),
60.0f);
}
static constexpr auto proc = [](u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
u8 quality, bool clear_alpha, bool flip_y, std::vector<u32> texture_data,
u32 texture_data_stride, GPUTexture::Format texture_format, std::string osd_key,
bool use_thread) {
bool result;
const char* extension = std::strrchr(filename.c_str(), '.');
if (extension)
if (GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
{
if (GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
if (clear_alpha)
{
if (clear_alpha)
{
for (u32& pixel : texture_data)
pixel |= 0xFF000000u;
}
for (u32& pixel : texture_data)
pixel |= 0xFF000000u;
}
if (flip_y)
GPUTexture::FlipTextureDataRGBA8(width, height, reinterpret_cast<u8*>(texture_data.data()),
texture_data_stride);
if (flip_y)
GPUTexture::FlipTextureDataRGBA8(width, height, reinterpret_cast<u8*>(texture_data.data()),
texture_data_stride);
Assert(texture_data_stride == sizeof(u32) * width);
RGBA8Image image(width, height, std::move(texture_data));
if (image.SaveToFile(filename.c_str(), fp.get(), quality))
{
result = true;
}
else
{
ERROR_LOG("Unknown extension in filename '{}' or save error: '{}'", filename, extension);
result = false;
}
Assert(texture_data_stride == sizeof(u32) * width);
RGBA8Image image(width, height, std::move(texture_data));
if (image.SaveToFile(filename.c_str(), fp.get(), quality))
{
result = true;
}
else
{
ERROR_LOG("Unknown extension in filename '{}' or save error: '{}'", filename, extension);
result = false;
}
}
else
{
ERROR_LOG("Unable to determine file extension for '{}'", filename);
result = false;
}
if (!osd_key.empty())
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_CAMERA,
fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") :
TRANSLATE_FS("GPU", "Failed to save screenshot to '{}'."),
Path::GetFileName(filename),
result ? Host::OSD_INFO_DURATION : Host::OSD_ERROR_DURATION));
}
if (use_thread)
RemoveSelfFromScreenshotThreads();
return result;
};
if (!use_thread)
}
else
{
return proc(width, height, std::move(filename), std::move(fp), quality, clear_alpha, flip_y,
std::move(texture_data), texture_data_stride, texture_format, std::move(osd_key), use_thread);
ERROR_LOG("Unable to determine file extension for '{}'", filename);
result = false;
}
std::unique_lock lock(s_screenshot_threads_mutex);
std::thread thread(proc, width, height, std::move(filename), std::move(fp), quality, clear_alpha, flip_y,
std::move(texture_data), texture_data_stride, texture_format, std::move(osd_key), use_thread);
s_screenshot_threads.push_back(std::move(thread));
return true;
}
void RemoveSelfFromScreenshotThreads()
{
const auto this_id = std::this_thread::get_id();
std::unique_lock lock(s_screenshot_threads_mutex);
for (auto it = s_screenshot_threads.begin(); it != s_screenshot_threads.end(); ++it)
if (!osd_key.empty())
{
if (it->get_id() == this_id)
{
it->detach();
s_screenshot_threads.erase(it);
break;
}
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_CAMERA,
fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") :
TRANSLATE_FS("GPU", "Failed to save screenshot to '{}'."),
Path::GetFileName(filename),
result ? Host::OSD_INFO_DURATION : Host::OSD_ERROR_DURATION));
}
return result;
}
void JoinScreenshotThreads()
{
std::unique_lock lock(s_screenshot_threads_mutex);
while (!s_screenshot_threads.empty())
{
std::thread save_thread(std::move(s_screenshot_threads.front()));
s_screenshot_threads.pop_front();
lock.unlock();
save_thread.join();
lock.lock();
}
}
bool GPU::WriteDisplayTextureToFile(std::string filename, bool compress_on_thread /* = false */)
bool GPU::WriteDisplayTextureToFile(std::string filename)
{
if (!m_display_texture)
return false;
@ -2590,7 +2525,7 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool compress_on_threa
return CompressAndWriteTextureToFile(
read_width, read_height, std::move(filename), std::move(fp), g_settings.display_screenshot_quality, clear_alpha,
flip_y, std::move(texture_data), texture_data_stride, m_display_texture->GetFormat(), false, compress_on_thread);
flip_y, std::move(texture_data), texture_data_stride, m_display_texture->GetFormat(), std::string());
}
bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
@ -2703,7 +2638,7 @@ void GPU::CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* h
}
}
bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
bool show_osd_message)
{
u32 width, height;
@ -2725,16 +2660,41 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod
}
Error error;
auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb", &error);
auto fp = FileSystem::OpenManagedCFile(path.c_str(), "wb", &error);
if (!fp)
{
ERROR_LOG("Can't open file '{}': {}", Path::GetFileName(filename), error.GetDescription());
ERROR_LOG("Can't open file '{}': {}", Path::GetFileName(path), error.GetDescription());
return false;
}
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), quality, true,
g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride,
pixels_format, show_osd_message, compress_on_thread);
std::string osd_key;
if (show_osd_message)
{
// Use a 60 second timeout to give it plenty of time to actually save.
osd_key = fmt::format("ScreenshotSaver_{}", path);
Host::AddIconOSDMessage(osd_key, ICON_EMOJI_CAMERA_WITH_FLASH,
fmt::format(TRANSLATE_FS("GPU", "Saving screenshot to '{}'."), Path::GetFileName(path)),
60.0f);
}
if (compress_on_thread)
{
System::QueueTaskOnThread([width, height, path = std::move(path), fp = fp.release(), quality,
flip_y = g_gpu_device->UsesLowerLeftOrigin(), pixels = std::move(pixels), pixels_stride,
pixels_format, osd_key = std::move(osd_key)]() mutable {
CompressAndWriteTextureToFile(width, height, std::move(path), FileSystem::ManagedCFilePtr(fp), quality, true,
flip_y, std::move(pixels), pixels_stride, pixels_format, std::move(osd_key));
System::RemoveSelfFromTaskThreads();
});
return true;
}
else
{
return CompressAndWriteTextureToFile(width, height, std::move(path), std::move(fp), quality, true,
g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride,
pixels_format, std::move(osd_key));
}
}
bool GPU::DumpVRAMToFile(const char* filename)
@ -2988,8 +2948,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);
std::unique_lock screenshot_lock(s_screenshot_threads_mutex);
s_screenshot_threads.emplace_back(
System::QueueTaskOnThread(
[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))
@ -3010,7 +2969,7 @@ void GPU::StopRecordingGPUDump()
Host::OSD_ERROR_DURATION);
}
RemoveSelfFromScreenshotThreads();
System::RemoveSelfFromTaskThreads();
});
}

View File

@ -18,8 +18,8 @@
#include <array>
#include <deque>
#include <memory>
#include <string>
#include <span>
#include <string>
#include <tuple>
#include <vector>
@ -37,7 +37,7 @@ namespace GPUDump {
enum class PacketType : u8;
class Recorder;
class Player;
}
} // namespace GPUDump
struct Settings;
namespace Threading {
@ -229,7 +229,7 @@ public:
GSVector4i* draw_rect) const;
/// Helper function to save current display texture to PNG.
bool WriteDisplayTextureToFile(std::string filename, bool compress_on_thread = false);
bool WriteDisplayTextureToFile(std::string path);
/// Renders the display, optionally with postprocessing to the specified image.
bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
@ -237,7 +237,7 @@ public:
GPUTexture::Format* out_format);
/// Helper function to save screenshot to PNG.
bool RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
bool RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
bool show_osd_message);
/// Draws the current display texture, with any post-processing.

View File

@ -165,6 +165,7 @@ static bool SetBootMode(BootMode new_boot_mode, DiscRegion disc_region, Error* e
static void InternalReset();
static void ClearRunningGame();
static void DestroySystem();
static void JoinTaskThreads();
static bool CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen, Error* error);
static bool RecreateGPU(GPURenderer renderer, bool force_recreate_device = false, bool update_display = true);
@ -320,6 +321,9 @@ 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;
#ifdef ENABLE_SOCKET_MULTIPLEXER
std::unique_ptr<SocketMultiplexer> socket_multiplexer;
#endif
@ -1936,6 +1940,8 @@ void System::DestroySystem()
if (s_state.state == State::Shutdown)
return;
JoinTaskThreads();
if (s_state.media_capture)
StopMediaCapture();
@ -5779,6 +5785,40 @@ u64 System::GetSessionPlayedTime()
return static_cast<u64>(std::round(Common::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()
{
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;
}
}
}
void System::JoinTaskThreads()
{
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();
}
}
SocketMultiplexer* System::GetSocketMultiplexer()
{
#ifdef ENABLE_SOCKET_MULTIPLEXER

View File

@ -5,6 +5,8 @@
#include "system.h"
#include <functional>
namespace System {
/// Memory save states - only for internal use.
@ -55,6 +57,10 @@ 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();
} // namespace System
namespace Host {