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