mirror of https://github.com/PCSX2/pcsx2.git
GS: Refactor screenshots/GS dumping triggers
i.e. make it not rubbish and a massive race condition. Also adds GS dump saving to Qt.
This commit is contained in:
parent
7bd7cdd867
commit
8c270288de
|
@ -611,6 +611,22 @@ void EmuThread::runOnCPUThread(const std::function<void()>& func)
|
|||
func();
|
||||
}
|
||||
|
||||
void EmuThread::queueSnapshot(quint32 gsdump_frames)
|
||||
{
|
||||
if (!isOnEmuThread())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "queueSnapshot", Qt::QueuedConnection, Q_ARG(quint32, gsdump_frames));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VMManager::HasValidVM())
|
||||
return;
|
||||
|
||||
GetMTGS().RunOnGSThread([gsdump_frames]() {
|
||||
GSQueueSnapshot(std::string(), gsdump_frames);
|
||||
});
|
||||
}
|
||||
|
||||
void EmuThread::updateDisplay()
|
||||
{
|
||||
pxAssertRel(!isOnEmuThread(), "Not on emu thread");
|
||||
|
@ -893,13 +909,6 @@ SysMtgsThread& GetMTGS()
|
|||
// ------------------------------------------------------------------------
|
||||
|
||||
BEGIN_HOTKEY_LIST(g_host_hotkeys)
|
||||
DEFINE_HOTKEY("Screenshot", "General", "Save Screenshot", [](bool pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
Host::AddOSDMessage("Saved Screenshot.", 10.0f);
|
||||
GSmakeSnapshot(EmuFolders::Snapshots.ToString().char_str());
|
||||
}
|
||||
})
|
||||
DEFINE_HOTKEY("ShutdownVM", "System", "Shut Down Virtual Machine", [](bool pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
|
|
|
@ -79,6 +79,7 @@ public Q_SLOTS:
|
|||
void enumerateInputDevices();
|
||||
void enumerateVibrationMotors();
|
||||
void runOnCPUThread(const std::function<void()>& func);
|
||||
void queueSnapshot(quint32 gsdump_frames);
|
||||
|
||||
Q_SIGNALS:
|
||||
DisplayWidget* onCreateDisplayRequested(bool fullscreen, bool render_to_main);
|
||||
|
|
|
@ -45,8 +45,6 @@
|
|||
#include "Settings/InterfaceSettingsWidget.h"
|
||||
#include "SettingWidgetBinder.h"
|
||||
|
||||
extern u32 GSmakeSnapshot(char* path);
|
||||
|
||||
static constexpr char DISC_IMAGE_FILTER[] =
|
||||
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.gz *.elf *.irx *.m3u *.gs *.gs.xz);;"
|
||||
"Single-Track Raw Images (*.bin *.iso);;"
|
||||
|
@ -212,6 +210,8 @@ void MainWindow::connectSignals()
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableIOPConsoleLogging, "Logging", "EnableIOPConsole", true);
|
||||
connect(m_ui.actionEnableIOPConsoleLogging, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged);
|
||||
|
||||
connect(m_ui.actionSaveGSDump, &QAction::triggered, this, &MainWindow::onSaveGSDumpActionTriggered);
|
||||
|
||||
// These need to be queued connections to stop crashing due to menus opening/closing and switching focus.
|
||||
connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress);
|
||||
connect(m_game_list_widget, &GameListWidget::refreshComplete, this, &MainWindow::onGameListRefreshComplete);
|
||||
|
@ -514,8 +514,12 @@ void MainWindow::setIconThemeFromSettings()
|
|||
|
||||
void MainWindow::onScreenshotActionTriggered()
|
||||
{
|
||||
Host::AddOSDMessage("Saved Screenshot.", 10.0f);
|
||||
GSmakeSnapshot(EmuFolders::Snapshots.ToString().char_str());
|
||||
g_emu_thread->queueSnapshot(0);
|
||||
}
|
||||
|
||||
void MainWindow::onSaveGSDumpActionTriggered()
|
||||
{
|
||||
g_emu_thread->queueSnapshot(1);
|
||||
}
|
||||
|
||||
void MainWindow::saveStateToConfig()
|
||||
|
|
|
@ -134,6 +134,7 @@ private Q_SLOTS:
|
|||
void onThemeChangedFromSettings();
|
||||
void onLoggingOptionChanged();
|
||||
void onScreenshotActionTriggered();
|
||||
void onSaveGSDumpActionTriggered();
|
||||
|
||||
void onVMStarting();
|
||||
void onVMStarted();
|
||||
|
|
|
@ -136,8 +136,9 @@
|
|||
</property>
|
||||
</widget>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionToggleSoftwareRendering"/>
|
||||
<addaction name="menuDebugSwitchRenderer"/>
|
||||
<addaction name="actionToggleSoftwareRendering"/>
|
||||
<addaction name="actionSaveGSDump"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionReloadPatches"/>
|
||||
<addaction name="separator"/>
|
||||
|
@ -561,12 +562,13 @@
|
|||
</property>
|
||||
</action>
|
||||
<action name="actionDEV9Settings">
|
||||
<property name="icon">
|
||||
<iconset theme="dashboard-line">
|
||||
<normaloff>.</normaloff>.</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Network && HDD</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="dashboard-line"/>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewToolbar">
|
||||
<property name="checkable">
|
||||
|
@ -738,6 +740,11 @@
|
|||
<string>Enable IOP Console Logging</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSaveGSDump">
|
||||
<property name="text">
|
||||
<string>Save Single Frame GS Dump</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
|
|
|
@ -208,6 +208,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useDebugDevice, "EmuCore/GS", "UseDebugDevice", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.overrideTextureBarriers, "EmuCore/GS", "OverrideTextureBarriers", -1, -1);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.overrideGeometryShader, "EmuCore/GS", "OverrideGeometryShaders", -1, -1);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.gsDumpCompression, "EmuCore/GS", "GSDumpCompression", static_cast<int>(GSDumpCompressionMethod::Uncompressed));
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableFramebufferFetch, "EmuCore/GS", "DisableFramebufferFetch", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableDualSource, "EmuCore/GS", "DisableDualSourceBlend", false);
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
<string>Auto Standard (4:3/3:2 Progressive)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Standard (4:3)</string>
|
||||
</property>
|
||||
|
@ -123,7 +123,7 @@
|
|||
<string>Off (Default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Auto Standard (4:3/3:2 Progressive)</string>
|
||||
</property>
|
||||
|
@ -1146,7 +1146,7 @@
|
|||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_7">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="useBlitSwapChain">
|
||||
|
@ -1178,6 +1178,27 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_34">
|
||||
<property name="text">
|
||||
<string>GS Dump Compression:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="gsDumpCompression">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Uncompressed</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>LZMA (XZ)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -192,6 +192,12 @@ enum class TexturePreloadingLevel : u8
|
|||
Full,
|
||||
};
|
||||
|
||||
enum class GSDumpCompressionMethod : u8
|
||||
{
|
||||
Uncompressed,
|
||||
LZMA
|
||||
};
|
||||
|
||||
// Template function for casting enumerations to their underlying type
|
||||
template <typename Enumeration>
|
||||
typename std::underlying_type<Enumeration>::type enum_cast(Enumeration E)
|
||||
|
@ -519,6 +525,7 @@ struct Pcsx2Config
|
|||
CRCHackLevel CRCHack{CRCHackLevel::Automatic};
|
||||
BiFiltering TextureFiltering{BiFiltering::PS2};
|
||||
TexturePreloadingLevel TexturePreloading{TexturePreloadingLevel::Off};
|
||||
GSDumpCompressionMethod GSDumpCompression{GSDumpCompressionMethod::Uncompressed};
|
||||
int Dithering{2};
|
||||
int MaxAnisotropy{0};
|
||||
int SWExtraThreads{2};
|
||||
|
|
|
@ -480,35 +480,6 @@ void GSvsync(u32 field, bool registers_written)
|
|||
}
|
||||
}
|
||||
|
||||
u32 GSmakeSnapshot(char* path)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::string s{path};
|
||||
|
||||
if (!s.empty())
|
||||
{
|
||||
// Allows for providing a complete path
|
||||
std::string extension = s.substr(s.size() - 4, 4);
|
||||
#ifdef _WIN32
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), (char(_cdecl*)(int))tolower);
|
||||
#else
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), tolower);
|
||||
#endif
|
||||
if (extension == ".png")
|
||||
return g_gs_renderer->MakeSnapshot(s);
|
||||
else if (s[s.length() - 1] != DIRECTORY_SEPARATOR)
|
||||
s = s + DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
return g_gs_renderer->MakeSnapshot(s + "gs");
|
||||
}
|
||||
catch (GSRecoverableError)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int GSfreeze(FreezeAction mode, freezeData* data)
|
||||
{
|
||||
try
|
||||
|
@ -533,6 +504,18 @@ int GSfreeze(FreezeAction mode, freezeData* data)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void GSQueueSnapshot(const std::string& path, u32 gsdump_frames)
|
||||
{
|
||||
if (g_gs_renderer)
|
||||
g_gs_renderer->QueueSnapshot(path, gsdump_frames);
|
||||
}
|
||||
|
||||
void GSStopGSDump()
|
||||
{
|
||||
if (g_gs_renderer)
|
||||
g_gs_renderer->StopGSDump();
|
||||
}
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
|
||||
void GSkeyEvent(const HostKeyEvent& e)
|
||||
|
@ -577,9 +560,7 @@ int GStest()
|
|||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void pt(const char* str)
|
||||
static void pt(const char* str)
|
||||
{
|
||||
struct tm* current;
|
||||
time_t now;
|
||||
|
@ -623,6 +604,7 @@ void GSendRecording()
|
|||
g_gs_renderer->EndCapture();
|
||||
pt(" - Capture ended\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
void GSsetGameCRC(u32 crc, int options)
|
||||
{
|
||||
|
@ -1293,6 +1275,9 @@ void GSApp::Init()
|
|||
m_gs_tv_shaders.push_back(GSSetting(3, "Triangular filter", ""));
|
||||
m_gs_tv_shaders.push_back(GSSetting(4, "Wave filter", ""));
|
||||
|
||||
m_gs_dump_compression.push_back(GSSetting(static_cast<u32>(GSDumpCompressionMethod::Uncompressed), "Uncompressed", ""));
|
||||
m_gs_dump_compression.push_back(GSSetting(static_cast<u32>(GSDumpCompressionMethod::LZMA), "LZMA (XZ)", ""));
|
||||
|
||||
// clang-format off
|
||||
// Avoid to clutter the ini file with useless options
|
||||
#if defined(ENABLE_VULKAN) || defined(_WIN32)
|
||||
|
@ -1333,6 +1318,7 @@ void GSApp::Init()
|
|||
m_default_configuration["filter"] = std::to_string(static_cast<s8>(BiFiltering::PS2));
|
||||
m_default_configuration["FMVSoftwareRendererSwitch"] = "0";
|
||||
m_default_configuration["fxaa"] = "0";
|
||||
m_default_configuration["GSDumpCompression"] = "0";
|
||||
m_default_configuration["HWDisableReadbacks"] = "0";
|
||||
m_default_configuration["pcrtc_offsets"] = "0";
|
||||
m_default_configuration["IntegerScaling"] = "0";
|
||||
|
@ -1581,8 +1567,32 @@ static void HotkeyAdjustZoom(double delta)
|
|||
GetMTGS().RunOnGSThread([new_zoom]() { GSConfig.Zoom = new_zoom; });
|
||||
}
|
||||
|
||||
BEGIN_HOTKEY_LIST(g_gs_hotkeys){
|
||||
"ToggleSoftwareRendering", "Graphics", "Toggle Software Rendering", [](bool pressed) {
|
||||
BEGIN_HOTKEY_LIST(g_gs_hotkeys)
|
||||
{"Screenshot", "Graphics", "Save Screenshot", [](bool pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
GetMTGS().RunOnGSThread([]() {
|
||||
GSQueueSnapshot(std::string(), 0);
|
||||
});
|
||||
}
|
||||
}},
|
||||
{"GSDumpSingleFrame", "Graphics", "Save Single Frame GS Dump", [](bool pressed) {
|
||||
if (!pressed)
|
||||
{
|
||||
GetMTGS().RunOnGSThread([]() {
|
||||
GSQueueSnapshot(std::string(), 1);
|
||||
});
|
||||
}
|
||||
}},
|
||||
{"GSDumpMultiFrame", "Graphics", "Save Multi Frame GS Dump", [](bool pressed) {
|
||||
GetMTGS().RunOnGSThread([pressed]() {
|
||||
if (pressed)
|
||||
GSQueueSnapshot(std::string(), std::numeric_limits<u32>::max());
|
||||
else
|
||||
GSStopGSDump();
|
||||
});
|
||||
}},
|
||||
{"ToggleSoftwareRendering", "Graphics", "Toggle Software Rendering", [](bool pressed) {
|
||||
if (!pressed)
|
||||
GetMTGS().ToggleSoftwareRendering();
|
||||
}},
|
||||
|
|
|
@ -66,15 +66,16 @@ void GSgifTransfer1(u8* mem, u32 addr);
|
|||
void GSgifTransfer2(u8* mem, u32 size);
|
||||
void GSgifTransfer3(u8* mem, u32 size);
|
||||
void GSvsync(u32 field, bool registers_written);
|
||||
u32 GSmakeSnapshot(char* path);
|
||||
int GSfreeze(FreezeAction mode, freezeData* data);
|
||||
void GSQueueSnapshot(const std::string& path, u32 gsdump_frames = 0);
|
||||
void GSStopGSDump();
|
||||
#ifndef PCSX2_CORE
|
||||
void GSkeyEvent(const HostKeyEvent& e);
|
||||
void GSconfigure();
|
||||
int GStest();
|
||||
#endif
|
||||
bool GSsetupRecording(std::string& filename);
|
||||
void GSendRecording();
|
||||
#endif
|
||||
void GSsetGameCRC(u32 crc, int options);
|
||||
void GSsetFrameSkip(int frameskip);
|
||||
|
||||
|
@ -139,6 +140,7 @@ public:
|
|||
std::vector<GSSetting> m_gs_crc_level;
|
||||
std::vector<GSSetting> m_gs_acc_blend_level;
|
||||
std::vector<GSSetting> m_gs_tv_shaders;
|
||||
std::vector<GSSetting> m_gs_dump_compression;
|
||||
};
|
||||
|
||||
struct GSError
|
||||
|
|
|
@ -17,14 +17,17 @@
|
|||
#include "GSDump.h"
|
||||
#include "GSExtra.h"
|
||||
#include "GSState.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/FileSystem.h"
|
||||
|
||||
GSDumpBase::GSDumpBase(const std::string& fn)
|
||||
: m_frames(0)
|
||||
GSDumpBase::GSDumpBase(std::string fn)
|
||||
: m_filename(std::move(fn))
|
||||
, m_frames(0)
|
||||
, m_extra_frames(2)
|
||||
{
|
||||
m_gs = px_fopen(fn, "wb");
|
||||
m_gs = FileSystem::OpenCFile(m_filename.c_str(), "wb");
|
||||
if (!m_gs)
|
||||
fprintf(stderr, "GSDump: Error failed to open %s\n", fn.c_str());
|
||||
Console.Error("GSDump: Error failed to open %s", m_filename.c_str());
|
||||
}
|
||||
|
||||
GSDumpBase::~GSDumpBase()
|
||||
|
|
|
@ -56,9 +56,10 @@ struct GSDumpHeader
|
|||
|
||||
class GSDumpBase
|
||||
{
|
||||
FILE* m_gs;
|
||||
std::string m_filename;
|
||||
int m_frames;
|
||||
int m_extra_frames;
|
||||
FILE* m_gs;
|
||||
|
||||
protected:
|
||||
void AddHeader(const std::string& serial, u32 crc,
|
||||
|
@ -70,9 +71,11 @@ protected:
|
|||
virtual void AppendRawData(u8 c) = 0;
|
||||
|
||||
public:
|
||||
GSDumpBase(const std::string& fn);
|
||||
GSDumpBase(std::string fn);
|
||||
virtual ~GSDumpBase();
|
||||
|
||||
__fi const std::string& GetPath() const { return m_filename; }
|
||||
|
||||
void ReadFIFO(u32 size);
|
||||
void Transfer(int index, const u8* mem, size_t size);
|
||||
bool VSync(int field, bool last, const GSPrivRegSet* regs);
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
#include "PerformanceMetrics.h"
|
||||
#include "pcsx2/Config.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "fmt/core.h"
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
#include "gui/AppCoreThread.h"
|
||||
|
@ -54,17 +56,9 @@ static std::string GetDumpSerial()
|
|||
|
||||
std::unique_ptr<GSRenderer> g_gs_renderer;
|
||||
|
||||
GSRenderer::GSRenderer()
|
||||
: m_shift_key(false)
|
||||
, m_control_key(false)
|
||||
, m_texture_shuffle(false)
|
||||
, m_real_size(0, 0)
|
||||
{
|
||||
}
|
||||
GSRenderer::GSRenderer() = default;
|
||||
|
||||
GSRenderer::~GSRenderer()
|
||||
{
|
||||
}
|
||||
GSRenderer::~GSRenderer() = default;
|
||||
|
||||
void GSRenderer::Destroy()
|
||||
{
|
||||
|
@ -541,10 +535,14 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
g_gs_device->RestoreAPIState();
|
||||
|
||||
// snapshot
|
||||
// wx is dumb and call this from the UI thread...
|
||||
#ifndef PCSX2_CORE
|
||||
std::unique_lock snapshot_lock(m_snapshot_mutex);
|
||||
#endif
|
||||
|
||||
if (!m_snapshot.empty())
|
||||
{
|
||||
if (!m_dump && m_shift_key)
|
||||
if (!m_dump && m_dump_frames > 0)
|
||||
{
|
||||
freezeData fd = {0, nullptr};
|
||||
Freeze(&fd, true);
|
||||
|
@ -557,12 +555,14 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
std::vector<u32> screenshot_pixels;
|
||||
SaveSnapshotToMemory(DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT, &screenshot_pixels);
|
||||
|
||||
if (m_control_key)
|
||||
std::string_view compression_str;
|
||||
if (GSConfig.GSDumpCompression == GSDumpCompressionMethod::Uncompressed)
|
||||
{
|
||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpUncompressed(m_snapshot, GetDumpSerial(), m_crc,
|
||||
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||
fd, m_regs));
|
||||
compression_str = "with no compression";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -570,26 +570,49 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||
fd, m_regs));
|
||||
compression_str = "with LZMA compression";
|
||||
}
|
||||
|
||||
delete[] fd.data;
|
||||
|
||||
Host::AddKeyedOSDMessage("GSDump", fmt::format("Saving {0} GS dump {1} to '{2}'",
|
||||
(m_dump_frames == 1) ? "single frame" : "multi-frame", compression_str,
|
||||
FileSystem::GetFileNameFromPath(m_dump->GetPath())), 10.0f);
|
||||
}
|
||||
|
||||
if (GSTexture* t = g_gs_device->GetCurrent())
|
||||
{
|
||||
t->Save(m_snapshot + ".png");
|
||||
const std::string path(m_snapshot + ".png");
|
||||
const std::string_view filename(FileSystem::GetFileNameFromPath(path));
|
||||
if (t->Save(path))
|
||||
{
|
||||
Host::AddKeyedOSDMessage("GSScreenshot",
|
||||
fmt::format("Screenshot saved to '{}'.", FileSystem::GetFileNameFromPath(path)), 10.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddFormattedOSDMessage(10.0f, "Failed to save screenshot to '%s'.", path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
m_snapshot.clear();
|
||||
m_snapshot = {};
|
||||
}
|
||||
else if (m_dump)
|
||||
{
|
||||
if (m_dump->VSync(field, !m_control_key, m_regs))
|
||||
const bool last = (m_dump_frames == 0);
|
||||
if (m_dump->VSync(field, last, m_regs))
|
||||
{
|
||||
Host::AddKeyedOSDMessage("GSDump", fmt::format("Saved GS dump to '{}'.", FileSystem::GetFileNameFromPath(m_dump->GetPath())), 10.0f);
|
||||
m_dump.reset();
|
||||
}
|
||||
else if (!last)
|
||||
{
|
||||
m_dump_frames--;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
// capture
|
||||
|
||||
if (m_capture.IsCapturing())
|
||||
{
|
||||
if (GSTexture* current = g_gs_device->GetCurrent())
|
||||
|
@ -610,55 +633,74 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GSRenderer::MakeSnapshot(const std::string& path)
|
||||
void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
|
||||
{
|
||||
if (m_snapshot.empty())
|
||||
if (!m_snapshot.empty())
|
||||
return;
|
||||
|
||||
// Allows for providing a complete path
|
||||
if (path.size() > 4 && StringUtil::compareNoCase(path.substr(path.size() - 4, 4), ".png"))
|
||||
{
|
||||
// Allows for providing a complete path
|
||||
if (path.substr(path.size() - 4, 4) == ".png")
|
||||
m_snapshot = path.substr(0, path.size() - 4);
|
||||
else
|
||||
m_snapshot = path.substr(0, path.size() - 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
time_t cur_time = time(nullptr);
|
||||
static time_t prev_snap;
|
||||
// The variable 'n' is used for labelling the screenshots when multiple screenshots are taken in
|
||||
// a single second, we'll start using this variable for naming when a second screenshot request is detected
|
||||
// at the same time as the first one. Hence, we're initially setting this counter to 2 to imply that
|
||||
// the captured image is the 2nd image captured at this specific time.
|
||||
static int n = 2;
|
||||
char local_time[16];
|
||||
|
||||
if (strftime(local_time, sizeof(local_time), "%Y%m%d%H%M%S", localtime(&cur_time)))
|
||||
{
|
||||
time_t cur_time = time(nullptr);
|
||||
static time_t prev_snap;
|
||||
// The variable 'n' is used for labelling the screenshots when multiple screenshots are taken in
|
||||
// a single second, we'll start using this variable for naming when a second screenshot request is detected
|
||||
// at the same time as the first one. Hence, we're initially setting this counter to 2 to imply that
|
||||
// the captured image is the 2nd image captured at this specific time.
|
||||
static int n = 2;
|
||||
char local_time[16];
|
||||
|
||||
if (strftime(local_time, sizeof(local_time), "%Y%m%d%H%M%S", localtime(&cur_time)))
|
||||
if (cur_time == prev_snap)
|
||||
m_snapshot = fmt::format("gs_{0}_({1})", local_time, n++);
|
||||
else
|
||||
{
|
||||
if (cur_time == prev_snap)
|
||||
m_snapshot = format("%s_%s_(%d)", path.c_str(), local_time, n++);
|
||||
else
|
||||
{
|
||||
n = 2;
|
||||
m_snapshot = format("%s_%s", path.c_str(), local_time);
|
||||
}
|
||||
prev_snap = cur_time;
|
||||
}
|
||||
|
||||
// append the game serial and title
|
||||
if (std::string name(GetDumpName()); !name.empty())
|
||||
{
|
||||
FileSystem::SanitizeFileName(name);
|
||||
m_snapshot += format("_%s", name.c_str());
|
||||
}
|
||||
if (std::string serial(GetDumpSerial()); !serial.empty())
|
||||
{
|
||||
FileSystem::SanitizeFileName(serial);
|
||||
m_snapshot += format("_%s", serial.c_str());
|
||||
n = 2;
|
||||
m_snapshot = fmt::format("gs_{}", local_time);
|
||||
}
|
||||
prev_snap = cur_time;
|
||||
}
|
||||
|
||||
// append the game serial and title
|
||||
if (std::string name(GetDumpName()); !name.empty())
|
||||
{
|
||||
FileSystem::SanitizeFileName(name);
|
||||
m_snapshot += '_';
|
||||
m_snapshot += name;
|
||||
}
|
||||
if (std::string serial(GetDumpSerial()); !serial.empty())
|
||||
{
|
||||
FileSystem::SanitizeFileName(serial);
|
||||
m_snapshot += '_';
|
||||
m_snapshot += serial;
|
||||
}
|
||||
|
||||
// prepend snapshots directory
|
||||
m_snapshot = Path::CombineStdString(EmuFolders::Snapshots, m_snapshot);
|
||||
}
|
||||
|
||||
return true;
|
||||
// this is really gross, but wx we get the snapshot request after shift...
|
||||
#ifdef PCSX2_CORE
|
||||
m_dump_frames = gsdump_frames;
|
||||
#endif
|
||||
}
|
||||
|
||||
void GSRenderer::StopGSDump()
|
||||
{
|
||||
m_snapshot = {};
|
||||
m_dump_frames = 0;
|
||||
}
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
|
||||
bool GSRenderer::BeginCapture(std::string& filename)
|
||||
{
|
||||
return m_capture.BeginCapture(GetTvRefreshRate(), GetInternalResolution(), GetCurrentAspectRatioFloat(GetVideoMode() == GSVideoMode::SDTV_480P), filename);
|
||||
|
@ -671,7 +713,6 @@ void GSRenderer::EndCapture()
|
|||
|
||||
void GSRenderer::KeyEvent(const HostKeyEvent& e)
|
||||
{
|
||||
#if !defined(PCSX2_CORE)
|
||||
#ifdef _WIN32
|
||||
m_shift_key = !!(::GetAsyncKeyState(VK_SHIFT) & 0x8000);
|
||||
m_control_key = !!(::GetAsyncKeyState(VK_CONTROL) & 0x8000);
|
||||
|
@ -696,6 +737,19 @@ void GSRenderer::KeyEvent(const HostKeyEvent& e)
|
|||
}
|
||||
#endif
|
||||
|
||||
if (m_dump_frames == 0)
|
||||
{
|
||||
// start dumping
|
||||
if (m_shift_key)
|
||||
m_dump_frames = m_control_key ? std::numeric_limits<u32>::max() : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// stop dumping
|
||||
if (m_dump && !m_control_key)
|
||||
m_dump_frames = 0;
|
||||
}
|
||||
|
||||
if (e.type == HostKeyEvent::Type::KeyPressed)
|
||||
{
|
||||
|
||||
|
@ -732,9 +786,10 @@ void GSRenderer::KeyEvent(const HostKeyEvent& e)
|
|||
return;
|
||||
}
|
||||
}
|
||||
#endif // PCSX2_CORE
|
||||
}
|
||||
|
||||
#endif // PCSX2_CORE
|
||||
|
||||
void GSRenderer::PurgePool()
|
||||
{
|
||||
g_gs_device->PurgePool();
|
||||
|
|
|
@ -19,21 +19,29 @@
|
|||
#include "GS/GSCapture.h"
|
||||
#include <memory>
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
#include <mutex>
|
||||
#endif
|
||||
|
||||
struct HostKeyEvent;
|
||||
|
||||
class GSRenderer : public GSState
|
||||
{
|
||||
GSCapture m_capture;
|
||||
std::string m_snapshot;
|
||||
|
||||
private:
|
||||
bool Merge(int field);
|
||||
|
||||
bool m_shift_key;
|
||||
bool m_control_key;
|
||||
#ifndef PCSX2_CORE
|
||||
GSCapture m_capture;
|
||||
std::mutex m_snapshot_mutex;
|
||||
bool m_shift_key = false;
|
||||
bool m_control_key = false;
|
||||
#endif
|
||||
std::string m_snapshot;
|
||||
u32 m_dump_frames = 0;
|
||||
|
||||
protected:
|
||||
GSVector2i m_real_size{0, 0};
|
||||
bool m_texture_shuffle;
|
||||
GSVector2i m_real_size;
|
||||
|
||||
virtual GSTexture* GetOutput(int i, int& y_offset) = 0;
|
||||
virtual GSTexture* GetFeedbackOutput() { return nullptr; }
|
||||
|
@ -45,20 +53,24 @@ public:
|
|||
virtual void Destroy();
|
||||
|
||||
virtual void VSync(u32 field, bool registers_written);
|
||||
virtual bool MakeSnapshot(const std::string& path);
|
||||
virtual void KeyEvent(const HostKeyEvent& e);
|
||||
virtual bool CanUpscale() { return false; }
|
||||
virtual int GetUpscaleMultiplier() { return 1; }
|
||||
virtual GSVector2 GetTextureScaleFactor() { return { 1.0f, 1.0f }; }
|
||||
GSVector2i GetInternalResolution();
|
||||
|
||||
virtual bool BeginCapture(std::string& filename);
|
||||
virtual void EndCapture();
|
||||
|
||||
virtual void PurgePool() override;
|
||||
virtual void PurgeTextureCache();
|
||||
|
||||
bool SaveSnapshotToMemory(u32 width, u32 height, std::vector<u32>* pixels);
|
||||
|
||||
void QueueSnapshot(const std::string& path, u32 gsdump_frames);
|
||||
void StopGSDump();
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
bool BeginCapture(std::string& filename);
|
||||
void EndCapture();
|
||||
void KeyEvent(const HostKeyEvent& e);
|
||||
#endif
|
||||
};
|
||||
|
||||
extern std::unique_ptr<GSRenderer> g_gs_renderer;
|
|
@ -586,6 +586,7 @@ DebugTab::DebugTab(wxWindow* parent)
|
|||
m_ui.addComboBoxAndLabel(ogl_grid, "Geometry Shader:", "OverrideGeometryShaders", &theApp.m_gs_generic_list, IDC_GEOMETRY_SHADER_OVERRIDE, vk_ogl_hw_prereq);
|
||||
m_ui.addComboBoxAndLabel(ogl_grid, "Image Load Store:", "override_GL_ARB_shader_image_load_store", &theApp.m_gs_generic_list, IDC_IMAGE_LOAD_STORE, ogl_hw_prereq);
|
||||
m_ui.addComboBoxAndLabel(ogl_grid, "Sparse Texture:", "override_GL_ARB_sparse_texture", &theApp.m_gs_generic_list, IDC_SPARSE_TEXTURE, ogl_hw_prereq);
|
||||
m_ui.addComboBoxAndLabel(ogl_grid, "Dump Compression:", "GSDumpCompression", &theApp.m_gs_dump_compression, -1);
|
||||
ogl_box->Add(ogl_grid);
|
||||
|
||||
tab_box->Add(ogl_box.outer, wxSizerFlags().Expand());
|
||||
|
|
|
@ -391,6 +391,7 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const
|
|||
OpEqu(CRCHack) &&
|
||||
OpEqu(TextureFiltering) &&
|
||||
OpEqu(TexturePreloading) &&
|
||||
OpEqu(GSDumpCompression) &&
|
||||
OpEqu(Dithering) &&
|
||||
OpEqu(MaxAnisotropy) &&
|
||||
OpEqu(SWExtraThreads) &&
|
||||
|
@ -575,6 +576,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
|
|||
GSSettingIntEnumEx(CRCHack, "crc_hack_level");
|
||||
GSSettingIntEnumEx(TextureFiltering, "filter");
|
||||
GSSettingIntEnumEx(TexturePreloading, "texture_preloading");
|
||||
GSSettingIntEnumEx(GSDumpCompression, "GSDumpCompression");
|
||||
GSSettingIntEx(Dithering, "dithering_ps2");
|
||||
GSSettingIntEx(MaxAnisotropy, "MaxAnisotropy");
|
||||
GSSettingIntEx(SWExtraThreads, "extrathreads");
|
||||
|
|
|
@ -372,8 +372,7 @@ namespace Implementations
|
|||
|
||||
void Sys_TakeSnapshot()
|
||||
{
|
||||
if (GSmakeSnapshot(g_Conf->Folders.Snapshots.ToUTF8().data()))
|
||||
OSDlog(ConsoleColors::Color_Black, true, "Snapshot taken");
|
||||
GSQueueSnapshot(std::string(), 0);
|
||||
}
|
||||
|
||||
void Sys_RenderToggle()
|
||||
|
|
|
@ -1040,7 +1040,7 @@ void MainEmuFrame::Menu_Capture_Screenshot_Screenshot_Click(wxCommandEvent& even
|
|||
{
|
||||
return;
|
||||
}
|
||||
GSmakeSnapshot(g_Conf->Folders.Snapshots.ToString().char_str());
|
||||
GSQueueSnapshot(std::string(), 0);
|
||||
}
|
||||
|
||||
void MainEmuFrame::Menu_Capture_Screenshot_Screenshot_As_Click(wxCommandEvent& event)
|
||||
|
@ -1056,7 +1056,7 @@ void MainEmuFrame::Menu_Capture_Screenshot_Screenshot_As_Click(wxCommandEvent& e
|
|||
wxFileDialog fileDialog(this, _("Select a file"), g_Conf->Folders.Snapshots.ToAscii(), wxEmptyString, "PNG files (*.png)|*.png", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
|
||||
if (fileDialog.ShowModal() == wxID_OK)
|
||||
GSmakeSnapshot((char*)fileDialog.GetPath().char_str());
|
||||
GSQueueSnapshot(StringUtil::wxStringToUTF8String(fileDialog.GetPath()), 0);
|
||||
|
||||
// Resume emulation
|
||||
if (!wasPaused)
|
||||
|
|
Loading…
Reference in New Issue