GS: Add dumping of transfer bitmaps.

This commit is contained in:
TJnotJT 2025-08-24 19:37:24 -04:00 committed by lightningterror
parent a8c549baee
commit f83e11892b
9 changed files with 69 additions and 16 deletions

View File

@ -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 <dir>: 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"))

View File

@ -71,6 +71,13 @@
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="saveTransferImages">
<property name="text">
<string>Save Transfer Image Data</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">

View File

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

View File

@ -774,6 +774,7 @@ struct Pcsx2Config
SaveDepth : 1,
SaveAlpha : 1,
SaveInfo : 1,
SaveTransferImages : 1,
DumpReplaceableTextures : 1,
DumpReplaceableMipmaps : 1,
DumpTexturesWithFMVActive : 1,

View File

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

View File

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

View File

@ -15,6 +15,7 @@
#include <algorithm>
#include <cfloat>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <bit>
@ -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<std::ofstream> 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<std::ofstream> file;
int transfer_n = 0;
for (int i = 0; i < static_cast<int>(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)

View File

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

View File

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