diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index d7d7e97483cbe..d54a4a7def147 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -868,7 +868,8 @@ SCAJ-20010: name: "Bakusou Dekotora Densetsu - Otoko Hanamichi Yume Roman" region: "NTSC-Unk" gsHWFixes: - beforeDraw: "OI_BigMuthaTruckers" + textureInsideRT: 1 # Fixes inside RT shuffling. + getSkipCount: "GSC_BigMuthaTruckers" SCAJ-20011: name: "Armored Core 3 - Silent Line" region: "NTSC-HK" @@ -10905,9 +10906,8 @@ SLAJ-25080: name: "Godfather, The" region: "NTSC-Unk" gsHWFixes: - gpuTargetCLUT: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLAJ-25081: name: "FIFA World Cup - Germany 2006" region: "NTSC-Unk" @@ -11460,11 +11460,16 @@ SLED-53664: SLED-53673: name: "007 - From Russia with Love [Demo]" region: "PAL-E" + gsHWFixes: + textureInsideRT: 1 # Required for complex offset shuffles. + recommendedBlendingLevel: 4 # Fixes lighting. SLED-53681: name: "007 - From Russia with Love & Need for Speed Most Wanted & SSX On Tour [Demo]" region: "PAL-E" gsHWFixes: halfPixelOffset: 2 # Fixes blurriness. + textureInsideRT: 1 # Required for complex offset shuffles on 007. + recommendedBlendingLevel: 4 # Fixes lighting in 007. SLED-53711: name: "Brothers in Arms - Earned in Blood" region: "PAL-A" @@ -14582,6 +14587,7 @@ SLES-51230: region: "PAL-E" gsHWFixes: cpuCLUTRender: 1 # Fixes light bleed through objects. + autoFlush: 1 # Fixes light bloom intensity. SLES-51232: name: "Virtua Tennis 2" region: "PAL-M4" @@ -14831,11 +14837,13 @@ SLES-51317: region: "PAL-F" gsHWFixes: cpuCLUTRender: 1 # Fixes light bleed through objects. + autoFlush: 1 # Fixes light bloom intensity. SLES-51318: name: "Minority Report" region: "PAL-G" gsHWFixes: cpuCLUTRender: 1 # Fixes light bleed through objects. + autoFlush: 1 # Fixes light bloom intensity. SLES-51322: name: "Robotech Battlecry" region: "PAL-M5" @@ -14915,7 +14923,8 @@ SLES-51355: region: "PAL-M5" compat: 5 gsHWFixes: - beforeDraw: "OI_BigMuthaTruckers" + textureInsideRT: 1 # Fixes inside RT shuffling. + getSkipCount: "GSC_BigMuthaTruckers" SLES-51356: name: "Road Trip Adventure" region: "PAL-M3" @@ -17582,21 +17591,18 @@ SLES-52588: name: "Mercenaries - Playground of Destruction" region: "PAL-E" gsHWFixes: - halfBottomOverride: 1 # Bottom screen has wrong colors. autoFlush: 2 # Fixes missing lighting. # halfPixelOffset: 1 # Fixes lighting misalignment. Do not enable this, it breaks a lot of graphics. SLES-52589: name: "Mercenaries - Playground of Destruction" region: "PAL-F" gsHWFixes: - halfBottomOverride: 1 # Bottom screen has wrong colors. autoFlush: 2 # Fixes missing lighting. # halfPixelOffset: 1 # Fixes lighting misalignment. Do not enable this, it breaks a lot of graphics. SLES-52590: name: "Mercenaries - Playground of Destruction" region: "PAL-G" gsHWFixes: - halfBottomOverride: 1 # Bottom screen has wrong colors. autoFlush: 2 # Fixes missing lighting. # halfPixelOffset: 1 # Fixes lighting misalignment. Do not enable this, it breaks a lot of graphics. SLES-52591: @@ -18719,7 +18725,6 @@ SLES-53008: name: "Mercenaries - Playground of Destruction" region: "PAL-I-S" gsHWFixes: - halfBottomOverride: 1 # Bottom screen has wrong colors. autoFlush: 2 # Fixes missing lighting. # halfPixelOffset: 1 # Fixes lighting misalignment. Do not enable this, it breaks a lot of graphics. SLES-53009: @@ -20266,6 +20271,9 @@ SLES-53552: SLES-53553: name: "007 - From Russia with Love" region: "PAL-M7" + gsHWFixes: + textureInsideRT: 1 # Required for complex offset shuffles. + recommendedBlendingLevel: 4 # Fixes lighting. SLES-53556: name: "Driver - Parallel Lines" region: "PAL-M3" @@ -21415,37 +21423,32 @@ SLES-53967: name: "Godfather, The" region: "PAL-M6" gsHWFixes: - gpuTargetCLUT: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLES-53968: name: "Parrain, Le" region: "PAL-F" gsHWFixes: - cpuCLUTRender: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLES-53969: name: "Pate, Der" region: "PAL-G" gsHWFixes: - cpuCLUTRender: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLES-53970: name: "Padrino, Il" region: "PAL-I" gsHWFixes: - cpuCLUTRender: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLES-53971: name: "Padrino, El" region: "PAL-S" gsHWFixes: - cpuCLUTRender: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLES-53972: name: "Stock Car Crash" region: "PAL-E" @@ -27517,9 +27520,8 @@ SLKA-25338: name: "Godfather, The" region: "NTSC-K" gsHWFixes: - gpuTargetCLUT: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLKA-25339: name: "Jin Samguk Mussang 4" region: "NTSC-K" @@ -33315,7 +33317,8 @@ SLPM-65234: name: "Bakusou Dekotora Densetsu - Otoko Hanamichi Yume Roman" region: "NTSC-J" gsHWFixes: - beforeDraw: "OI_BigMuthaTruckers" + textureInsideRT: 1 # Fixes inside RT shuffling. + getSkipCount: "GSC_BigMuthaTruckers" SLPM-65235: name: "New Roommania - Porori Seishun" region: "NTSC-J" @@ -35846,7 +35849,6 @@ SLPM-65942: name: "Mercenaries - Playground of Destruction" region: "NTSC-J" gsHWFixes: - halfBottomOverride: 1 # Bottom screen has wrong colors. autoFlush: 2 # Fixes missing lighting. # halfPixelOffset: 1 # Fixes lighting misalignment. Do not enable this, it breaks a lot of graphics. SLPM-65943: @@ -37446,6 +37448,9 @@ SLPM-66321: SLPM-66322: name: "007 - Russia yori Ai o Komete" region: "NTSC-J" + gsHWFixes: + textureInsideRT: 1 # Required for complex offset shuffles. + recommendedBlendingLevel: 4 # Fixes lighting. SLPM-66323: name: "Princess Software Collection, The" region: "NTSC-J" @@ -38043,7 +38048,6 @@ SLPM-66465: name: "Mercenaries - Playground of Destruction [EA Best Hits]" region: "NTSC-J" gsHWFixes: - halfBottomOverride: 1 # Bottom screen has wrong colors. autoFlush: 2 # Fixes missing lighting. # halfPixelOffset: 1 # Fixes lighting misalignment. Do not enable this, it breaks a lot of graphics. SLPM-66467: @@ -39049,9 +39053,8 @@ SLPM-66710: name: "Godfather, The" region: "NTSC-J" gsHWFixes: - gpuTargetCLUT: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLPM-66712: name: "Rozen Maiden - Geppetto Garden [Limited Edition]" region: "NTSC-J" @@ -39991,9 +39994,8 @@ SLPM-66966: name: "Godfather, The [EA-SY! 1980]" region: "NTSC-J" gsHWFixes: - gpuTargetCLUT: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLPM-66967: name: "Aria - The Natural - Tooi Yume no Mirage [Alchemist Best Collection]" region: "NTSC-J" @@ -48247,7 +48249,8 @@ SLUS-20291: region: "NTSC-U" compat: 5 gsHWFixes: - beforeDraw: "OI_BigMuthaTruckers" + textureInsideRT: 1 # Fixes inside RT shuffling. + getSkipCount: "GSC_BigMuthaTruckers" SLUS-20292: name: "Tsugunai - Atonement" region: "NTSC-U" @@ -48432,6 +48435,7 @@ SLUS-20331: compat: 5 gsHWFixes: cpuCLUTRender: 1 # Fixes light bleed through objects. + autoFlush: 1 # Fixes light bloom intensity. SLUS-20332: name: "NCAA March Madness 2002" region: "NTSC-U" @@ -49829,7 +49833,8 @@ SLUS-20605: name: "Big Mutha Truckers" region: "NTSC-U" gsHWFixes: - beforeDraw: "OI_BigMuthaTruckers" + textureInsideRT: 1 # Fixes inside RT shuffling. + getSkipCount: "GSC_BigMuthaTruckers" SLUS-20606: name: "Bounty Hunter - Seek & Destroy" region: "NTSC-U" @@ -51500,7 +51505,6 @@ SLUS-20932: region: "NTSC-U" compat: 5 gsHWFixes: - halfBottomOverride: 1 # Bottom screen has wrong colors. autoFlush: 2 # Fixes missing lighting. # halfPixelOffset: 1 # Fixes lighting misalignment. Do not enable this, it breaks a lot of graphics. SLUS-20933: @@ -53505,6 +53509,9 @@ SLUS-21282: name: "007 - From Russia with Love" region: "NTSC-U" compat: 5 + gsHWFixes: + textureInsideRT: 1 # Required for complex offset shuffles. + recommendedBlendingLevel: 4 # Fixes lighting. SLUS-21283: name: "Total Overdose - A Gunslinger's Tale in Mexico" region: "NTSC-U" @@ -54162,9 +54169,8 @@ SLUS-21385: region: "NTSC-U" compat: 5 gsHWFixes: - gpuTargetCLUT: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLUS-21386: name: "Tales of the Abyss" region: "NTSC-U" @@ -54277,9 +54283,8 @@ SLUS-21406: name: "Godfather, The - Collector's Edition" region: "NTSC-U" gsHWFixes: - gpuTargetCLUT: 1 # Fixes light occlusion. - skipDrawStart: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. - skipDrawEnd: 1 # Removes mono-colored sepia bottom and vertical sepia bars on top. + textureInsideRT: 1 # Required for complex offset shuffles. + halfPixelOffset: 2 # Fixes center line in post processing. SLUS-21407: name: "NFL Head Coach" region: "NTSC-U" @@ -57498,7 +57503,6 @@ SLUS-29137: name: "Mercenaries - Playground of Destruction [Demo]" region: "NTSC-U" gsHWFixes: - halfBottomOverride: 1 # Bottom screen has wrong colors. autoFlush: 2 # Fixes missing lighting. # halfPixelOffset: 1 # Fixes lighting misalignment. Do not enable this, it breaks a lot of graphics. SLUS-29138: @@ -57633,9 +57637,14 @@ SLUS-29167: region: "NTSC-U" gsHWFixes: halfPixelOffset: 2 # Fixes blurriness. + textureInsideRT: 1 # Required for complex offset shuffles in 007. + recommendedBlendingLevel: 4 # Fixes lighting in 007. SLUS-29168: name: "007 - From Russia with Love [Demo]" region: "NTSC-U" + gsHWFixes: + textureInsideRT: 1 # Required for complex offset shuffles. + recommendedBlendingLevel: 4 # Fixes lighting. SLUS-29169: name: "Resident Evil 4 [Demo]" region: "NTSC-U" diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index 852c002550da6..da734db74a583 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -52,6 +52,7 @@ #define PS_POINT_SAMPLER 0 #define PS_REGION_RECT 0 #define PS_SHUFFLE 0 +#define PS_SHUFFLE_SAME 0 #define PS_READ_BA 0 #define PS_READ16_SRC 0 #define PS_DFMT 0 @@ -940,8 +941,22 @@ PS_OUTPUT ps_main(PS_INPUT input) { uint4 denorm_c = uint4(C); uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f); - - if (PS_READ16_SRC) + + // Special case for 32bit input and 16bit output, shuffle used by The Godfather. + if (PS_SHUFFLE_SAME) + { + if (PS_READ_BA) + { + C.ga = (float2)(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u))); + C.rb = C.ga; + } + else + { + C.ga = C.rg; + C.rb = C.ga; + } + } + else if (PS_READ16_SRC) { C.rb = (float2)float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)); if (denorm_c.a & 0x80u) diff --git a/bin/resources/shaders/opengl/tfx_fs.glsl b/bin/resources/shaders/opengl/tfx_fs.glsl index cf9a1aba8b350..54284b5e98039 100644 --- a/bin/resources/shaders/opengl/tfx_fs.glsl +++ b/bin/resources/shaders/opengl/tfx_fs.glsl @@ -950,6 +950,15 @@ void ps_main() #if PS_SHUFFLE uvec4 denorm_c = uvec4(C); uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f); +#if PS_SHUFFLE_SAME +#if (PS_READ_BA) + C.ga = vec2(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u))); + C.rb = C.ga; +#else + C.ga = C.rg; + C.rb = C.ga; +#endif +#else #if PS_READ16_SRC C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5))); if (bool(denorm_c.a & 0x80u)) @@ -995,6 +1004,7 @@ void ps_main() #endif // PS_READ_BA #endif // READ16_SRC +#endif // PS_SHUFFLE_SAME #endif // PS_SHUFFLE // Must be done before alpha correction diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index cddde4c8aed06..ac149e1686416 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -277,7 +277,9 @@ void main() #define PS_TCOFFSETHACK 0 #define PS_POINT_SAMPLER 0 #define PS_SHUFFLE 0 +#define PS_SHUFFLE_SAME 0 #define PS_READ_BA 0 +#define PS_WRITE_RG 0 #define PS_READ16_SRC 0 #define PS_DFMT 0 #define PS_DEPTH_FMT 0 @@ -1197,30 +1199,42 @@ void main() #if PS_SHUFFLE uvec4 denorm_c = uvec4(C); uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f); - #if PS_READ16_SRC - C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5))); - if ((denorm_c.a & 0x80u) != 0u) - C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u))); - else - C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u))); - #else - // Mask will take care of the correct destination - #if PS_READ_BA - C.rb = C.bb; + + // Special case for 32bit input and 16bit output, shuffle used by The Godfather. + #if PS_SHUFFLE_SAME + #if (PS_READ_BA) + C.ga = vec2(float((denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80u))); + C.rb = C.ga; #else - C.rb = C.rr; + C.ga = C.rg; + C.rb = C.ga; #endif - - #if PS_READ_BA + #else + #if PS_READ16_SRC + C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5))); if ((denorm_c.a & 0x80u) != 0u) - C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u))); + C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u))); else - C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u))); + C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u))); #else - if ((denorm_c.g & 0x80u) != 0u) - C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u))); - else - C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u))); + // Mask will take care of the correct destination + #if PS_READ_BA + C.rb = C.bb; + #else + C.rb = C.rr; + #endif + + #if PS_READ_BA + if ((denorm_c.a & 0x80u) != 0u) + C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.y & 0x80u))); + else + C.ga = vec2(float((denorm_c.a & 0x7Fu) | (denorm_TA.x & 0x80u))); + #else + if ((denorm_c.g & 0x80u) != 0u) + C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.y & 0x80u))); + else + C.ga = vec2(float((denorm_c.g & 0x7Fu) | (denorm_TA.x & 0x80u))); + #endif #endif #endif #endif diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 1cc0023f826e2..9e83a1b495381 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -181,7 +181,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget* ////////////////////////////////////////////////////////////////////////// // HW Renderer Fixes ////////////////////////////////////////////////////////////////////////// - SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.halfScreenFix, "EmuCore/GS", "UserHacks_Half_Bottom_Override", -1, -1); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cpuSpriteRenderBW, "EmuCore/GS", "UserHacks_CPUSpriteRenderBW", 0); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cpuSpriteRenderLevel, "EmuCore/GS", "UserHacks_CPUSpriteRenderLevel", 0); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.cpuCLUTRender, "EmuCore/GS", "UserHacks_CPUCLUTRender", 0); @@ -515,9 +514,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget* // Hardware Fixes tab { - dialog->registerWidgetHelp(m_ui.halfScreenFix, tr("Half Screen Fix"), tr("Automatic (Default)"), - tr("Control the half-screen fix detection on texture shuffling.")); - dialog->registerWidgetHelp(m_ui.cpuSpriteRenderBW, tr("CPU Sprite Renderer Size"), tr("0 (Disabled)"), tr("")); dialog->registerWidgetHelp(m_ui.cpuCLUTRender, tr("Software CLUT Render"), tr("0 (Disabled)"), tr("")); @@ -1050,7 +1046,6 @@ void GraphicsSettingsWidget::resetManualHardwareFixes() check_bool("EmuCore/GS", "UserHacks", false); - check_int("EmuCore/GS", "UserHacks_Half_Bottom_Override", -1); check_int("EmuCore/GS", "UserHacks_CPUSpriteRenderBW", 0); check_int("EmuCore/GS", "UserHacks_CPUCLUTRender", 0); check_int("EmuCore/GS", "UserHacks_GPUTargetCLUTMode", 0); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index dcddf2a6d7b19..39339a0bbd654 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -787,25 +787,6 @@ - - - - - Automatic (Default) - - - - - Force Disabled - - - - - Force Enabled - - - - diff --git a/pcsx2/Config.h b/pcsx2/Config.h index fc7dd05b33aa3..bedd6cbc938c0 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -765,7 +765,6 @@ struct Pcsx2Config int SkipDrawEnd = 0; GSHWAutoFlushLevel UserHacks_AutoFlush = GSHWAutoFlushLevel::Disabled; - s8 UserHacks_HalfBottomOverride = -1; s8 UserHacks_HalfPixelOffset = 0; s8 UserHacks_RoundSprite = 0; s32 UserHacks_TCOffsetX = 0; diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index 633413a6e6511..57c3d81b087e5 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -316,6 +316,7 @@ struct alignas(16) GSHWDrawConfig u32 ltf : 1; // Shuffle and fbmask effect u32 shuffle : 1; + u32 shuffle_same : 1; u32 real16src: 1; u32 read_ba : 1; u32 write_rg : 1; diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.h b/pcsx2/GS/Renderers/Common/GSRenderer.h index 0885a1a46d7d9..2b96d58395fa9 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.h +++ b/pcsx2/GS/Renderers/Common/GSRenderer.h @@ -40,6 +40,7 @@ class GSRenderer : public GSState GSVector2i m_real_size{0, 0}; bool m_texture_shuffle = false; bool m_copy_16bit_to_target_shuffle = false; + bool m_same_group_texture_shuffle = false; virtual GSTexture* GetOutput(int i, float& scale, int& y_offset) = 0; virtual GSTexture* GetFeedbackOutput(float& scale) { return nullptr; } diff --git a/pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp b/pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp index 0613c97c440ae..9591d92fc4ff4 100644 --- a/pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSTextureFX11.cpp @@ -138,6 +138,7 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant sm.AddMacro("PS_POINT_SAMPLER", sel.point_sampler); sm.AddMacro("PS_REGION_RECT", sel.region_rect); sm.AddMacro("PS_SHUFFLE", sel.shuffle); + sm.AddMacro("PS_SHUFFLE_SAME", sel.shuffle_same); sm.AddMacro("PS_READ_BA", sel.read_ba); sm.AddMacro("PS_READ16_SRC", sel.real16src); sm.AddMacro("PS_CHANNEL_FETCH", sel.channel); diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index 6e724abb9496d..65fc7839f3378 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -2743,6 +2743,7 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector& sm.AddMacro("PS_POINT_SAMPLER", sel.point_sampler); sm.AddMacro("PS_REGION_RECT", sel.region_rect); sm.AddMacro("PS_SHUFFLE", sel.shuffle); + sm.AddMacro("PS_SHUFFLE_SAME", sel.shuffle_same); sm.AddMacro("PS_READ_BA", sel.read_ba); sm.AddMacro("PS_READ16_SRC", sel.real16src); sm.AddMacro("PS_CHANNEL_FETCH", sel.channel); diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index 2d0a31ccccc67..5f77cfb1e3f8b 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -851,6 +851,44 @@ bool GSHwHack::GSC_MetalGearSolid3(GSRendererHW& r, int& skip) return true; } +bool GSHwHack::GSC_BigMuthaTruckers(GSRendererHW& r, int& skip) +{ + // Rendering pattern: + // CRTC frontbuffer at 0x0 is interlaced (half vertical resolution), + // game needs to do a depth effect (so green channel to alpha), + // but there is a vram limitation so green is pushed into the alpha channel of the CRCT buffer, + // vertical resolution is half so only half is processed at once + // We, however, don't have this limitation so we'll replace the draw with a full-screen TS. + + const GIFRegTEX0& Texture = RTEX0; + + GIFRegTEX0 Frame = {}; + Frame.TBW = RFRAME.FBW; + Frame.TBP0 = RFRAME.Block(); + const int frame_offset_pal = GSLocalMemory::GetEndBlockAddress(0xa00, 10, PSMCT32, GSVector4i(0, 0, 640, 256)) + 1; + const int frame_offset_ntsc = GSLocalMemory::GetEndBlockAddress(0xa00, 10, PSMCT32, GSVector4i(0, 0, 640, 224)) + 1; + const GSVector4i rect = GSVector4i(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y); + + if (RPRIM->TME && Frame.TBW == 10 && Texture.TBW == 10 && Texture.PSM == PSMCT16 && ((rect.w == 512 && Frame.TBP0 == frame_offset_pal) || (Frame.TBP0 == frame_offset_ntsc && rect.w == 448))) + { + // 224 ntsc, 256 pal. + GL_INS("GSC_BigMuthaTruckers half bottom offset %d", r.m_context->XYOFFSET.OFX >> 4); + + const size_t count = r.m_vertex.next; + GSVertex* v = &r.m_vertex.buff[0]; + const u16 offset = (u16)rect.w * 16; + + for (size_t i = 0; i < count; i++) + v[i].XYZ.Y += offset; + + r.m_vt.m_min.p.y += rect.w; + r.m_vt.m_max.p.y += rect.w; + r.m_cached_ctx.FRAME.FBP = 0x50; // 0xA00 >> 5 + } + + return true; +} + bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { const u32 n_vertices = r.m_vertex.next; @@ -904,37 +942,6 @@ bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds return true; } -bool GSHwHack::OI_BigMuthaTruckers(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) -{ - // Rendering pattern: - // CRTC frontbuffer at 0x0 is interlaced (half vertical resolution), - // game needs to do a depth effect (so green channel to alpha), - // but there is a vram limitation so green is pushed into the alpha channel of the CRCT buffer, - // vertical resolution is half so only half is processed at once - // We, however, don't have this limitation so we'll replace the draw with a full-screen TS. - - const GIFRegTEX0& Texture = RTEX0; - - GIFRegTEX0 Frame = {}; - Frame.TBW = RFRAME.FBW; - Frame.TBP0 = RFRAME.Block(); - - if (RPRIM->TME && Frame.TBW == 10 && Texture.TBW == 10 && Frame.TBP0 == 0x00a00 && Texture.PSM == PSMT8H && (r.m_r.y == 256 || r.m_r.y == 224)) - { - // 224 ntsc, 256 pal. - GL_INS("OI_BigMuthaTruckers half bottom offset"); - - const size_t count = r.m_vertex.next; - GSVertex* v = &r.m_vertex.buff[0]; - const u16 offset = (u16)r.m_r.y * 16; - - for (size_t i = 0; i < count; i++) - v[i].V += offset; - } - - return true; -} - bool GSHwHack::OI_DBZBTGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { if (t && t->m_from_target) // Avoid slow framebuffer readback @@ -1457,6 +1464,7 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function // Texture shuffle CRC_F(GSC_DeathByDegreesTekkenNinaWilliams), // + Upscaling issues + CRC_F(GSC_BigMuthaTruckers), // Upscaling hacks CRC_F(GSC_UltramanFightingEvolution), @@ -1467,7 +1475,6 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function const GSHwHack::Entry GSHwHack::s_before_draw_functions[] = { CRC_F(OI_PointListPalette), - CRC_F(OI_BigMuthaTruckers), CRC_F(OI_DBZBTGames), CRC_F(OI_FFX), CRC_F(OI_RozenMaidenGebetGarden), diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.h b/pcsx2/GS/Renderers/HW/GSHwHack.h index ed44f7a75616f..79fcac8fd1f1b 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.h +++ b/pcsx2/GS/Renderers/HW/GSHwHack.h @@ -44,9 +44,9 @@ class GSHwHack static bool GSC_NFSUndercover(GSRendererHW& r, int& skip); static bool GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip); static bool GSC_MetalGearSolid3(GSRendererHW& r, int& skip); + static bool GSC_BigMuthaTruckers(GSRendererHW& r, int& skip); static bool OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); - static bool OI_BigMuthaTruckers(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); static bool OI_DBZBTGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); static bool OI_FFX(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); static bool OI_RozenMaidenGebetGarden(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 25c0bf313bdde..00fafc1808a74 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -337,7 +337,7 @@ void GSRendererHW::ExpandLineIndices() } // Fix the vertex position/tex_coordinate from 16 bits color to 32 bits color -void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) +void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, GSTextureCache::Target* rt, GSTextureCache::Source* tex) { const u32 count = m_vertex.next; GSVertex* v = &m_vertex.buff[0]; @@ -351,7 +351,8 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) const float tw = static_cast(1u << m_cached_ctx.TEX0.TW); int tex_pos = (PRIM->FST) ? first_vert.U : static_cast(tw * first_vert.ST.S); tex_pos &= 0xFF; - read_ba = (tex_pos > 112 && tex_pos < 144); + // "same group" means it can read blue and write alpha using C32 tricks + read_ba = (tex_pos > 112 && tex_pos < 144) || (m_same_group_texture_shuffle && (m_cached_ctx.FRAME.FBMSK & 0xFFFF0000) != 0xFFFF00000); // Another way of selecting whether to read RG/BA is to use region repeat. // Ace Combat 04 reads RG, writes to RGBA by setting a MINU of 1015. @@ -406,60 +407,51 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) return; } - bool half_bottom = false; - switch (GSConfig.UserHacks_HalfBottomOverride) + bool half_bottom_vert = true; + bool half_right_vert = true; + bool half_bottom_uv = true; + bool half_right_uv = true; + + if (m_same_group_texture_shuffle) { - case 0: - // Force Disabled. - // Force Disabled will help games such as Xenosaga. - // Xenosaga handles the half bottom as an vertex offset instead of a buffer offset which does the effect twice. - // Half bottom won't trigger a cache miss that skip the draw because it is still the normal buffer but with a vertices offset. - half_bottom = false; - break; - case 1: - // Force Enabled. - // Force Enabled will help games such as Superman Shadows of Apokolips, The Lord of the Rings: The Two Towers, - // Demon Stone, Midnight Club 3. - half_bottom = true; - break; - case -1: - default: - // Default, Automatic. - // Here's the idea - // TS effect is 16 bits but we emulate it on a 32 bits format - // Normally this means we need to divide size by 2. - // - // Some games do two TS effects on each half of the buffer. - // This makes a mess for us in the TC because we end up with two targets - // when we only want one, thus half screen bug. - // - // 32bits emulation means we can do the effect once but double the size. - // Test cases: Crash Twinsantiy and DBZ BT3 - // Test Case: NFS: HP2 splits the effect h:256 and h:192 so 64 - // Other games: Midnight Club 3 headlights, black bar in Xenosaga 3 dialogue, - // Firefighter FD18 fire occlusion, PSI Ops half screen green overlay, Lord of the Rings - Two Towers, - // Demon Stone , Sonic Unleashed, Lord of the Rings Two Towers, - // Superman Shadow of Apokolips, Matrix Path of Neo, Big Mutha Truckers - - int maxvert = 0; - int minvert = 4096; - for (u32 i = 0; i < count; i++) + if (m_cached_ctx.FRAME.FBW != rt->m_TEX0.TBW && m_cached_ctx.FRAME.FBW == rt->m_TEX0.TBW * 2) + half_right_vert = false; + else + half_bottom_vert = false; + } + else + { + // Different source (maybe?) + // If a game does the texture and frame doubling differently, they can burn in hell. + if (m_cached_ctx.TEX0.TBP0 != m_cached_ctx.FRAME.Block()) + { + // No super source of truth here, since the width can get batted around, the valid is probably our best bet. + int tex_width = tex->m_target ? tex->m_from_target->m_valid.z : (tex->m_TEX0.TBW * 64); + int tex_tbw = tex->m_target ? tex->m_from_target_TEX0.TBW : tex->m_TEX0.TBW; + if (static_cast(m_cached_ctx.TEX0.TBW * 64) >= std::min(tex_width * 2, 1024) && tex_tbw != m_cached_ctx.TEX0.TBW || (m_cached_ctx.TEX0.TBW * 64) < floor(m_vt.m_max.t.x)) { - int YCord = 0; - - if (!PRIM->FST) - YCord = static_cast((1 << m_cached_ctx.TEX0.TH) * (v[i].ST.T / v[i].RGBAQ.Q)); - else - YCord = (v[i].V >> 4); - - if (maxvert < YCord) - maxvert = YCord; - if (minvert > YCord) - minvert = YCord; + half_right_uv = false; + half_right_vert = false; } - - half_bottom = minvert == 0 && m_r.height() <= maxvert; - break; + else + { + half_bottom_uv = false; + half_bottom_vert = false; + } + } + else + { + if ((floor(m_vt.m_max.p.y) <= rt->m_valid.w) && ((floor(m_vt.m_max.p.x) > (m_cached_ctx.FRAME.FBW * 64)) || (rt->m_TEX0.TBW != m_cached_ctx.FRAME.FBW))) + { + half_right_vert = false; + half_right_uv = false; + } + else + { + half_bottom_vert = false; + half_bottom_uv = false; + } + } } if (PRIM->FST) @@ -469,16 +461,16 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) for (u32 i = 0; i < count; i += 2) { if (write_ba) - v[i].XYZ.X -= 128u; + v[i].XYZ.X -= 128u; else - v[i+1].XYZ.X += 128u; + v[i + 1].XYZ.X += 128u; if (read_ba) - v[i].U -= 128u; + v[i].U -= 128u; else - v[i+1].U += 128u; + v[i + 1].U += 128u; - if (!half_bottom) + if (!half_bottom_vert) { // Height is too big (2x). const int tex_offset = v[i].V & 0xF; @@ -488,9 +480,13 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) tmp = GSVector4i(tmp - offset).srl32(1) + offset; v[i].XYZ.Y = static_cast(tmp.x); - v[i].V = static_cast(tmp.y); v[i + 1].XYZ.Y = static_cast(tmp.z); - v[i + 1].V = static_cast(tmp.w); + + if (!half_bottom_uv) + { + v[i].V = static_cast(tmp.y); + v[i + 1].V = static_cast(tmp.w); + } } } } @@ -502,16 +498,16 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) for (u32 i = 0; i < count; i += 2) { if (write_ba) - v[i].XYZ.X -= 128u; + v[i].XYZ.X -= 128u; else - v[i+1].XYZ.X += 128u; + v[i + 1].XYZ.X += 128u; if (read_ba) - v[i].ST.S -= offset_8pix; + v[i].ST.S -= offset_8pix; else - v[i+1].ST.S += offset_8pix; + v[i + 1].ST.S += offset_8pix; - if (!half_bottom) + if (!half_bottom_vert) { // Height is too big (2x). const GSVector4i offset(o.OFY, o.OFY); @@ -521,9 +517,13 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) //fprintf(stderr, "Before %d, After %d\n", v[i + 1].XYZ.Y, tmp.y); v[i].XYZ.Y = static_cast(tmp.x); - v[i].ST.T /= 2.0f; v[i + 1].XYZ.Y = static_cast(tmp.y); - v[i + 1].ST.T /= 2.0f; + + if (!half_bottom_uv) + { + v[i].ST.T /= 2.0f; + v[i + 1].ST.T /= 2.0f; + } } } } @@ -534,21 +534,41 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba) else m_vt.m_max.p.x += 8.0f; - if (!half_bottom) + if (!m_same_group_texture_shuffle) { - const float delta_Y = m_vt.m_max.p.y - m_vt.m_min.p.y; - m_vt.m_max.p.y -= delta_Y / 2.0f; + if (read_ba) + m_vt.m_min.t.x -= 8.0f; + else + m_vt.m_max.t.x += 8.0f; } - if (read_ba) - m_vt.m_min.t.x -= 8.0f; - else - m_vt.m_max.t.x += 8.0f; + if (!half_right_vert) + { + m_vt.m_min.p.x /= 2.0f; + m_vt.m_max.p.x /= 2.0f; + m_context->scissor.in.x = m_vt.m_min.p.x; + m_context->scissor.in.z = m_vt.m_max.p.x + 8.0f; + } - if (!half_bottom) + if (!half_bottom_vert) { - const float delta_T = m_vt.m_max.t.y - m_vt.m_min.t.y; - m_vt.m_max.t.y -= delta_T / 2.0f; + m_vt.m_min.p.y /= 2.0f; + m_vt.m_max.p.y /= 2.0f; + m_context->scissor.in.y = m_vt.m_min.p.y; + m_context->scissor.in.w = m_vt.m_max.p.y + 8.0f; + } + + // Only do this is the source is being interpreted as 16bit + if (!half_bottom_uv) + { + m_vt.m_min.t.y /= 2.0f; + m_vt.m_max.t.y /= 2.0f; + } + + if (!half_right_uv) + { + m_vt.m_min.t.y /= 2.0f; + m_vt.m_max.t.y /= 2.0f; } } @@ -849,19 +869,22 @@ bool GSRendererHW::IsSplitTextureShuffle(u32 rt_tbw) // If this is a split texture shuffle, the next draw's FRAME/TEX0 should line up. // Re-add the offset we subtracted in Draw() to get the original FBP/TBP0.. this won't handle wrapping. Oh well. + // "Potential" ones are for Jak3 which does a split shuffle on a 128x128 texture with a width of 256, writing to the lower half then offsetting 2 pages. const u32 expected_next_FBP = (m_cached_ctx.FRAME.FBP + m_split_texture_shuffle_pages) + num_pages; + const u32 potential_expected_next_FBP = m_cached_ctx.FRAME.FBP + ((m_context->FRAME.FBW * 64) / aligned_rc.width()); const u32 expected_next_TBP0 = (m_cached_ctx.TEX0.TBP0 + (m_split_texture_shuffle_pages + num_pages) * BLOCKS_PER_PAGE); + const u32 potential_expected_next_TBP0 = m_cached_ctx.TEX0.TBP0 + (BLOCKS_PER_PAGE * ((m_context->TEX0.TBW * 64) / aligned_rc.width())); GL_CACHE("IsSplitTextureShuffle: Draw covers %ux%u pages, next FRAME %x TEX %x", static_cast(aligned_rc.width()) / frame_psm.pgs.x, pages_high, expected_next_FBP * BLOCKS_PER_PAGE, expected_next_TBP0); - if (next_ctx.TEX0.TBP0 != expected_next_TBP0) + if (next_ctx.TEX0.TBP0 != expected_next_TBP0 && next_ctx.TEX0.TBP0 != potential_expected_next_TBP0) { GL_CACHE("IsSplitTextureShuffle: Mismatch on TBP0, expecting %x, got %x", expected_next_TBP0, next_ctx.TEX0.TBP0); return false; } // Some games don't offset the FBP. - if (next_ctx.FRAME.FBP != expected_next_FBP && next_ctx.FRAME.FBP != m_cached_ctx.FRAME.FBP) + if (next_ctx.FRAME.FBP != expected_next_FBP && next_ctx.FRAME.FBP != m_cached_ctx.FRAME.FBP && next_ctx.FRAME.FBP != potential_expected_next_FBP) { GL_CACHE("IsSplitTextureShuffle: Mismatch on FBP, expecting %x, got %x", expected_next_FBP * BLOCKS_PER_PAGE, next_ctx.FRAME.FBP * BLOCKS_PER_PAGE); @@ -1904,6 +1927,7 @@ void GSRendererHW::Draw() m_texture_shuffle = false; m_copy_16bit_to_target_shuffle = false; + m_same_group_texture_shuffle = false; const bool is_split_texture_shuffle = (m_split_texture_shuffle_pages > 0); if (is_split_texture_shuffle) @@ -2167,9 +2191,30 @@ void GSRendererHW::Draw() GL_CACHE("Estimated texture region: %u,%u -> %u,%u", MIP_CLAMP.MINU, MIP_CLAMP.MINV, MIP_CLAMP.MAXU + 1, MIP_CLAMP.MAXV + 1); } - const bool possible_shuffle = m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0 || IsPossibleChannelShuffle(); - src = tex_psm.depth ? g_texture_cache->LookupDepthSource(TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, possible_shuffle, m_vt.IsLinear()) : - g_texture_cache->LookupSource(TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, (GSConfig.HWMipmap >= HWMipmapLevel::Basic || GSConfig.TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr, possible_shuffle, m_vt.IsLinear()); + + GIFRegTEX0 FRAME_TEX0; + bool rt_32bit = false; + if (!no_rt && m_cached_ctx.FRAME.Block() != m_cached_ctx.TEX0.TBP0 && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) + { + // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. + FRAME_TEX0.U64 = 0; + FRAME_TEX0.TBP0 = m_cached_ctx.FRAME.Block(); + FRAME_TEX0.TBW = m_cached_ctx.FRAME.FBW; + FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; + + GSTextureCache::Target* tgt = g_texture_cache->LookupTarget(FRAME_TEX0, GSVector2i(m_vt.m_max.p.x, m_vt.m_max.p.y), GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, + fm); + + if (tgt) + rt_32bit = tgt->m_32_bits_fmt; + + tgt = nullptr; + } + const bool possible_shuffle = ((rt_32bit && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) || m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0) || IsPossibleChannelShuffle(); + + src = tex_psm.depth ? g_texture_cache->LookupDepthSource(TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, possible_shuffle, m_vt.IsLinear(), m_cached_ctx.FRAME.Block()) : + g_texture_cache->LookupSource(TEX0, env.TEXA, MIP_CLAMP, tmm.coverage, (GSConfig.HWMipmap >= HWMipmapLevel::Basic || GSConfig.TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr, possible_shuffle, m_vt.IsLinear(), m_cached_ctx.FRAME.Block()); + if (unlikely(!src)) { GL_INS("ERROR: Source lookup failed, skipping."); @@ -2257,7 +2302,7 @@ void GSRendererHW::Draw() } rt = g_texture_cache->CreateTarget(FRAME_TEX0, t_size, GetValidSize(src), target_scale, GSTextureCache::RenderTarget, true, - fm, false, force_preload, preserve_rt_color, m_r); + fm, false, force_preload, preserve_rt_color, m_r, src); if (unlikely(!rt)) { GL_INS("ERROR: Failed to create FRAME target, skipping."); @@ -2281,7 +2326,7 @@ void GSRendererHW::Draw() if (!ds) { ds = g_texture_cache->CreateTarget(ZBUF_TEX0, t_size, GetValidSize(src), target_scale, GSTextureCache::DepthStencil, - m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, m_r); + m_cached_ctx.DepthWrite(), 0, false, force_preload, preserve_depth, m_r, src); if (unlikely(!ds)) { GL_INS("ERROR: Failed to create ZBUF target, skipping."); @@ -2294,21 +2339,36 @@ void GSRendererHW::Draw() if (process_texture) { GIFRegCLAMP MIP_CLAMP = m_cached_ctx.CLAMP; - + const u32 draw_end = GSLocalMemory::GetEndBlockAddress(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r)+1; + const bool draw_uses_target = src->m_from_target && ((src->m_from_target_TEX0.TBP0 <= m_cached_ctx.FRAME.Block() && + src->m_from_target->UnwrappedEndBlock() > m_cached_ctx.FRAME.Block()) || + (m_cached_ctx.FRAME.Block() < src->m_from_target_TEX0.TBP0 && draw_end > src->m_from_target_TEX0.TBP0)); + if (rt) { // copy of a 16bit source in to this target, make sure it's opaque and not bilinear to reduce false positives. m_copy_16bit_to_target_shuffle = m_cached_ctx.TEX0.TBP0 != m_cached_ctx.FRAME.Block() && rt->m_32_bits_fmt == true && IsOpaque() && !(context->TEX1.MMIN & 1) && !src->m_32_bits_fmt && m_cached_ctx.FRAME.FBMSK; - } + // It's not actually possible to do a C16->C16 texture shuffle of B to A as they are the same group + // However you can do it by using C32 and offsetting the target verticies to point to B A, then mask as appropriate. + m_same_group_texture_shuffle = draw_uses_target && (m_cached_ctx.TEX0.PSM & 0xE) == PSMCT32 && (m_cached_ctx.FRAME.PSM & 0x7) == PSMCT16 && (m_vt.m_min.p.x == 8.0f); + } + const GSVertex* v = &m_vertex.buff[0]; // Hypothesis: texture shuffle is used as a postprocessing effect so texture will be an old target. // Initially code also tested the RT but it gives too much false-positive - // - // Both input and output are 16 bits and texture was initially 32 bits! - m_texture_shuffle = (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) && (tex_psm.bpp == 16) + const int first_x = (v[0].XYZ.X + 8) >> 4; + const int first_u = PRIM->FST ? ((v[0].U + 8) >> 4) : static_cast(((1 << m_cached_ctx.TEX0.TW) * (v[0].ST.S / v[1].RGBAQ.Q)) + 0.5f); + const bool shuffle_coords = (first_x ^ first_u) & 8; + // Both input and output are 16 bits and texture was initially 32 bits! Same for the target, Sonic Unleash makes a new target which really is 16bit. + m_texture_shuffle = ((m_same_group_texture_shuffle || (tex_psm.bpp == 16)) && (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) && + (shuffle_coords || rt->m_32_bits_fmt)) + && draw_sprite_tex && (src->m_32_bits_fmt || m_copy_16bit_to_target_shuffle); + /* const bool old_shuffle = ((m_same_group_texture_shuffle || (tex_psm.bpp == 16)) && (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16)) && draw_sprite_tex && (src->m_32_bits_fmt || m_copy_16bit_to_target_shuffle); + if (old_shuffle && !m_texture_shuffle) + DevCon.Warning("Here draw %d", s_n);*/ // Okami mustn't call this code if (m_texture_shuffle && m_vertex.next < 3 && PRIM->FST && ((m_cached_ctx.FRAME.FBMSK & fm_mask) == 0)) { @@ -2318,7 +2378,6 @@ void GSRendererHW::Draw() // Shadow of Memories/Destiny shouldn't call this code. // Causes shadow flickering. - const GSVertex* v = &m_vertex.buff[0]; m_texture_shuffle = ((v[1].U - v[0].U) < 256) || // Tomb Raider Angel of Darkness relies on this behavior to produce a fog effect. // In this case, the address of the framebuffer and texture are the same. @@ -2509,9 +2568,23 @@ void GSRendererHW::Draw() GSTextureCache::Target* old_rt = nullptr; GSTextureCache::Target* old_ds = nullptr; { + GSVector2i new_size = t_size; + + // We need to adjust the size if it's a texture shuffle as we could end up making the RT twice the size. + if (rt && m_texture_shuffle && m_split_texture_shuffle_pages == 0) + { + if (new_size.x > rt->m_valid.z || new_size.y > rt->m_valid.w) + { + if (new_size.y <= rt->m_valid.w && (rt->m_TEX0.TBW != m_cached_ctx.FRAME.FBW)) + new_size.x /= 2; + else + new_size.y /= 2; + } + } + // We still need to make sure the dimensions of the targets match. - const int new_w = std::max(t_size.x, std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds->m_unscaled_size.x : 0)); - const int new_h = std::max(t_size.y, std::max(rt ? rt->m_unscaled_size.y : 0, ds ? ds->m_unscaled_size.y : 0)); + const int new_w = std::max(new_size.x, std::max(rt ? rt->m_unscaled_size.x : 0, ds ? ds->m_unscaled_size.x : 0)); + const int new_h = std::max(new_size.y, std::max(rt ? rt->m_unscaled_size.y : 0, ds ? ds->m_unscaled_size.y : 0)); if (rt) { const u32 old_end_block = rt->m_end_block; @@ -2522,6 +2595,7 @@ void GSRendererHW::Draw() pxAssert(rt->GetScale() == target_scale); if (rt->GetUnscaledWidth() != new_w || rt->GetUnscaledHeight() != new_h) GL_INS("Resize RT from %dx%d to %dx%d", rt->GetUnscaledWidth(), rt->GetUnscaledHeight(), new_w, new_h); + rt->ResizeTexture(new_w, new_h); if (!m_texture_shuffle && !m_channel_shuffle) @@ -2531,8 +2605,8 @@ void GSRendererHW::Draw() } // Limit to 2x the vertical height of the resolution (for double buffering) - rt->UpdateValidity(m_r, can_update_size || m_r.w <= (resolution.y * 2)); - rt->UpdateDrawn(m_r, can_update_size || m_r.w <= (resolution.y * 2)); + rt->UpdateValidity(m_r, can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle)); + rt->UpdateDrawn(m_r, can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle)); // Probably changing to double buffering, so invalidate any old target that was next to it. // This resolves an issue where the PCRTC will find the old target in FMV's causing flashing. // Grandia Xtreme, Onimusha Warlord. @@ -2735,7 +2809,7 @@ void GSRendererHW::Draw() { //rt->m_valid = rt->m_valid.runion(r); // Limit to 2x the vertical height of the resolution (for double buffering) - rt->UpdateValidity(m_r, can_update_size || m_r.w <= (resolution.y * 2)); + rt->UpdateValidity(m_r, can_update_size || (m_r.w <= (resolution.y * 2) && !m_texture_shuffle)); g_texture_cache->InvalidateVideoMem(context->offset.fb, m_r, false); @@ -2748,7 +2822,7 @@ void GSRendererHW::Draw() { //ds->m_valid = ds->m_valid.runion(r); // Limit to 2x the vertical height of the resolution (for double buffering) - ds->UpdateValidity(m_r, can_update_size || m_r.w <= (resolution.y * 2)); + ds->UpdateValidity(m_r, can_update_size || m_r.w <= (resolution.y * 2) && !m_texture_shuffle); g_texture_cache->InvalidateVideoMem(context->offset.zb, m_r, false); @@ -2996,7 +3070,7 @@ void GSRendererHW::EmulateZbuffer(const GSTextureCache::Target* ds) } } -void GSRendererHW::EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt) +void GSRendererHW::EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex) { // Uncomment to disable texture shuffle emulation. // m_texture_shuffle = false; @@ -3039,7 +3113,7 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt) bool write_ba; bool read_ba; - ConvertSpriteTextureShuffle(write_ba, read_ba); + ConvertSpriteTextureShuffle(write_ba, read_ba, rt, tex); // If date is enabled you need to test the green channel instead of the // alpha channel. Only enable this code in DATE mode to reduce the number @@ -3048,6 +3122,7 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt) m_conf.ps.read_ba = read_ba; m_conf.ps.real16src = m_copy_16bit_to_target_shuffle; + m_conf.ps.shuffle_same = m_same_group_texture_shuffle; // Please bang my head against the wall! // 1/ Reduce the frame mask to a 16 bit format const u32 m = m_cached_ctx.FRAME.FBMSK & GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk; @@ -4101,7 +4176,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt, // Force a 32 bits access (normally shuffle is done on 16 bits) // m_ps_sel.tex_fmt = 0; // removed as an optimization m_conf.ps.aem = TEXA.AEM; - ASSERT(tex->m_target); + //ASSERT(tex->m_target); // Require a float conversion if the texure is a depth otherwise uses Integral scaling if (psm.depth) @@ -4669,7 +4744,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_prim_overlap = PrimitiveOverlap(); - EmulateTextureShuffleAndFbmask(rt); + EmulateTextureShuffleAndFbmask(rt, tex); const GSDevice::FeatureSupport features = g_gs_device->Features(); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index e83dff1b0843d..1bf9ffddabb63 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -86,7 +86,7 @@ class GSRendererHW : public GSRenderer void ResetStates(); void SetupIA(float target_scale, float sx, float sy); - void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt); + void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex); bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only); void EmulateBlending(int rt_alpha_min, int rt_alpha_max, bool& DATE_PRIMID, bool& DATE_BARRIER, bool& blending_alpha_pass); @@ -194,7 +194,7 @@ class GSRendererHW : public GSRenderer void Lines2Sprites(); bool VerifyIndices(); void ExpandLineIndices(); - void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba); + void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba, GSTextureCache::Target* rt, GSTextureCache::Source* tex); GSVector4 RealignTargetTextureCoordinate(const GSTextureCache::Source* tex); GSVector4i ComputeBoundingBox(const GSVector2i& rtsize, float rtscale); void MergeSprite(GSTextureCache::Source* tex); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 2f17c7436db94..f69d1c67f4446 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -586,7 +586,7 @@ __ri static GSTextureCache::Source* FindSourceInMap(const GIFRegTEX0& TEX0, cons return nullptr; } -GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const bool possible_shuffle, const bool linear, bool palette) +GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const bool possible_shuffle, const bool linear, const u32 frame_fbp, bool palette) { if (GSConfig.UserHacks_DisableDepthSupport) { @@ -700,7 +700,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0 { for (Target* t : m_dst[RenderTarget]) { - if (t->m_age <= 1 && t->m_TEX0.TBP0 == bp && t->HasValidAlpha()) + if (t->m_age <= 1 && t->m_TEX0.TBP0 == bp && t->m_TEX0.TBW == TEX0.TBW && t->HasValidAlpha()) { GL_CACHE("TC depth: Using RT %x instead of depth because of missing alpha", t->m_TEX0.TBP0); @@ -776,7 +776,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0 else { // This is a bit of a worry, since it could load junk from local memory... but it's better than skipping the draw. - return LookupSource(TEX0, TEXA, CLAMP, r, nullptr, possible_shuffle, linear); + return LookupSource(TEX0, TEXA, CLAMP, r, nullptr, possible_shuffle, linear, frame_fbp); } ASSERT(src->m_texture); @@ -785,7 +785,7 @@ GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0 return src; } -GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const GSVector2i* lod, const bool possible_shuffle, const bool linear) +GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const GSVector2i* lod, const bool possible_shuffle, const bool linear, const u32 frame_fbp) { GL_CACHE("TC: Lookup Source <%d,%d => %d,%d> (0x%x, %s, BW: %u, CBP: 0x%x, TW: %d, TH: %d)", r.x, r.y, r.z, r.w, TEX0.TBP0, psm_str(TEX0.PSM), TEX0.TBW, TEX0.CBP, 1 << TEX0.TW, 1 << TEX0.TH); @@ -1031,15 +1031,38 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con // Make sure the texture actually is INSIDE the RT, it's possibly not valid if it isn't. // Also check BP >= TBP, create source isn't equpped to expand it backwards and all data comes from the target. (GH3) else if (GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && psm >= PSMCT32 && - psm <= PSMCT16S && GSUtil::HasCompatibleBits(t->m_TEX0.PSM, psm) && (t->Overlaps(bp, bw, psm, r) || t->Wraps()) && - t->m_age <= 1 && (!found_t || dst->m_TEX0.TBW < bw)) + psm <= PSMCT16S && (GSUtil::HasCompatibleBits(t->m_TEX0.PSM, psm) || + (possible_shuffle && t->m_TEX0.PSM <= PSMCT24 && ((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) == bp)) + && (t->Overlaps(bp, bw, psm, r) || t->Wraps()) && + t->m_age <= 1 && (!found_t || dst->m_TEX0.TBW < bw)) { // PSM equality needed because CreateSource does not handle PSM conversion. // Only inclusive hit to limit false hits. + GSVector4i rect = r; + int src_bw = bw; + int src_psm = psm; + // If the input is C16 and it's actually a shuffle of 32bits we need to correct the size. + if ((t->m_TEX0.PSM & 0xF) == PSMCT32 && (psm & 0x7) == PSMCT16 && possible_shuffle) + { + src_psm = t->m_TEX0.PSM; + // If it's taking double width for the shuffle, half that. + if (src_bw == (t->m_TEX0.TBW * 2)) + { + src_bw = t->m_TEX0.TBW; + + rect.x /= 2; + rect.z /= 2; + } + else + { + rect.y /= 2; + rect.w /= 2; + } + } if (bp > t->m_TEX0.TBP0) { - GSVector4i new_rect = r; + GSVector4i new_rect = rect; if (linear) { new_rect.z -= 1; @@ -1052,10 +1075,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con const bool can_translate = CanTranslate(bp, bw, psm, new_rect, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW); if (can_translate) { - const bool swizzle_match = GSLocalMemory::m_psm[psm].depth == GSLocalMemory::m_psm[t->m_TEX0.PSM].depth; + const bool swizzle_match = GSLocalMemory::m_psm[src_psm].depth == GSLocalMemory::m_psm[t->m_TEX0.PSM].depth; const GSVector2i& page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs; const GSVector4i page_mask(GSVector4i((page_size.x - 1), (page_size.y - 1)).xyxy()); - GSVector4i rect = new_rect & ~page_mask; + rect = new_rect & ~page_mask; if (swizzle_match) { @@ -1068,18 +1091,18 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con // If it's not page aligned, grab the whole pages it covers, to be safe. if (GSLocalMemory::m_psm[psm].bpp != GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp) { - const GSVector2i& dst_page_size = GSLocalMemory::m_psm[psm].pgs; - rect = GSVector4i(rect.x / page_size.x, rect.y / page_size.y, (rect.z + (page_size.x - 1)) / page_size.x, (rect.w + (page_size.y - 1)) / page_size.y); - rect = GSVector4i(rect.x * dst_page_size.x, rect.y * dst_page_size.y, rect.z * dst_page_size.x, rect.w * dst_page_size.y); + const GSVector2i& dst_page_size = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs; + new_rect = GSVector4i(new_rect.x / page_size.x, new_rect.y / page_size.y, (new_rect.z + (page_size.x - 1)) / page_size.x, (new_rect.w + (page_size.y - 1)) / page_size.y); + new_rect = GSVector4i(new_rect.x * dst_page_size.x, new_rect.y * dst_page_size.y, new_rect.z * dst_page_size.x, new_rect.w * dst_page_size.y); } else { - rect.x &= ~(page_size.x - 1); - rect.y &= ~(page_size.y - 1); - rect.z = (new_rect.z + (page_size.x - 1)) & ~(page_size.x - 1); - rect.w = (new_rect.w + (page_size.y - 1)) & ~(page_size.y - 1); + new_rect.x &= ~(page_size.x - 1); + new_rect.y &= ~(page_size.y - 1); + new_rect.z = (new_rect.z + (page_size.x - 1)) & ~(page_size.x - 1); + new_rect.w = (new_rect.w + (page_size.y - 1)) & ~(page_size.y - 1); } - rect = TranslateAlignedRectByPage(t, bp & ~((1 << 5) - 1), psm, bw, rect); + rect = TranslateAlignedRectByPage(t, bp & ~((1 << 5) - 1), psm, bw, new_rect); rect.x -= new_rect.x & ~(page_size.y - 1); rect.y -= new_rect.x & ~(page_size.y - 1); } @@ -1107,11 +1130,14 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con dst = t; tex_merge_rt = false; found_t = true; - continue; + if (dst->m_TEX0.TBP0 == frame_fbp && possible_shuffle) + break; + else + continue; } else { - SurfaceOffset so = ComputeSurfaceOffset(bp, bw, psm, r, t); + SurfaceOffset so = ComputeSurfaceOffset(bp, bw, psm, new_rect, t); if (!so.is_valid && t->Wraps()) { // Improves Beyond Good & Evil shadow. @@ -1127,14 +1153,12 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con tex_merge_rt = false; found_t = true; // Keep looking, just in case there is an exact match (Situation: Target frame drawn inside target frame, current makes a separate texture) - continue; + if (dst->m_TEX0.TBP0 == frame_fbp && possible_shuffle) + break; + else + continue; } } - if (linear) - { - new_rect.z += 1; - new_rect.w += 1; - } } else { @@ -1161,7 +1185,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con dst = t; tex_merge_rt = false; found_t = true; - continue; + if (dst->m_TEX0.TBP0 == frame_fbp && possible_shuffle) + break; + else + continue; } // Strictly speaking this path is no longer needed, but I'm leaving it here for now because Guitar @@ -1175,7 +1202,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con // Prefer a target inside over a target outside. found_t = false; - continue; + if (dst->m_TEX0.TBP0 == frame_fbp && possible_shuffle) + break; + else + continue; } } } @@ -1208,11 +1238,11 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con GIFRegTEX0 depth_TEX0; depth_TEX0.U32[0] = TEX0.U32[0] | (0x30u << 20u); depth_TEX0.U32[1] = TEX0.U32[1]; - return LookupDepthSource(depth_TEX0, TEXA, CLAMP, r, possible_shuffle, linear); + return LookupDepthSource(depth_TEX0, TEXA, CLAMP, r, possible_shuffle, linear, frame_fbp); } else { - return LookupDepthSource(TEX0, TEXA, CLAMP, r, possible_shuffle, linear, true); + return LookupDepthSource(TEX0, TEXA, CLAMP, r, possible_shuffle, linear, frame_fbp, true); } } } @@ -1311,6 +1341,15 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe if (bp == t->m_TEX0.TBP0) { + // if It's an old target and it's being completely overwritten, kill it. + if (!preserve_rgb && !preserve_alpha && TEX0.TBW != t->m_TEX0.TBW && TEX0.TBW > 1 && t->m_age >= 1) + { + GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to width change to %d", t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, TEX0.TBW); + InvalidateSourcesFromTarget(t); + i = list.erase(i); + delete t; + continue; + } list.MoveFront(i.Index()); dst = t; @@ -1512,7 +1551,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst_match = t; } } - + // We only want to use a matched target if it's actually being used. if (dst_match) { calcRescale(dst_match); @@ -1537,6 +1576,9 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_valid_alpha_high = dst_match->m_valid_alpha_high && psm_s.trbpp != 24; dst->m_valid_rgb = dst_match->m_valid_rgb; + if(GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[dst_match->m_TEX0.PSM].bpp > 16) + dst->m_TEX0.TBW = dst_match->m_TEX0.TBW; // Be careful of shuffles of the depth as C16, but using a buffer width of 16 (Mercenaries). + ShaderConvert shader; // m_32_bits_fmt gets set on a shuffle or if the format isn't 16bit. // In this case it needs to make sure it isn't part of a shuffle, where it needs to be interpreted as 32bits. @@ -1616,7 +1658,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size, float scale, int type, - bool used, u32 fbmask, bool is_frame, bool preload, bool preserve_target, const GSVector4i draw_rect) + bool used, u32 fbmask, bool is_frame, bool preload, bool preserve_target, const GSVector4i draw_rect, GSTextureCache::Source* src) { if (type == DepthStencil) { @@ -1631,7 +1673,7 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe Target* dst = Target::Create(TEX0, size.x, size.y, scale, type, true); - PreloadTarget(TEX0, size, valid_size, is_frame, preload, preserve_target, draw_rect, dst);; + PreloadTarget(TEX0, size, valid_size, is_frame, preload, preserve_target, draw_rect, dst, src); dst->m_is_frame = is_frame; @@ -1654,7 +1696,7 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe } void GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size, bool is_frame, - bool preload, bool preserve_target, const GSVector4i draw_rect, Target* dst) + bool preload, bool preserve_target, const GSVector4i draw_rect, Target* dst, GSTextureCache::Source* src) { // In theory new textures contain invalidated data. Still in theory a new target // must contains the content of the GS memory. @@ -1786,17 +1828,62 @@ void GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons auto j = i; Target* t = *j; - // could be overwriting a double buffer, so if it's the second half of it, just reduce the size down to half. - if (dst != t && t->m_TEX0.TBW == dst->m_TEX0.TBW && - t->m_TEX0.PSM == dst->m_TEX0.PSM && - ((((t->m_end_block + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) == dst->m_TEX0.TBP0) - { - //DevCon.Warning("Found one %x->%x BW %d PSM %x (new target %x->%x BW %d PSM %x)", t->m_TEX0.TBP0, t->m_end_block, t->m_TEX0.TBW, t->m_TEX0.PSM, dst->m_TEX0.TBP0, dst->m_end_block, dst->m_TEX0.TBW, dst->m_TEX0.PSM); - GSVector4i new_valid = t->m_valid; - new_valid.w /= 2; - t->ResizeValidity(new_valid); - return; - } + if (dst != t && t->m_TEX0.TBW == dst->m_TEX0.TBW && t->m_TEX0.PSM == dst->m_TEX0.PSM) + if(t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid)) + { + // could be overwriting a double buffer, so if it's the second half of it, just reduce the size down to half. + if (((((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) == dst->m_TEX0.TBP0) + { + GSVector4i new_valid = t->m_valid; + new_valid.w /= 2; + GL_INS("RT resize buffer for FBP 0x%x, %dx%d => %d,%d", t->m_TEX0.TBP0, t->m_valid.width(), t->m_valid.height(), new_valid.width(), new_valid.height()); + t->ResizeValidity(new_valid); + return; + } + // The new texture is behind it but engulfs the whole thing, shrink the new target so it grows in the HW Draw resize. + else if (((((dst->UnwrappedEndBlock() + 1) - dst->m_TEX0.TBP0) >> 1) + dst->m_TEX0.TBP0) == t->m_TEX0.TBP0) + { + if (dst->m_TEX0.TBW == 2) + { + i++; + continue; + } + int overlapping_pages = ((dst->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5; + int y_reduction = (overlapping_pages / dst->m_TEX0.TBW) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y; + + if (y_reduction == 0 || (overlapping_pages % dst->m_TEX0.TBW)) + { + i++; + continue; + } + + const int copy_width = (t->m_texture->GetWidth()) > (dst->m_texture->GetWidth()) ? (dst->m_texture->GetWidth()) : t->m_texture->GetWidth(); + const int copy_height = y_reduction * t->m_scale; + const int old_height = (dst->m_valid.w - y_reduction) * dst->m_scale; + GL_INS("RT double buffer copy from FBP 0x%x, %dx%d => %d,%d", t->m_TEX0.TBP0, copy_width, copy_height, 0, old_height); + + // Clear the dirty first + dst->Update(); + // Invalidate has been moved to after DrawPrims(), because we might kill the current sources' backing. + g_gs_device->CopyRect(t->m_texture, dst->m_texture, GSVector4i(0, 0, copy_width, copy_height), 0, old_height); + if (src && src->m_target && src->m_from_target == t) + { + // This should never happen as we're making a new target so the src should never be something it overlaps, but just incase.. + GSVector4i new_valid = t->m_valid; + new_valid.y = std::max(new_valid.y - y_reduction, 0); + new_valid.w = std::max(new_valid.w - y_reduction, 0); + t->m_TEX0.TBP0 += (y_reduction / GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y) << 5; + t->ResizeValidity(new_valid); + } + else + { + InvalidateSourcesFromTarget(t); + i = list.erase(j); + delete t; + } + return; + } + } i++; } } @@ -3702,6 +3789,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con src->m_alpha_minmax.second = (using_both ? std::max(TEXA.TA1, TEXA.TA0) : (using_ta1 ? TEXA.TA1 : TEXA.TA0)); } } + src->m_32_bits_fmt = dst->m_32_bits_fmt; if (psm.pal > 0) { @@ -3762,6 +3850,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con src->m_alpha_minmax.second = (using_both ? std::max(TEXA.TA1, TEXA.TA0) : (using_ta1 ? TEXA.TA1 : TEXA.TA0)); } } + src->m_32_bits_fmt = dst->m_32_bits_fmt; dst->Update(); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 3831ac2f4016b..0d0a08b37e6ad 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -424,7 +424,7 @@ class GSTextureCache Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t, bool half_right, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region); void PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size, bool is_frame, - bool preload, bool preserve_target, const GSVector4i draw_rect, Target* dst); + bool preload, bool preserve_target, const GSVector4i draw_rect, Target* dst, GSTextureCache::Source* src = nullptr); // Returns scaled texture size. static GSVector2i ScaleRenderTargetSize(const GSVector2i& sz, float scale); @@ -473,8 +473,8 @@ class GSTextureCache GSTexture* LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2i& offset, float* scale, const GSVector2i& size); std::shared_ptr LookupPaletteObject(const u32* clut, u16 pal, bool need_gs_texture); - Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const GSVector2i* lod, const bool possible_shuffle, const bool linear); - Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const bool possible_shuffle, const bool linear, bool palette = false); + Source* LookupSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const GSVector2i* lod, const bool possible_shuffle, const bool linear, const u32 frame_fbp = 0xFFFFFFFF); + Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const bool possible_shuffle, const bool linear, const u32 frame_fbp = 0xFFFFFFFF, bool palette = false); Target* FindTargetOverlap(Target* target, int type, int psm); Target* LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, bool used = true, u32 fbmask = 0, @@ -482,7 +482,7 @@ class GSTextureCache const GSVector4i draw_rc = GSVector4i::zero()); Target* CreateTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size,float scale, int type, bool used = true, u32 fbmask = 0, bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_target = true, - const GSVector4i draw_rc = GSVector4i::zero()); + const GSVector4i draw_rc = GSVector4i::zero(), GSTextureCache::Source* src = nullptr); Target* LookupDisplayTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale); /// Looks up a target in the cache, and only returns it if the BP/BW match exactly. diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index 8a0855f14a780..b43160cec129e 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -1803,6 +1803,7 @@ static GSMTLExpandType ConvertVSExpand(GSHWDrawConfig::VSExpand generic) setFnConstantB(m_fn_constants, pssel.adjt, GSMTLConstantIndex_PS_ADJT); setFnConstantB(m_fn_constants, pssel.ltf, GSMTLConstantIndex_PS_LTF); setFnConstantB(m_fn_constants, pssel.shuffle, GSMTLConstantIndex_PS_SHUFFLE); + setFnConstantB(m_fn_constants, pssel.shuffle_same, GSMTLConstantIndex_PS_SHUFFLE_SAME); setFnConstantB(m_fn_constants, pssel.read_ba, GSMTLConstantIndex_PS_READ_BA); setFnConstantB(m_fn_constants, pssel.real16src, GSMTLConstantIndex_PS_READ16_SRC); setFnConstantB(m_fn_constants, pssel.write_rg, GSMTLConstantIndex_PS_WRITE_RG); diff --git a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h index d36473c5286e4..d8818034dbca8 100644 --- a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h +++ b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h @@ -182,6 +182,7 @@ enum GSMTLFnConstants GSMTLConstantIndex_PS_ADJT, GSMTLConstantIndex_PS_LTF, GSMTLConstantIndex_PS_SHUFFLE, + GSMTLConstantIndex_PS_SHUFFLE_SAME, GSMTLConstantIndex_PS_READ_BA, GSMTLConstantIndex_PS_READ16_SRC, GSMTLConstantIndex_PS_WRITE_RG, diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index e16e7f43f6951..ebe38f63346d9 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -41,6 +41,7 @@ constant bool PS_ADJS [[function_constant(GSMTLConstantIndex_PS_AD constant bool PS_ADJT [[function_constant(GSMTLConstantIndex_PS_ADJT)]]; constant bool PS_LTF [[function_constant(GSMTLConstantIndex_PS_LTF)]]; constant bool PS_SHUFFLE [[function_constant(GSMTLConstantIndex_PS_SHUFFLE)]]; +constant bool PS_SHUFFLE_SAME [[function_constant(GSMTLConstantIndex_PS_SHUFFLE_SAME)]]; constant bool PS_READ_BA [[function_constant(GSMTLConstantIndex_PS_READ_BA)]]; constant bool PS_READ16_SRC [[function_constant(GSMTLConstantIndex_PS_READ16_SRC)]]; constant bool PS_WRITE_RG [[function_constant(GSMTLConstantIndex_PS_WRITE_RG)]]; @@ -1021,21 +1022,37 @@ struct PSMain uint4 denorm_c = uint4(C); uint2 denorm_TA = uint2(cb.ta * 255.5f); - if (PS_READ16_SRC) + if (PS_SHUFFLE_SAME) { - C.rb = (denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5); - if (denorm_c.a & 0x80) - C.ga = (denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80); + if (PS_READ_BA) + { + C.ga = (denorm_c.b & 0x7Fu) | (denorm_c.a & 0x80); + C.rb = C.ga; + } else - C.ga = (denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80); + { + C.ga = C.rg; + C.rb = C.ga; + } } else { - C.rb = PS_READ_BA ? C.bb : C.rr; - if (PS_READ_BA) - C.ga = (denorm_c.a & 0x7F) | (denorm_c.a & 0x80 ? denorm_TA.y & 0x80 : denorm_TA.x & 0x80); + if (PS_READ16_SRC) + { + C.rb = (denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5); + if (denorm_c.a & 0x80) + C.ga = (denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80); + else + C.ga = (denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80); + } else - C.ga = (denorm_c.g & 0x7F) | (denorm_c.g & 0x80 ? denorm_TA.y & 0x80 : denorm_TA.x & 0x80); + { + C.rb = PS_READ_BA ? C.bb : C.rr; + if (PS_READ_BA) + C.ga = (denorm_c.a & 0x7F) | (denorm_c.a & 0x80 ? denorm_TA.y & 0x80 : denorm_TA.x & 0x80); + else + C.ga = (denorm_c.g & 0x7F) | (denorm_c.g & 0x80 ? denorm_TA.y & 0x80 : denorm_TA.x & 0x80); + } } } diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index a90bdfc5ee52a..dc2d9e787711b 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -1378,6 +1378,7 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel) + fmt::format("#define PS_BLEND_D {}\n", sel.blend_d) + fmt::format("#define PS_IIP {}\n", sel.iip) + fmt::format("#define PS_SHUFFLE {}\n", sel.shuffle) + + fmt::format("#define PS_SHUFFLE_SAME {}\n", sel.shuffle_same) + fmt::format("#define PS_READ_BA {}\n", sel.read_ba) + fmt::format("#define PS_READ16_SRC {}\n", sel.real16src) + fmt::format("#define PS_WRITE_RG {}\n", sel.write_rg) diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 49aaebaafc6ca..0a201c79aae32 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -4669,6 +4669,7 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector AddMacro(ss, "PS_FIXED_ONE_A", sel.fixed_one_a); AddMacro(ss, "PS_IIP", sel.iip); AddMacro(ss, "PS_SHUFFLE", sel.shuffle); + AddMacro(ss, "PS_SHUFFLE_SAME", sel.shuffle_same); AddMacro(ss, "PS_READ_BA", sel.read_ba); AddMacro(ss, "PS_READ16_SRC", sel.real16src); AddMacro(ss, "PS_WRITE_RG", sel.write_rg); diff --git a/pcsx2/GameDatabase.cpp b/pcsx2/GameDatabase.cpp index 361e3b47d91b0..3857df9be0b3e 100644 --- a/pcsx2/GameDatabase.cpp +++ b/pcsx2/GameDatabase.cpp @@ -601,9 +601,6 @@ bool GameDatabaseSchema::GameEntry::configMatchesHWFix(const Pcsx2Config::GSOpti case GSHWFixId::SkipDrawEnd: return (config.SkipDrawEnd == value); - case GSHWFixId::HalfBottomOverride: - return (config.UserHacks_HalfBottomOverride == value); - case GSHWFixId::HalfPixelOffset: return (config.UpscaleMultiplier <= 1.0f || config.UserHacks_HalfPixelOffset == value); @@ -782,10 +779,6 @@ void GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions& config.SkipDrawEnd = value; break; - case GSHWFixId::HalfBottomOverride: - config.UserHacks_HalfBottomOverride = value; - break; - case GSHWFixId::HalfPixelOffset: config.UserHacks_HalfPixelOffset = value; break; diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 6d713f5e48f68..7e3ad31b29de0 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -3232,8 +3232,6 @@ void FullscreenUI::DrawGraphicsSettingsPage() static constexpr const char* s_auto_flush_options[] = { "Disabled (Default)", "Enabled (Sprites Only)", "Enabled (All Primitives)"}; - DrawIntListSetting(bsi, "Half-Bottom Override", "Control the half-screen fix detection on texture shuffling.", "EmuCore/GS", - "UserHacks_Half_Bottom_Override", -1, s_generic_options, std::size(s_generic_options), -1); DrawIntListSetting(bsi, "CPU Sprite Render Size", "Uses software renderer to draw texture decompression-like sprites.", "EmuCore/GS", "UserHacks_CPUSpriteRenderBW", 0, s_cpu_sprite_render_bw_options, std::size(s_cpu_sprite_render_bw_options)); DrawIntListSetting(bsi, "CPU Sprite Render Level", "Determines filter level for CPU sprite render.", "EmuCore/GS", diff --git a/pcsx2/ImGui/ImGuiOverlays.cpp b/pcsx2/ImGui/ImGuiOverlays.cpp index 554fd339f2758..8be999c011a5f 100644 --- a/pcsx2/ImGui/ImGuiOverlays.cpp +++ b/pcsx2/ImGui/ImGuiOverlays.cpp @@ -390,8 +390,6 @@ void ImGuiManager::DrawSettingsOverlay() APPEND("AF={} ", EmuConfig.GS.MaxAnisotropy); if (GSConfig.Dithering != 2) APPEND("DI={} ", GSConfig.Dithering); - if (GSConfig.UserHacks_HalfBottomOverride >= 0) - APPEND("HBO={} ", GSConfig.UserHacks_HalfBottomOverride); if (GSConfig.UserHacks_HalfPixelOffset > 0) APPEND("HPO={} ", GSConfig.UserHacks_HalfPixelOffset); if (GSConfig.UserHacks_RoundSprite > 0) diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 641ed69101113..87b67e80e1d83 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -616,7 +616,6 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const OpEqu(SkipDrawStart) && OpEqu(UserHacks_AutoFlush) && - OpEqu(UserHacks_HalfBottomOverride) && OpEqu(UserHacks_HalfPixelOffset) && OpEqu(UserHacks_RoundSprite) && OpEqu(UserHacks_TCOffsetX) && @@ -814,7 +813,6 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) GSSettingIntEx(SkipDrawEnd, "UserHacks_SkipDraw_End"); SkipDrawEnd = std::max(SkipDrawStart, SkipDrawEnd); - GSSettingIntEx(UserHacks_HalfBottomOverride, "UserHacks_Half_Bottom_Override"); GSSettingIntEx(UserHacks_HalfPixelOffset, "UserHacks_HalfPixelOffset"); GSSettingIntEx(UserHacks_RoundSprite, "UserHacks_round_sprite_offset"); GSSettingIntEx(UserHacks_TCOffsetX, "UserHacks_TCOffsetX"); @@ -882,7 +880,6 @@ void Pcsx2Config::GSOptions::MaskUserHacks() UserHacks_NativePaletteDraw = false; UserHacks_DisableSafeFeatures = false; UserHacks_DisableRenderFixes = false; - UserHacks_HalfBottomOverride = -1; UserHacks_HalfPixelOffset = 0; UserHacks_RoundSprite = 0; UserHacks_AutoFlush = GSHWAutoFlushLevel::Disabled; diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index 3f2af18c7ccf1..4fd626260dc4d 100644 --- a/pcsx2/ShaderCacheVersion.h +++ b/pcsx2/ShaderCacheVersion.h @@ -15,4 +15,4 @@ /// Version number for GS and other shaders. Increment whenever any of the contents of the /// shaders change, to invalidate the cache. -static constexpr u32 SHADER_CACHE_VERSION = 30; +static constexpr u32 SHADER_CACHE_VERSION = 31;