diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp
index f1ecaffb24..cbc2fb1a97 100644
--- a/pcsx2-gsrunner/Main.cpp
+++ b/pcsx2-gsrunner/Main.cpp
@@ -473,8 +473,8 @@ static void PrintCommandLineHelp(const char* progname)
std::fprintf(stderr, " -help: Displays this information and exits.\n");
std::fprintf(stderr, " -version: Displays version information and exits.\n");
std::fprintf(stderr, " -dumpdir
: Frame dump directory (will be dumped as filename_frameN.png).\n");
- std::fprintf(stderr, " -dump [rt|tex|z|f|a|i]: Enabling dumping of render target, texture, z buffer, frame, "
- "alphas, and info (context, vertices), respectively, per draw. Generates lots of data.\n");
+ std::fprintf(stderr, " -dump [rt|tex|z|f|a|i|tr]: Enabling dumping of render target, texture, z buffer, frame, "
+ "alphas, and info (context, vertices, transfers (list)), transfers (images), respectively, per draw. Generates lots of data.\n");
std::fprintf(stderr, " -dumprange N[,L,B]: Start dumping from draw N (base 0), stops after L draws, and only "
"those draws that are multiples of B (intersection of -dumprange and -dumprangef used)."
"Defaults to 0,-1,1 (all draws). Only used if -dump used.\n");
@@ -558,6 +558,8 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveAlpha", true);
if (str.find("i") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveInfo", true);
+ if (str.find("tr") != std::string::npos)
+ s_settings_interface.SetBoolValue("EmuCore/GS", "SaveTransferImages", true);
continue;
}
else if (CHECK_ARG_PARAM("-dumprange"))
diff --git a/pcsx2-qt/Settings/DebugGSSettingsTab.ui b/pcsx2-qt/Settings/DebugGSSettingsTab.ui
index 45a0bc9315..f262c2eb37 100644
--- a/pcsx2-qt/Settings/DebugGSSettingsTab.ui
+++ b/pcsx2-qt/Settings/DebugGSSettingsTab.ui
@@ -71,6 +71,13 @@
+ -
+
+
+ Save Transfer Image Data
+
+
+
-
diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.cpp b/pcsx2-qt/Settings/DebugSettingsWidget.cpp
index 110b165513..2cd0e63849 100644
--- a/pcsx2-qt/Settings/DebugSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/DebugSettingsWidget.cpp
@@ -109,6 +109,7 @@ DebugSettingsWidget::DebugSettingsWidget(SettingsWindow* settings_dialog, QWidge
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveDepth, "EmuCore/GS", "SaveDepth", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveAlpha, "EmuCore/GS", "SaveAlpha", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveInfo, "EmuCore/GS", "SaveInfo", false);
+ SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveTransferImages, "EmuCore/GS", "SaveTransferImages", false);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveDrawStart, "EmuCore/GS", "SaveDrawStart", 0);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveDrawCount, "EmuCore/GS", "SaveDrawCount", 5000);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveFrameStart, "EmuCore/GS", "SaveFrameStart", 0);
@@ -213,6 +214,7 @@ void DebugSettingsWidget::onDrawDumpingChanged()
m_gs.saveDepth->setEnabled(enabled);
m_gs.saveAlpha->setEnabled(enabled);
m_gs.saveInfo->setEnabled(enabled);
+ m_gs.saveTransferImages->setEnabled(enabled);
m_gs.saveDrawStart->setEnabled(enabled);
m_gs.saveDrawCount->setEnabled(enabled);
m_gs.saveFrameStart->setEnabled(enabled);
diff --git a/pcsx2/Config.h b/pcsx2/Config.h
index 786843f2da..254dfb8599 100644
--- a/pcsx2/Config.h
+++ b/pcsx2/Config.h
@@ -774,6 +774,7 @@ struct Pcsx2Config
SaveDepth : 1,
SaveAlpha : 1,
SaveInfo : 1,
+ SaveTransferImages : 1,
DumpReplaceableTextures : 1,
DumpReplaceableMipmaps : 1,
DumpTexturesWithFMVActive : 1,
diff --git a/pcsx2/GS/GSLocalMemory.cpp b/pcsx2/GS/GSLocalMemory.cpp
index 55281ae156..7ce626f95f 100644
--- a/pcsx2/GS/GSLocalMemory.cpp
+++ b/pcsx2/GS/GSLocalMemory.cpp
@@ -651,7 +651,7 @@ void GSLocalMemory::ReadTexture(const GSOffset& off, const GSVector4i& r, u8* ds
//
-void GSLocalMemory::SaveBMP(const std::string& fn, u32 bp, u32 bw, u32 psm, int w, int h)
+void GSLocalMemory::SaveBMP(const std::string& fn, u32 bp, u32 bw, u32 psm, int w, int h, int x, int y)
{
int pitch = w * 4;
int size = pitch * h;
@@ -671,7 +671,7 @@ void GSLocalMemory::SaveBMP(const std::string& fn, u32 bp, u32 bw, u32 psm, int
{
for (int i = 0; i < w; i++)
{
- ((u32*)p)[i] = (this->*rp)(i, j, TEX0.TBP0, TEX0.TBW);
+ ((u32*)p)[i] = (this->*rp)(x + i, y + j, TEX0.TBP0, TEX0.TBW);
}
}
diff --git a/pcsx2/GS/GSLocalMemory.h b/pcsx2/GS/GSLocalMemory.h
index cef9c9e0d1..06dfa449e8 100644
--- a/pcsx2/GS/GSLocalMemory.h
+++ b/pcsx2/GS/GSLocalMemory.h
@@ -1121,7 +1121,7 @@ public:
//
- void SaveBMP(const std::string& fn, u32 bp, u32 bw, u32 psm, int w, int h);
+ void SaveBMP(const std::string& fn, u32 bp, u32 bw, u32 psm, int w, int h, int x = 0, int y = 0);
};
constexpr inline GSOffset GSOffset::fromKnownPSM(u32 bp, u32 bw, GS_PSM psm)
diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp
index 3f419651e6..23aa9d96e3 100644
--- a/pcsx2/GS/GSState.cpp
+++ b/pcsx2/GS/GSState.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include
#include
#include
@@ -457,7 +458,7 @@ void GSState::DumpDrawInfo(bool dump_regs, bool dump_verts, bool dump_transfers)
if (dump_transfers)
{
s = GetDrawDumpPath("%05d_transfers.txt", s_n);
- DumpTransfers(s);
+ DumpTransferList(s);
}
}
@@ -729,7 +730,7 @@ void GSState::DumpVertices(const std::string& filename)
file << CLOSE_MAP << std::endl;
}
-void GSState::DumpTransfers(const std::string& filename)
+void GSState::DumpTransferList(const std::string& filename)
{
// Only create the file if there are transfers to dump
std::optional file;
@@ -792,9 +793,6 @@ void GSState::DumpTransfers(const std::string& filename)
(*file) << INDENT << "rect: [" << std::dec << transfer.rect.x << DEL << transfer.rect.y << DEL <<
transfer.rect.z << DEL << transfer.rect.w << "]" << std::endl;
- // Dump draw number
- (*file) << INDENT << "draw: " << std::dec << transfer.draw << std::endl;
-
// Dump zero_clear
(*file) << INDENT << "zero_clear: " << (transfer.zero_clear ? "true" : "false") << std::endl;
@@ -802,6 +800,41 @@ void GSState::DumpTransfers(const std::string& filename)
}
}
+void GSState::DumpTransferImages()
+{
+ // Only create the file if there are transfers to dump
+ std::optional file;
+
+ int transfer_n = 0;
+ for (int i = 0; i < static_cast(m_draw_transfers.size()); ++i)
+ {
+ if (m_draw_transfers[i].draw != s_n - 1)
+ continue; // skip transfers that did not start in the previous draw
+
+ const GSUploadQueue& transfer = m_draw_transfers[i];
+
+ std::string filename;
+ if (transfer.ee_to_gs)
+ {
+ // Transferring EE->GS then only the destination info is relevant.
+ filename = GetDrawDumpPath("%05d_transfer%02d_EE_to_GS_%03x_%d_%s_%d_%d_%d_%d.png",
+ s_n, transfer_n++, transfer.blit.DBP, transfer.blit.DBW, GSUtil::GetPSMName(transfer.blit.DPSM),
+ transfer.rect.x, transfer.rect.y, transfer.rect.z, transfer.rect.w);
+ }
+ else
+ {
+ // Transferring GS->GS then the source info is relevant.
+ filename = GetDrawDumpPath("%05d_transfer%02d_GS_to_GS_%03x_%d_%s_%03x_%d_%s_%d_%d_%d_%d.bmp",
+ s_n, transfer_n++, transfer.blit.SBP, transfer.blit.SBW, GSUtil::GetPSMName(transfer.blit.SPSM),
+ transfer.blit.DBP, transfer.blit.DBW, GSUtil::GetPSMName(transfer.blit.DPSM),
+ transfer.rect.x, transfer.rect.y, transfer.rect.z, transfer.rect.w);
+ }
+
+ m_mem.SaveBMP(filename, transfer.blit.DBP, transfer.blit.DBW, transfer.blit.DPSM,
+ transfer.rect.width(), transfer.rect.height(), transfer.rect.x, transfer.rect.y);
+ }
+}
+
__inline void GSState::CheckFlushes()
{
if (m_dirty_gs_regs && m_index.tail > 0)
@@ -2029,12 +2062,18 @@ void GSState::FlushPrim()
const bool skip_draw = (m_context->TEST.ZTE && m_context->TEST.ZTST == ZTST_NEVER);
m_quad_check_valid = false;
- if (GSConfig.SaveInfo && GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
+ if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
{
- // Only dump registers/vertices if we are drawing.
- // Always dump the transfers since these are relevant for debugging regardless of
- // whether the draw is skipped or not.
- DumpDrawInfo(!skip_draw, !skip_draw, true);
+ if (GSConfig.SaveInfo)
+ {
+ // Only dump registers/vertices if we are drawing.
+ // Always dump the transfers since these are relevant for debugging regardless of
+ // whether the draw is skipped or not.
+ DumpDrawInfo(!skip_draw, !skip_draw, true);
+ }
+
+ if (GSConfig.SaveTransferImages)
+ DumpTransferImages();
}
if (!skip_draw)
diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h
index dd700203d0..e3f61a6f0d 100644
--- a/pcsx2/GS/GSState.h
+++ b/pcsx2/GS/GSState.h
@@ -444,7 +444,8 @@ public:
void DumpDrawInfo(bool dump_regs, bool dump_verts, bool dump_transfers);
void DumpVertices(const std::string& filename);
- void DumpTransfers(const std::string& filename);
+ void DumpTransferList(const std::string& filename);
+ void DumpTransferImages();
bool TrianglesAreQuads(bool shuffle_check = false);
PRIM_OVERLAP PrimitiveOverlap();
diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp
index 91346dabab..45e8a4b0dc 100644
--- a/pcsx2/Pcsx2Config.cpp
+++ b/pcsx2/Pcsx2Config.cpp
@@ -987,6 +987,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBoolEx(SaveDepth, "SaveDepth");
SettingsWrapBitBoolEx(SaveAlpha, "SaveAlpha");
SettingsWrapBitBoolEx(SaveInfo, "SaveInfo");
+ SettingsWrapBitBoolEx(SaveTransferImages, "SaveTransferImages");
SettingsWrapBitBool(DumpReplaceableTextures);
SettingsWrapBitBool(DumpReplaceableMipmaps);
SettingsWrapBitBool(DumpTexturesWithFMVActive);