GPU: Ensure screenshots are saved before shutdown
This commit is contained in:
parent
12a24b9fae
commit
0373105ba7
174
src/core/gpu.cpp
174
src/core/gpu.cpp
|
@ -22,9 +22,13 @@
|
||||||
#include "common/file_system.h"
|
#include "common/file_system.h"
|
||||||
#include "common/heap_array.h"
|
#include "common/heap_array.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
#include "common/path.h"
|
||||||
#include "common/small_string.h"
|
#include "common/small_string.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
|
|
||||||
|
#include "IconsFontAwesome5.h"
|
||||||
|
#include "fmt/format.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
@ -35,6 +39,15 @@ alignas(HOST_PAGE_SIZE) u16 g_vram[VRAM_SIZE / sizeof(u16)];
|
||||||
|
|
||||||
const GPU::GP0CommandHandlerTable GPU::s_GP0_command_handler_table = GPU::GenerateGP0CommandHandlerTable();
|
const GPU::GP0CommandHandlerTable GPU::s_GP0_command_handler_table = GPU::GenerateGP0CommandHandlerTable();
|
||||||
|
|
||||||
|
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 JoinScreenshotThreads();
|
||||||
|
|
||||||
|
static std::deque<std::thread> s_screenshot_threads;
|
||||||
|
static std::mutex s_screenshot_threads_mutex;
|
||||||
|
|
||||||
GPU::GPU()
|
GPU::GPU()
|
||||||
{
|
{
|
||||||
ResetStatistics();
|
ResetStatistics();
|
||||||
|
@ -42,6 +55,8 @@ GPU::GPU()
|
||||||
|
|
||||||
GPU::~GPU()
|
GPU::~GPU()
|
||||||
{
|
{
|
||||||
|
JoinScreenshotThreads();
|
||||||
|
|
||||||
if (g_gpu_device)
|
if (g_gpu_device)
|
||||||
g_gpu_device->SetGPUTimingEnabled(false);
|
g_gpu_device->SetGPUTimingEnabled(false);
|
||||||
}
|
}
|
||||||
|
@ -1907,41 +1922,119 @@ Common::Rectangle<s32> GPU::CalculateDrawRect(s32 window_width, s32 window_heigh
|
||||||
static_cast<s32>(draw_rc.GetWidth()), static_cast<s32>(draw_rc.GetHeight()));
|
static_cast<s32>(draw_rc.GetWidth()), static_cast<s32>(draw_rc.GetHeight()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
|
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,
|
u8 quality, bool clear_alpha, bool flip_y, std::vector<u32> texture_data,
|
||||||
u32 texture_data_stride, GPUTexture::Format texture_format)
|
u32 texture_data_stride, GPUTexture::Format texture_format, bool display_osd_message,
|
||||||
|
bool use_thread)
|
||||||
{
|
{
|
||||||
|
std::string osd_key;
|
||||||
const char* extension = std::strrchr(filename.c_str(), '.');
|
if (display_osd_message)
|
||||||
if (!extension)
|
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("Unable to determine file extension for '%s'", filename.c_str());
|
// Use a 60 second timeout to give it plenty of time to actually save.
|
||||||
return false;
|
osd_key = fmt::format("ScreenshotSaver_{}", filename);
|
||||||
|
Host::AddIconOSDMessage(osd_key, ICON_FA_CAMERA,
|
||||||
|
fmt::format(TRANSLATE_FS("GPU", "Saving screenshot to '{}'."), Path::GetFileName(filename)),
|
||||||
|
60.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
|
static constexpr auto proc = [](u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
|
||||||
return false;
|
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;
|
||||||
|
|
||||||
if (clear_alpha)
|
const char* extension = std::strrchr(filename.c_str(), '.');
|
||||||
|
if (extension)
|
||||||
|
{
|
||||||
|
if (GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
|
||||||
|
{
|
||||||
|
if (clear_alpha)
|
||||||
|
{
|
||||||
|
for (u32& pixel : texture_data)
|
||||||
|
pixel |= 0xFF000000u;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Unknown extension in filename '%s' or save error: '%s'", filename.c_str(), extension);
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Unable to determine file extension for '%s'", filename.c_str());
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!osd_key.empty())
|
||||||
|
{
|
||||||
|
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_CAMERA,
|
||||||
|
fmt::format(result ? TRANSLATE_FS("GS", "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)
|
||||||
|
{
|
||||||
|
// remove ourselves from the list, if the GS thread is waiting for us, we won't be in there
|
||||||
|
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 (it->get_id() == this_id)
|
||||||
|
{
|
||||||
|
it->detach();
|
||||||
|
s_screenshot_threads.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!use_thread)
|
||||||
{
|
{
|
||||||
for (u32& pixel : texture_data)
|
return proc(width, height, std::move(filename), std::move(fp), quality, clear_alpha, flip_y,
|
||||||
pixel |= 0xFF000000u;
|
std::move(texture_data), texture_data_stride, texture_format, std::move(osd_key), use_thread);
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
Log_ErrorPrintf("Unknown extension in filename '%s' or save error: '%s'", filename.c_str(), extension);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
std::unique_lock lock(s_screenshot_threads_mutex);
|
||||||
|
s_screenshot_threads.push_back(std::move(thread));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, bool compress_on_thread /* = false */)
|
||||||
{
|
{
|
||||||
if (!m_display_texture)
|
if (!m_display_texture)
|
||||||
|
@ -1992,18 +2085,9 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool compress_on_threa
|
||||||
constexpr bool clear_alpha = true;
|
constexpr bool clear_alpha = true;
|
||||||
const bool flip_y = g_gpu_device->UsesLowerLeftOrigin();
|
const bool flip_y = g_gpu_device->UsesLowerLeftOrigin();
|
||||||
|
|
||||||
if (!compress_on_thread)
|
return CompressAndWriteTextureToFile(
|
||||||
{
|
read_width, read_height, std::move(filename), std::move(fp), g_settings.display_screenshot_quality, clear_alpha,
|
||||||
return CompressAndWriteTextureToFile(read_width, read_height, std::move(filename), std::move(fp),
|
flip_y, std::move(texture_data), texture_data_stride, m_display_texture->GetFormat(), false, compress_on_thread);
|
||||||
g_settings.display_screenshot_quality, clear_alpha, flip_y,
|
|
||||||
std::move(texture_data), texture_data_stride, m_display_texture->GetFormat());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread compress_thread(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());
|
|
||||||
compress_thread.detach();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, bool postfx,
|
bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangle<s32>& draw_rect, bool postfx,
|
||||||
|
@ -2053,7 +2137,8 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangl
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread)
|
bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
|
||||||
|
bool show_osd_message)
|
||||||
{
|
{
|
||||||
u32 width = g_gpu_device->GetWindowWidth();
|
u32 width = g_gpu_device->GetWindowWidth();
|
||||||
u32 height = g_gpu_device->GetWindowHeight();
|
u32 height = g_gpu_device->GetWindowHeight();
|
||||||
|
@ -2131,18 +2216,9 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compress_on_thread)
|
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), quality, true,
|
||||||
{
|
g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride,
|
||||||
return CompressAndWriteTextureToFile(width, height, std::move(filename), std::move(fp), quality, true,
|
pixels_format, show_osd_message, compress_on_thread);
|
||||||
g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride,
|
|
||||||
pixels_format);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread compress_thread(CompressAndWriteTextureToFile, width, height, std::move(filename), std::move(fp), quality,
|
|
||||||
true, g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride,
|
|
||||||
pixels_format);
|
|
||||||
compress_thread.detach();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GPU::DumpVRAMToFile(const char* filename)
|
bool GPU::DumpVRAMToFile(const char* filename)
|
||||||
|
|
|
@ -208,7 +208,8 @@ public:
|
||||||
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format);
|
std::vector<u32>* out_pixels, u32* out_stride, GPUTexture::Format* out_format);
|
||||||
|
|
||||||
/// Helper function to save screenshot to PNG.
|
/// Helper function to save screenshot to PNG.
|
||||||
bool RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread);
|
bool RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
|
||||||
|
bool show_osd_message);
|
||||||
|
|
||||||
/// Draws the current display texture, with any post-processing.
|
/// Draws the current display texture, with any post-processing.
|
||||||
bool PresentDisplay();
|
bool PresentDisplay();
|
||||||
|
|
|
@ -4364,20 +4364,7 @@ bool System::SaveScreenshot(const char* filename, DisplayScreenshotMode mode, Di
|
||||||
filename = auto_filename.c_str();
|
filename = auto_filename.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FileSystem::FileExists(filename))
|
return g_gpu->RenderScreenshotToFile(filename, mode, quality, compress_on_thread, true);
|
||||||
{
|
|
||||||
Host::AddFormattedOSDMessage(10.0f, TRANSLATE("OSDMessage", "Screenshot file '%s' already exists."), filename);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_gpu->RenderScreenshotToFile(filename, mode, quality, compress_on_thread))
|
|
||||||
{
|
|
||||||
Host::AddFormattedOSDMessage(10.0f, TRANSLATE("OSDMessage", "Failed to save screenshot to '%s'"), filename);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Host::AddFormattedOSDMessage(5.0f, TRANSLATE("OSDMessage", "Screenshot saved to '%s'."), filename);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string System::GetGameSaveStateFileName(const std::string_view& serial, s32 slot)
|
std::string System::GetGameSaveStateFileName(const std::string_view& serial, s32 slot)
|
||||||
|
|
Loading…
Reference in New Issue