diff --git a/pcsx2-gsrunner/Main.cpp b/pcsx2-gsrunner/Main.cpp
index 410583a4fe..117b82056e 100644
--- a/pcsx2-gsrunner/Main.cpp
+++ b/pcsx2-gsrunner/Main.cpp
@@ -436,8 +436,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|tr|ds|fs]: Enabling dumping of render target, texture, z buffer, frame, "
- "alphas, and info (context, vertices, list of transfers), transfers images, draw stats, frame stats, respectively, per draw. Generates lots of data.\n");
+ std::fprintf(stderr, " -dump [rt|tex|z|f|a|i|tr|ds|fs|hw]: Enabling dumping of render target, texture, z buffer, frame, "
+ "alphas, and info (context, vertices, list of transfers), transfers images, draw stats, frame stats, HW config, 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");
@@ -527,6 +527,8 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveDrawStats", true);
if (str.find("fs") != std::string::npos)
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveFrameStats", true);
+ if (str.find("hw") != std::string::npos)
+ s_settings_interface.SetBoolValue("EmuCore/GS", "SaveHWConfig", true);
continue;
}
else if (CHECK_ARG_PARAM("-dumprange"))
diff --git a/pcsx2-qt/Settings/DebugGSSettingsTab.ui b/pcsx2-qt/Settings/DebugGSSettingsTab.ui
index f3e02687d9..404cdec19d 100644
--- a/pcsx2-qt/Settings/DebugGSSettingsTab.ui
+++ b/pcsx2-qt/Settings/DebugGSSettingsTab.ui
@@ -92,6 +92,13 @@
+ -
+
+
+ Save HW Config
+
+
+
-
diff --git a/pcsx2-qt/Settings/DebugSettingsWidget.cpp b/pcsx2-qt/Settings/DebugSettingsWidget.cpp
index 65ba232f7d..614947c9ac 100644
--- a/pcsx2-qt/Settings/DebugSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/DebugSettingsWidget.cpp
@@ -112,6 +112,7 @@ DebugSettingsWidget::DebugSettingsWidget(SettingsWindow* settings_dialog, QWidge
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveTransferImages, "EmuCore/GS", "SaveTransferImages", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveDrawStats, "EmuCore/GS", "SaveDrawStats", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveFrameStats, "EmuCore/GS", "SaveFrameStats", false);
+ SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveHWConfig, "EmuCore/GS", "SaveHWConfig", 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);
@@ -219,6 +220,7 @@ void DebugSettingsWidget::onDrawDumpingChanged()
m_gs.saveTransferImages->setEnabled(enabled);
m_gs.saveDrawStats->setEnabled(enabled);
m_gs.saveFrameStats->setEnabled(enabled);
+ m_gs.saveHWConfig->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 3949532465..755db54cbe 100644
--- a/pcsx2/Config.h
+++ b/pcsx2/Config.h
@@ -788,6 +788,7 @@ struct Pcsx2Config
SaveTransferImages : 1,
SaveDrawStats : 1,
SaveFrameStats : 1,
+ SaveHWConfig : 1,
DumpReplaceableTextures : 1,
DumpReplaceableMipmaps : 1,
DumpTexturesWithFMVActive : 1,
diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp
index a894002432..5ceff85185 100644
--- a/pcsx2/GS/Renderers/Common/GSDevice.cpp
+++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp
@@ -4,6 +4,7 @@
#include "GS/Renderers/Common/GSDevice.h"
#include "GS/GSGL.h"
#include "GS/GS.h"
+#include "GS/GSUtil.h"
#include "Host.h"
#include "common/Console.h"
@@ -18,6 +19,8 @@
#include "imgui.h"
#include
+#include
+#include
int SetDATMShader(SetDATM datm)
{
@@ -1054,6 +1057,457 @@ bool GSHWDrawConfig::BlendState::IsEffective(ColorMaskSelector colormask) const
((colormask.key & 8u) && (src_factor_alpha != GSDevice::CONST_ONE || dst_factor_alpha != GSDevice::CONST_ZERO)));
}
+std::string GSHWDrawConfig::GetTopologyName(u32 topology)
+{
+ switch (static_cast(topology))
+ {
+ case GSHWDrawConfig::Topology::Point: return "Point";
+ case GSHWDrawConfig::Topology::Line: return "Line";
+ case GSHWDrawConfig::Topology::Triangle: return "Triangle";
+ default: return std::to_string(topology);
+ }
+}
+
+std::string GSHWDrawConfig::GetVSExpandName(u32 vsexpand)
+{
+ switch (static_cast(vsexpand))
+ {
+ case GSHWDrawConfig::VSExpand::None: return "None";
+ case GSHWDrawConfig::VSExpand::Point: return "Point";
+ case GSHWDrawConfig::VSExpand::Line: return "Line";
+ case GSHWDrawConfig::VSExpand::Sprite: return "Sprite";
+ default: return std::to_string(vsexpand);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSDateName(u32 date)
+{
+ switch (date)
+ {
+ case 0: return "None";
+ case 1: return "0_pass_PrimID_init";
+ case 2: return "1_pass_PrimID_init";
+ case 3: return "PrimID_main";
+ case 5: return "0_pass_main";
+ case 6: return "1_pass_main";
+ default: return std::to_string(date);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSAlphaTestName(u32 atst)
+{
+ switch (static_cast(atst))
+ {
+ case GSHWDrawConfig::PSAlphaTest::PS_ATST_NONE: return "NONE";
+ case GSHWDrawConfig::PSAlphaTest::PS_ATST_LEQUAL: return "LEQUAL";
+ case GSHWDrawConfig::PSAlphaTest::PS_ATST_GEQUAL: return "GEQUAL";
+ case GSHWDrawConfig::PSAlphaTest::PS_ATST_EQUAL: return "EQUAL";
+ case GSHWDrawConfig::PSAlphaTest::PS_ATST_NOTEQUAL: return "NOTEQUAL";
+ default: return std::to_string(atst);
+ };
+}
+
+std::string GSHWDrawConfig::GetPSDstFmtName(u32 dstfmt)
+{
+ switch (dstfmt)
+ {
+ case 0: return "32bit";
+ case 1: return "24bit";
+ case 2: return "16bit";
+ default: return std::to_string(dstfmt);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSDepthFmtName(u32 depthfmt)
+{
+ switch (depthfmt)
+ {
+ case 0: return "None";
+ case 1: return "32bit";
+ case 2: return "16bit";
+ case 3: return "RGBA";
+ default: return std::to_string(depthfmt);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSBlendABDName(u32 abd)
+{
+ switch (abd)
+ {
+ case 0: return "Cs";
+ case 1: return "Cd";
+ case 2: return "Zero";
+ default: return std::to_string(abd);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSBlendCName(u32 c)
+{
+ switch (c)
+ {
+ case 0: return "As";
+ case 1: return "Ad";
+ case 2: return "Af";
+ default: return std::to_string(c);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSBlendHWName(u32 blendhw)
+{
+ switch (static_cast(blendhw))
+ {
+ case HWBlendType::SRC_ONE_DST_FACTOR: return "SRC_ONE_DST_FACTOR";
+ case HWBlendType::SRC_ALPHA_DST_FACTOR: return "SRC_ALPHA_DST_FACTOR";
+ case HWBlendType::SRC_DOUBLE: return "SRC_DOUBLE";
+ case HWBlendType::SRC_HALF_ONE_DST_FACTOR: return "SRC_HALF_ONE_DST_FACTOR";
+ case HWBlendType::SRC_INV_DST_BLEND_HALF: return "SRC_INV_DST_BLEND_HALF";
+ case HWBlendType::INV_SRC_DST_BLEND_HALF: return "INV_SRC_DST_BLEND_HALF";
+ default: return std::to_string(blendhw);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSBlendMixName(u32 blendmix)
+{
+ switch (static_cast(blendmix))
+ {
+ case HWBlendType::BMIX1_ALPHA_HIGH_ONE: return "BMIX1_ALPHA_HIGH_ONE";
+ case HWBlendType::BMIX1_SRC_HALF: return "BMIX1_SRC_HALF";
+ case HWBlendType::BMIX2_OVERFLOW: return "BMIX2_OVERFLOW";
+ default: return std::to_string(blendmix);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSChannelName(u32 channel)
+{
+ switch (channel)
+ {
+ case 0: return "None";
+ case 1: return "FetchRed";
+ case 2: return "FetchGreen";
+ case 3: return "FetchBlue";
+ case 4: return "FetchAlpha";
+ case 5: return "FetchRGB";
+ case 6: return "FetchGXBY";
+ default: return std::to_string(channel);
+ }
+}
+
+std::string GSHWDrawConfig::GetPSDitherName(u32 dither)
+{
+ switch (dither)
+ {
+ case 0: return "None";
+ case 1: return "Standard";
+ case 2: return "ReciprocalScaled";
+ default: return std::to_string(dither);
+ }
+}
+
+std::string GSHWDrawConfig::GetSSTrilnName(u32 triln)
+{
+ switch (static_cast(triln))
+ {
+ case GS_MIN_FILTER::Nearest: return "Nearest";
+ case GS_MIN_FILTER::Linear: return "Linear";
+ case GS_MIN_FILTER::Nearest_Mipmap_Nearest: return "Nearest_Mipmap_Nearest";
+ case GS_MIN_FILTER::Nearest_Mipmap_Linear: return "Nearest_Mipmap_Linear";
+ case GS_MIN_FILTER::Linear_Mipmap_Nearest: return "Linear_Mipmap_Nearest";
+ case GS_MIN_FILTER::Linear_Mipmap_Linear: return "Linear_Mipmap_Linear";
+ default: return std::to_string(triln);
+ }
+}
+
+std::string GSHWDrawConfig::GetBlendOpName(u32 blendop)
+{
+ switch (blendop)
+ {
+ case 0: return "ADD";
+ case 1: return "SUBTRACT";
+ case 2: return "REVERSE_SUBTRACT";
+ default: return std::to_string(blendop);
+ }
+}
+
+std::string GSHWDrawConfig::GetBlendFactorName(u32 blendfactor)
+{
+ switch (blendfactor)
+ {
+ case 0: return "SRC_COLOR";
+ case 1: return "ONE_MINUS_SRC_COLOR";
+ case 2: return "DST_COLOR";
+ case 3: return "ONE_MINUS_DST_COLOR";
+ case 4: return "SRC1_COLOR";
+ case 5: return "ONE_MINUS_SRC1_COLOR";
+ case 6: return "SRC_ALPHA";
+ case 7: return "ONE_MINUS_SRC_ALPHA";
+ case 8: return "DST_ALPHA";
+ case 9: return "ONE_MINUS_DST_ALPHA";
+ case 10: return "SRC1_ALPHA";
+ case 11: return "ONE_MINUS_SRC1_ALPHA";
+ case 12: return "CONSTANT_COLOR";
+ case 13: return "ONE_MINUS_CONSTANT_COLOR";
+ case 14: return "ONE";
+ case 15: return "ZERO";
+ default: return std::to_string(blendfactor);
+ }
+}
+
+std::string GSHWDrawConfig::GetDestinationAlphaModeName(u32 datm)
+{
+ switch (static_cast(datm))
+ {
+ case GSHWDrawConfig::DestinationAlphaMode::Off: return "Off";
+ case GSHWDrawConfig::DestinationAlphaMode::Stencil: return "Stencil";
+ case GSHWDrawConfig::DestinationAlphaMode::StencilOne: return "StencilOne";
+ case GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking: return "PrimIDTracking";
+ case GSHWDrawConfig::DestinationAlphaMode::Full: return "Full";
+ default: return std::to_string(datm);
+ }
+}
+
+std::string GSHWDrawConfig::GetColClipModeName(u32 ccmode)
+{
+ switch (static_cast(ccmode))
+ {
+ case GSHWDrawConfig::ColClipMode::NoModify: return "NoModify";
+ case GSHWDrawConfig::ColClipMode::ConvertOnly: return "ConvertOnly";
+ case GSHWDrawConfig::ColClipMode::ResolveOnly: return "ResolveOnly";
+ case GSHWDrawConfig::ColClipMode::ConvertAndResolve: return "ConvertAndResolve";
+ case GSHWDrawConfig::ColClipMode::EarlyResolve: return "EarlyResolve";
+ default: return std::to_string(ccmode);
+ }
+}
+
+std::string GSHWDrawConfig::GetSetDATMName(u32 datm)
+{
+ switch (static_cast(datm))
+ {
+ case SetDATM::DATM0: return "DATM0";
+ case SetDATM::DATM1: return "DATM1";
+ case SetDATM::DATM0_RTA_CORRECTION: return "DATM0_RTA_CORRECTION";
+ case SetDATM::DATM1_RTA_CORRECTION: return "DATM1_RTA_CORRECTION";
+ default: return std::to_string(datm);
+ };
+}
+
+static constexpr const char* INDENT = " ";
+
+void GSHWDrawConfig::DumpPSSelector(std::ostream& out, const PSSelector& ps, const std::string& indent)
+{
+ out.imbue(std::locale::classic()); // Disable integer separators
+ out << std::dec;
+
+ out << indent << "aem_fmt: " << static_cast(ps.aem_fmt) << std::endl;
+ out << indent << "pal_fmt: " << static_cast(ps.pal_fmt) << std::endl;
+ out << indent << "dst_fmt: " << static_cast(ps.dst_fmt) << " # " << GetPSDstFmtName(static_cast(ps.dst_fmt)) << std::endl;
+ out << indent << "depth_fmt: " << static_cast(ps.depth_fmt) << " # " << GetPSDepthFmtName(static_cast(ps.depth_fmt)) << std::endl;
+ out << indent << "aem: " << static_cast(ps.aem) << std::endl;
+ out << indent << "fba: " << static_cast(ps.fba) << std::endl;
+ out << indent << "fog: " << static_cast(ps.fog) << std::endl;
+ out << indent << "iip: " << static_cast(ps.iip) << std::endl;
+ out << indent << "date: " << static_cast(ps.date) << " # " << GetPSDateName(static_cast(ps.date)) << std::endl;
+ out << indent << "atst: " << static_cast(ps.atst) << " # " << GetPSAlphaTestName(static_cast(ps.atst)) << std::endl;
+ out << indent << "afail: " << static_cast(ps.afail) << " # " << GSUtil::GetAFAILName(static_cast(ps.afail)) << std::endl;
+ out << indent << "fst: " << static_cast(ps.fst) << std::endl;
+ out << indent << "tfx: " << static_cast(ps.tfx) << std::endl;
+ out << indent << "tcc: " << GSUtil::GetTCCName(static_cast(ps.tcc)) << std::endl;
+ out << indent << "wms: " << static_cast(ps.tcc) << " # " << static_cast(ps.wms) << std::endl;
+ out << indent << "wmt: " << static_cast(ps.wmt) << std::endl;
+ out << indent << "adjs: " << static_cast(ps.adjs) << std::endl;
+ out << indent << "adjt: " << static_cast(ps.adjt) << std::endl;
+ out << indent << "ltf: " << static_cast(ps.ltf) << std::endl;
+ out << indent << "shuffle: " << static_cast(ps.shuffle) << std::endl;
+ out << indent << "shuffle_same: " << static_cast(ps.shuffle_same) << std::endl;
+ out << indent << "real16src: " << static_cast(ps.real16src) << std::endl;
+ out << indent << "process_ba: " << static_cast(ps.process_ba) << std::endl;
+ out << indent << "process_rg: " << static_cast(ps.process_rg) << std::endl;
+ out << indent << "shuffle_across: " << static_cast(ps.shuffle_across) << std::endl;
+ out << indent << "write_rg: " << static_cast(ps.write_rg) << std::endl;
+ out << indent << "fbmask: " << static_cast(ps.fbmask) << std::endl;
+ out << indent << "blend_a: " << static_cast(ps.blend_a) << " # " << GetPSBlendABDName(static_cast(ps.blend_a)) << std::endl;
+ out << indent << "blend_b: " << static_cast(ps.blend_b) << " # " << GetPSBlendABDName(static_cast(ps.blend_b)) << std::endl;
+ out << indent << "blend_c: " << static_cast(ps.blend_c) << " # " << GetPSBlendCName(static_cast(ps.blend_c)) << std::endl;
+ out << indent << "blend_d: " << static_cast(ps.blend_d) << " # " << GetPSBlendABDName(static_cast(ps.blend_d)) << std::endl;
+ out << indent << "fixed_one_a: " << static_cast(ps.fixed_one_a) << std::endl;
+ out << indent << "blend_hw: " << static_cast(ps.blend_hw) << " # " << GetPSBlendHWName(static_cast(ps.blend_hw)) << std::endl;
+ out << indent << "a_masked: " << static_cast(ps.a_masked) << std::endl;
+ out << indent << "colclip_hw: " << static_cast(ps.colclip_hw) << std::endl;
+ out << indent << "rta_correction: " << static_cast(ps.rta_correction) << std::endl;
+ out << indent << "rta_source_correction: " << static_cast(ps.rta_source_correction) << std::endl;
+ out << indent << "colclip: " << static_cast(ps.colclip) << std::endl;
+ out << indent << "blend_mix: " << static_cast(ps.blend_mix) << " # " << GetPSBlendMixName(static_cast(ps.blend_mix)) << std::endl;
+ out << indent << "round_inv: " << static_cast(ps.round_inv) << std::endl;
+ out << indent << "pabe: " << static_cast(ps.pabe) << std::endl;
+ out << indent << "no_color: " << static_cast(ps.no_color) << std::endl;
+ out << indent << "no_color1: " << static_cast(ps.no_color1) << std::endl;
+ out << indent << "channel: " << static_cast(ps.channel) << " # " << GetPSChannelName(static_cast(ps.channel)) << std::endl;
+ out << indent << "channel_fb: " << static_cast(ps.channel_fb) << std::endl;
+ out << indent << "dither: " << static_cast(ps.dither) << " # " << GetPSDitherName(static_cast(ps.dither)) << std::endl;
+ out << indent << "dither_adjust: " << static_cast(ps.dither_adjust) << std::endl;
+ out << indent << "zclamp: " << static_cast(ps.zclamp) << std::endl;
+ out << indent << "tcoffsethack: " << static_cast(ps.tcoffsethack) << std::endl;
+ out << indent << "urban_chaos_hle: " << static_cast(ps.urban_chaos_hle) << std::endl;
+ out << indent << "tales_of_abyss_hle: " << static_cast(ps.tales_of_abyss_hle) << std::endl;
+ out << indent << "tex_is_fb: " << static_cast(ps.tex_is_fb) << std::endl;
+ out << indent << "automatic_lod: " << static_cast(ps.automatic_lod) << std::endl;
+ out << indent << "manual_lod: " << static_cast(ps.manual_lod) << std::endl;
+ out << indent << "point_sampler: " << static_cast(ps.point_sampler) << std::endl;
+ out << indent << "region_rect: " << static_cast(ps.region_rect) << std::endl;
+ out << indent << "scanmsk: " << static_cast(ps.scanmsk) << " # " << GSUtil::GetSCANMSKName(static_cast(ps.scanmsk)) << std::endl;
+}
+
+void GSHWDrawConfig::DumpVSSelector(std::ostream& out, const VSSelector& vs, const std::string& indent)
+{
+ out.imbue(std::locale::classic()); // Disable integer separators
+ out << std::dec;
+
+ out << indent << "fst: " << static_cast(vs.fst) << std::endl;
+ out << indent << "tme: " << static_cast(vs.tme) << std::endl;
+ out << indent << "iip: " << static_cast(vs.iip) << std::endl;
+ out << indent << "point_size: " << static_cast(vs.point_size) << std::endl;
+ out << indent << "expand: " << static_cast(vs.expand) << " # " << GetVSExpandName(static_cast(vs.expand)) << std::endl;
+}
+
+void GSHWDrawConfig::DumpBlendState(std::ostream& out, const BlendState& bs, const std::string& indent)
+{
+ out.imbue(std::locale::classic()); // Disable integer separators
+ out << std::dec;
+
+ out << indent << "enable: " << static_cast(bs.enable) << std::endl;
+ out << indent << "constant_enable: " << static_cast(bs.constant_enable) << std::endl;
+ out << indent << "op: " << static_cast(bs.op) << " # " << GetBlendOpName(static_cast(bs.op)) << std::endl;
+ out << indent << "src_factor: " << static_cast(bs.src_factor) << " # " << GetBlendFactorName(static_cast(bs.src_factor)) << std::endl;
+ out << indent << "dst_factor: " << static_cast(bs.dst_factor) << " # " << GetBlendFactorName(static_cast(bs.dst_factor)) << std::endl;
+ out << indent << "src_factor_alpha: " << static_cast(bs.src_factor_alpha) << " # " << GetBlendFactorName(static_cast(bs.src_factor_alpha)) << std::endl;
+ out << indent << "dst_factor_alpha: " << static_cast(bs.dst_factor_alpha) << " # " << GetBlendFactorName(static_cast(bs.dst_factor_alpha)) << std::endl;
+ out << indent << "constant: " << static_cast(bs.constant) << std::endl;
+}
+
+void GSHWDrawConfig::DumpDepthStencilSelctor(std::ostream& out, const DepthStencilSelector& dss, const std::string& indent)
+{
+ out.imbue(std::locale::classic()); // Disable integer separators
+ out << std::dec;
+
+ out << indent << "ztst: " << static_cast(dss.ztst) << " # " << GSUtil::GetZTSTName(static_cast(dss.ztst)) << std::endl;
+ out << indent << "zwe: " << static_cast(dss.zwe) << std::endl;
+ out << indent << "date: " << static_cast(dss.date) << std::endl;
+ out << indent << "date_one: " << static_cast(dss.date_one) << std::endl;
+}
+
+void GSHWDrawConfig::DumpSamplerSelector(std::ostream& out, const SamplerSelector& ss, const std::string& indent)
+{
+ out.imbue(std::locale::classic()); // Disable integer separators
+ out << std::dec;
+
+ out << indent << "tau: " << static_cast(ss.tau) << std::endl;
+ out << indent << "tav: " << static_cast(ss.tav) << std::endl;
+ out << indent << "biln: " << static_cast(ss.biln) << std::endl;
+ out << indent << "triln: " << static_cast(ss.triln) << " # " << GetSSTrilnName(static_cast(ss.triln)) << std::endl;
+ out << indent << "aniso: " << static_cast(ss.aniso) << std::endl;
+ out << indent << "lodclamp: " << static_cast(ss.lodclamp) << std::endl;
+}
+
+void GSHWDrawConfig::DumpAlphaPass(std::ostream& out, const AlphaPass& ap, const std::string& indent)
+{
+ out.imbue(std::locale::classic()); // Disable integer separators
+ out << std::dec;
+
+ out << indent << "enable: " << static_cast(ap.enable) << std::endl;
+ out << indent << "require_one_barrier: " << static_cast(ap.require_one_barrier) << std::endl;
+ out << indent << "require_full_barrier: " << static_cast(ap.require_full_barrier) << std::endl;
+ out << indent << "colormask: " << std::showbase << std::hex << static_cast(ap.colormask.wrgba) << std::dec << std::endl;
+ out << indent << "ps_aref: " << static_cast(ap.ps_aref) << std::endl;
+
+ out << indent << "ps:" << std::endl;
+ DumpPSSelector(out, ap.ps, indent + INDENT);
+
+ out << indent << "dss:" << std::endl;
+ DumpDepthStencilSelctor(out, ap.depth, indent + INDENT);
+}
+
+void GSHWDrawConfig::DumpBlendMultipass(std::ostream& out, const BlendMultiPass& bmp, const std::string& indent)
+{
+ out.imbue(std::locale::classic()); // Disable integer separators
+ out << std::dec;
+
+ out << indent << "enable: " << static_cast(bmp.enable) << std::endl;
+ out << indent << "no_color1: " << static_cast(bmp.no_color1) << std::endl;
+ out << indent << "blend_hw: " << static_cast(bmp.blend_hw) << " # " << GetPSBlendHWName(static_cast(bmp.blend_hw)) << std::endl;
+ out << indent << "dither: " << static_cast(bmp.dither) << std::endl;
+
+ out << indent << "blend:" << std::endl;
+ DumpBlendState(out, bmp.blend, indent + INDENT);
+}
+
+void GSHWDrawConfig::DumpConfig(std::ostream& out, const GSHWDrawConfig& conf,
+ bool ps, bool vs, bool bs, bool dss, bool ss, bool asp, bool bmp)
+{
+ out.imbue(std::locale::classic()); // Disable integer separators
+ out << std::dec;
+
+ out << "topology: " << static_cast(conf.topology) << " # " << GetTopologyName(static_cast(conf.topology)) << std::endl;
+ out << "require_one_barrier: " << static_cast(conf.require_one_barrier) << std::endl;
+ out << "require_full_barrier: " << static_cast(conf.require_full_barrier) << std::endl;
+
+ out << "destination_alpha: " << static_cast(conf.destination_alpha) << " # " << GetDestinationAlphaModeName(static_cast(conf.destination_alpha)) << std::endl;
+ out << "datm: " << static_cast(conf.datm) << " # " << GetSetDATMName(static_cast(conf.datm)) << std::endl;
+ out << "line_expand: " << conf.line_expand << std::endl;
+ out << "colormask: " << std::hex << std::showbase << static_cast(conf.colormask.wrgba) << std::dec << std::endl;
+
+ if (ps)
+ {
+ out << "ps:" << std::endl;
+ DumpPSSelector(out, conf.ps, INDENT);
+ }
+
+ if (vs)
+ {
+ out << "vs:" << std::endl;
+ DumpVSSelector(out, conf.vs, INDENT);
+ }
+
+ if (bs)
+ {
+ out << "blend:" << std::endl;
+ DumpBlendState(out, conf.blend, INDENT);
+ }
+
+ if (ss)
+ {
+ out << "sampler:" << std::endl;
+ DumpSamplerSelector(out, conf.sampler, INDENT);
+ }
+
+ if (dss)
+ {
+ out << "depth:" << std::endl;
+ DumpDepthStencilSelctor(out, conf.depth, INDENT);
+ }
+
+ if (asp)
+ {
+ out << "alpha_second_pass:" << std::endl;
+ DumpAlphaPass(out, conf.alpha_second_pass, INDENT);
+ }
+
+ if (bmp)
+ {
+ out << "blend_multi_pass:" << std::endl;
+ DumpBlendMultipass(out, conf.blend_multi_pass, INDENT);
+ }
+}
+
+void GSHWDrawConfig::DumpConfig(const std::string& fn, const GSHWDrawConfig& conf,
+ bool ps, bool vs, bool bs, bool dss, bool ss, bool asp, bool bmp)
+{
+ std::ofstream file(fn);
+
+ if (!file.is_open())
+ return;
+
+ DumpConfig(file, conf, ps, vs, bs, dss, ss, asp, bmp);
+}
+
// clang-format off
// Maps PS2 blend modes to our best approximation of them with PC hardware
diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h
index 6ca3e6aff3..3afa7b31d1 100644
--- a/pcsx2/GS/Renderers/Common/GSDevice.h
+++ b/pcsx2/GS/Renderers/Common/GSDevice.h
@@ -328,6 +328,16 @@ struct alignas(16) GSHWDrawConfig
};
static_assert(sizeof(VSSelector) == 1, "VSSelector is a single byte");
#pragma pack(pop)
+
+ enum PSAlphaTest
+ {
+ PS_ATST_NONE = 0,
+ PS_ATST_LEQUAL = 1,
+ PS_ATST_GEQUAL = 2,
+ PS_ATST_EQUAL = 3,
+ PS_ATST_NOTEQUAL = 4
+ };
+
#pragma pack(push, 4)
struct PSSelector
{
@@ -780,6 +790,37 @@ struct alignas(16) GSHWDrawConfig
ColClipMode colclip_mode;
GIFRegFRAME colclip_frame;
GSVector4i colclip_update_area; ///< Area in the framebuffer which colclip will modify;
+
+ // Dumping
+ static std::string GetTopologyName(u32 topology);
+ static std::string GetVSExpandName(u32 vsexpand);
+ static std::string GetPSAlphaTestName(u32 dstfmt);
+ static std::string GetPSDstFmtName(u32 dstfmt);
+ static std::string GetPSDepthFmtName(u32 depthfmt);
+ static std::string GetPSBlendABDName(u32 abd);
+ static std::string GetPSBlendCName(u32 c);
+ static std::string GetPSBlendHWName(u32 blendhw);
+ static std::string GetPSBlendMixName(u32 blendmix);
+ static std::string GetPSDitherName(u32 dither);
+ static std::string GetPSChannelName(u32 channel);
+ static std::string GetSSTrilnName(u32 triln);
+ static std::string GetBlendOpName(u32 blendop);
+ static std::string GetBlendFactorName(u32 blendfactor);
+ static std::string GetDestinationAlphaModeName(u32 datm);
+ static std::string GetPSDateName(u32 date);
+ static std::string GetColClipModeName(u32 ccmode);
+ static std::string GetSetDATMName(u32 setdatm);
+ static void DumpPSSelector(std::ostream& out, const PSSelector& ps, const std::string& indent = "");
+ static void DumpVSSelector(std::ostream& out, const VSSelector& vs, const std::string& indent = "");
+ static void DumpBlendState(std::ostream& out, const BlendState& bs, const std::string& indent = "");
+ static void DumpDepthStencilSelctor(std::ostream& out, const DepthStencilSelector& ds, const std::string& indent = "");
+ static void DumpSamplerSelector(std::ostream& out, const SamplerSelector& ss, const std::string& indent = "");
+ static void DumpAlphaPass(std::ostream& out, const AlphaPass& ap, const std::string& indent = "");
+ static void DumpBlendMultipass(std::ostream& out, const BlendMultiPass& bmp, const std::string& indent = "");
+ static void DumpConfig(std::ostream& out, const GSHWDrawConfig& conf,
+ bool ps = true, bool vs = true, bool bs = true, bool dss = true, bool ss = true, bool asp = true, bool bmp = true);
+ static void DumpConfig(const std::string& fn, const GSHWDrawConfig& conf,
+ bool ps = true, bool vs = true, bool bs = true, bool dss = true, bool ss = true, bool asp = true, bool bmp = true);
};
static inline u32 GetExpansionFactor(GSHWDrawConfig::VSExpand expand)
diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
index 80a8fdf6d9..77fabac12e 100644
--- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
+++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
@@ -7280,27 +7280,27 @@ void GSRendererHW::EmulateATST(float& AREF, GSHWDrawConfig::PSSelector& ps, bool
{
case ATST_LESS:
AREF = aref - 0.1f;
- ps.atst = 1;
+ ps.atst = GSHWDrawConfig::PSAlphaTest::PS_ATST_LEQUAL;
break;
case ATST_LEQUAL:
AREF = aref - 0.1f + 1.0f;
- ps.atst = 1;
+ ps.atst = GSHWDrawConfig::PSAlphaTest::PS_ATST_LEQUAL;
break;
case ATST_GEQUAL:
AREF = aref - 0.1f;
- ps.atst = 2;
+ ps.atst = GSHWDrawConfig::PSAlphaTest::PS_ATST_GEQUAL;
break;
case ATST_GREATER:
AREF = aref - 0.1f + 1.0f;
- ps.atst = 2;
+ ps.atst = GSHWDrawConfig::PSAlphaTest::PS_ATST_GEQUAL;
break;
case ATST_EQUAL:
AREF = aref;
- ps.atst = 3;
+ ps.atst = GSHWDrawConfig::PSAlphaTest::PS_ATST_EQUAL;
break;
case ATST_NOTEQUAL:
AREF = aref;
- ps.atst = 4;
+ ps.atst = GSHWDrawConfig::PSAlphaTest::PS_ATST_NOTEQUAL;
break;
case ATST_NEVER: // Draw won't be done so no need to implement it in shader
case ATST_ALWAYS:
@@ -8209,6 +8209,11 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.alpha_second_pass.enable = false;
}
+ if (GSConfig.SaveHWConfig && GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
+ {
+ GSHWDrawConfig::DumpConfig(GetDrawDumpPath("%05d_hwconfig.txt", s_n), m_conf);
+ }
+
if (!m_channel_shuffle_width)
g_gs_device->RenderHW(m_conf);
else
diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp
index 8f6151ceba..6f0064a24e 100644
--- a/pcsx2/Pcsx2Config.cpp
+++ b/pcsx2/Pcsx2Config.cpp
@@ -999,6 +999,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBoolEx(SaveTransferImages, "SaveTransferImages");
SettingsWrapBitBoolEx(SaveDrawStats, "SaveDrawStats");
SettingsWrapBitBoolEx(SaveFrameStats, "SaveFrameStats");
+ SettingsWrapBitBoolEx(SaveHWConfig, "SaveHWConfig");
SettingsWrapBitBool(DumpReplaceableTextures);
SettingsWrapBitBool(DumpReplaceableMipmaps);
SettingsWrapBitBool(DumpTexturesWithFMVActive);