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:
Connor McLaughlin 2022-05-16 21:10:34 +10:00 committed by refractionpcsx2
parent 7bd7cdd867
commit 8c270288de
18 changed files with 268 additions and 130 deletions

View File

@ -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)
{

View File

@ -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);

View File

@ -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()

View File

@ -134,6 +134,7 @@ private Q_SLOTS:
void onThemeChangedFromSettings();
void onLoggingOptionChanged();
void onScreenshotActionTriggered();
void onSaveGSDumpActionTriggered();
void onVMStarting();
void onVMStarted();

View File

@ -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>&amp;Network &amp;&amp; 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"/>

View File

@ -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);

View File

@ -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>

View File

@ -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};

View File

@ -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();
}},

View File

@ -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

View File

@ -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()

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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());

View File

@ -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");

View File

@ -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()

View File

@ -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)