GS: Move HW hacks into their own file and remove from GSState

This commit is contained in:
Stenzek 2023-01-05 17:45:41 +10:00 committed by refractionpcsx2
parent 2db6bf399e
commit 069196704e
15 changed files with 702 additions and 705 deletions

View File

@ -575,6 +575,7 @@ set(pcsx2GSHeaders
GS/Renderers/Null/GSDeviceNull.h
GS/Renderers/Null/GSRendererNull.h
GS/Renderers/Null/GSTextureNull.h
GS/Renderers/HW/GSHwHack.h
GS/Renderers/HW/GSRendererHW.h
GS/Renderers/HW/GSTextureCache.h
GS/Renderers/HW/GSTextureReplacements.h

View File

@ -275,7 +275,6 @@ bool GSreopen(bool recreate_display, const Pcsx2Config::GSOptions& old_config)
u8* basemem = g_gs_renderer->GetRegsMem();
const u32 gamecrc = g_gs_renderer->GetGameCRC();
const int gamecrc_options = g_gs_renderer->GetGameCRCOptions();
g_gs_renderer->Destroy();
g_gs_renderer.reset();
g_gs_device->Destroy();
@ -332,7 +331,7 @@ bool GSreopen(bool recreate_display, const Pcsx2Config::GSOptions& old_config)
return false;
}
g_gs_renderer->SetGameCRC(gamecrc, gamecrc_options);
g_gs_renderer->SetGameCRC(gamecrc, GSUtil::GetEffectiveCRCHackLevel(GSConfig.Renderer, GSConfig.CRCHack));
return true;
}
@ -559,9 +558,9 @@ void GSThrottlePresentation()
Threading::SleepUntil(s_next_manual_present_time);
}
void GSsetGameCRC(u32 crc, int options)
void GSsetGameCRC(u32 crc)
{
g_gs_renderer->SetGameCRC(crc, options);
g_gs_renderer->SetGameCRC(crc, GSUtil::GetEffectiveCRCHackLevel(GSConfig.Renderer, GSConfig.CRCHack));
}
GSVideoMode GSgetDisplayMode()
@ -709,8 +708,7 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
if (GSConfig.CRCHack != old_config.CRCHack ||
GSConfig.PointListPalette != old_config.PointListPalette)
{
// for automatic mipmaps, we need to reload the crc
g_gs_renderer->SetGameCRC(g_gs_renderer->GetGameCRC(), g_gs_renderer->GetGameCRCOptions());
g_gs_renderer->SetGameCRC(g_gs_renderer->GetGameCRC(), GSUtil::GetEffectiveCRCHackLevel(GSConfig.Renderer, GSConfig.CRCHack));
}
// renderer-specific options (e.g. auto flush, TC offset)

View File

@ -69,7 +69,7 @@ bool GSBeginCapture(std::string filename);
void GSEndCapture();
void GSPresentCurrentFrame();
void GSThrottlePresentation();
void GSsetGameCRC(u32 crc, int options);
void GSsetGameCRC(u32 crc);
GSVideoMode GSgetDisplayMode();
void GSgetInternalResolution(int* width, int* height);

View File

@ -20,7 +20,7 @@
class CRC
{
public:
enum Title
enum Title : u16
{
NoTitle,
AceCombat4,
@ -83,7 +83,7 @@ public:
TitleCount,
};
enum Region
enum Region : u8
{
NoRegion,
US,

View File

@ -39,16 +39,12 @@ static __fi bool IsFirstProvokingVertex()
GSState::GSState()
: m_version(STATE_VERSION)
, m_gsc(NULL)
, m_skip(0)
, m_skip_offset(0)
, m_q(1.0f)
, m_scanmask_used(0)
, tex_flushed(true)
, m_vt(this, IsFirstProvokingVertex())
, m_regs(NULL)
, m_crc(0)
, m_options(0)
{
// m_nativeres seems to be a hack. Unfortunately it impacts draw call number which make debug painful in the replayer.
// Let's keep it disabled to ease debug.
@ -57,10 +53,6 @@ GSState::GSState()
s_n = 0;
m_crc_hack_level = GSConfig.CRCHack;
if (m_crc_hack_level == CRCHackLevel::Automatic)
m_crc_hack_level = GSUtil::GetRecommendedCRCHackLevel(GSConfig.Renderer);
memset(&m_v, 0, sizeof(m_v));
memset(&m_vertex, 0, sizeof(m_vertex));
memset(&m_index, 0, sizeof(m_index));
@ -2759,12 +2751,10 @@ int GSState::Defrost(const freezeData* fd)
return 0;
}
void GSState::SetGameCRC(u32 crc, int options)
void GSState::SetGameCRC(u32 crc, CRCHackLevel level)
{
m_crc = crc;
m_options = options;
m_game = CRC::Lookup(m_crc_hack_level != CRCHackLevel::Off ? crc : 0);
SetupCrcHack();
m_game = CRC::Lookup((level != CRCHackLevel::Off) ? crc : 0);
}
//

View File

@ -29,19 +29,6 @@
#include "GSAlignedClass.h"
#include "GSDump.h"
struct GSFrameInfo
{
u32 FBP;
u32 FPSM;
u32 FBMSK;
u32 TBP0;
u32 TPSM;
u32 TZTST;
bool TME;
};
typedef bool (*GetSkipCount)(const GSFrameInfo& fi, int& skip);
class GSState : public GSAlignedClass<32>
{
public:
@ -152,15 +139,6 @@ private:
void CalcAlphaMinMax();
protected:
bool IsBadFrame();
void SetupCrcHack() noexcept;
bool m_isPackedUV_HackFlag;
CRCHackLevel m_crc_hack_level;
GetSkipCount m_gsc;
int m_skip;
int m_skip_offset;
GSVertex m_v;
float m_q;
GSVector4i m_scissor;
@ -168,6 +146,7 @@ protected:
u8 m_scanmask_used;
bool tex_flushed;
bool m_isPackedUV_HackFlag;
struct
{
@ -239,7 +218,6 @@ public:
u32 m_crc;
CRC::Game m_game;
std::unique_ptr<GSDumpBase> m_dump;
int m_options;
bool m_nativeres;
bool m_mipmap;
u32 m_dirty_gs_regs;
@ -387,8 +365,7 @@ public:
int Defrost(const freezeData* fd);
u32 GetGameCRC() const { return m_crc; }
int GetGameCRCOptions() const { return m_options; }
virtual void SetGameCRC(u32 crc, int options);
virtual void SetGameCRC(u32 crc, CRCHackLevel level);
u8* GetRegsMem() const { return reinterpret_cast<u8*>(m_regs); }
void SetRegsMem(u8* basemem) { m_regs = reinterpret_cast<GSPrivRegSet*>(basemem); }

View File

@ -145,8 +145,11 @@ bool GSUtil::HasCompatibleBits(u32 spsm, u32 dpsm)
return (s_maps.CompatibleBitsField[spsm][dpsm >> 5] & (1 << (dpsm & 0x1f))) != 0;
}
CRCHackLevel GSUtil::GetRecommendedCRCHackLevel(GSRendererType type)
CRCHackLevel GSUtil::GetEffectiveCRCHackLevel(GSRendererType type, CRCHackLevel level)
{
if (level != CRCHackLevel::Automatic)
return level;
return (type == GSRendererType::DX11 || type == GSRendererType::DX12) ? CRCHackLevel::Full : CRCHackLevel::Partial;
}

View File

@ -33,7 +33,7 @@ public:
static bool HasSharedBits(u32 sbp, u32 spsm, u32 dbp, u32 dpsm);
static bool HasCompatibleBits(u32 spsm, u32 dpsm);
static CRCHackLevel GetRecommendedCRCHackLevel(GSRendererType type);
static CRCHackLevel GetEffectiveCRCHackLevel(GSRendererType type, CRCHackLevel level);
static GSRendererType GetPreferredRenderer();
};

View File

@ -14,22 +14,25 @@
*/
#include "PrecompiledHeader.h"
#include "GS/GSState.h"
#include "GS/Renderers/HW/GSRendererHW.h"
#include "GS/Renderers/HW/GSHwHack.h"
#include "GS/GSGL.h"
bool s_nativeres;
static bool s_nativeres;
static CRCHackLevel s_crc_hack_level = CRCHackLevel::Full;
// hacks
#define CRC_Partial (s_crc_hack_level >= CRCHackLevel::Partial)
#define CRC_Full (s_crc_hack_level >= CRCHackLevel::Full)
#define CRC_Aggressive (s_crc_hack_level >= CRCHackLevel::Aggressive)
CRC::Region g_crc_region = CRC::NoRegion;
#define RPRIM r.PRIM
#define RCONTEXT r.m_context
////////////////////////////////////////////////////////////////////////////////
// Partial level, broken on all renderers.
////////////////////////////////////////////////////////////////////////////////
bool GSC_BigMuthaTruckers(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_BigMuthaTruckers(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -47,7 +50,7 @@ bool GSC_BigMuthaTruckers(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_DeathByDegreesTekkenNinaWilliams(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_DeathByDegreesTekkenNinaWilliams(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
// Note: Game also has issues with texture shuffle not supported on strange clamp mode.
// See https://forums.pcsx2.net/Thread-GSDX-Texture-Cache-Bug-Report-Death-By-Degrees-SLUS-20934-NTSC
@ -77,7 +80,7 @@ bool GSC_DeathByDegreesTekkenNinaWilliams(const GSFrameInfo& fi, int& skip) noex
return true;
}
bool GSC_GiTS(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_GiTS(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -92,7 +95,7 @@ bool GSC_GiTS(const GSFrameInfo& fi, int& skip) noexcept
}
// Channel effect not properly supported yet
bool GSC_Manhunt2(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_Manhunt2(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
/*
* The game readback RT as 8 bits index texture to apply a non-linear brightness/gamma correction on all channel
@ -123,7 +126,7 @@ bool GSC_Manhunt2(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_CrashBandicootWoC(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_CrashBandicootWoC(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
// Channel effect not properly supported - Removes fog to fix the fog wall issue on Direct3D at any resolution, and while upscaling on every Hardware renderer.
if (skip == 0)
@ -153,7 +156,7 @@ bool GSC_CrashBandicootWoC(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_SacredBlaze(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_SacredBlaze(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
//Fix Sacred Blaze rendering glitches
if (skip == 0)
@ -167,7 +170,7 @@ bool GSC_SacredBlaze(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_Spartan(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_Spartan(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -186,7 +189,7 @@ bool GSC_Spartan(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_Oneechanbara2Special(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_Oneechanbara2Special(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -201,7 +204,7 @@ bool GSC_Oneechanbara2Special(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_SakuraTaisen(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_SakuraTaisen(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -233,7 +236,7 @@ bool GSC_SakuraTaisen(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_SFEX3(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_SFEX3(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -248,7 +251,7 @@ bool GSC_SFEX3(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_Tekken5(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_Tekken5(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -260,7 +263,7 @@ bool GSC_Tekken5(const GSFrameInfo& fi, int& skip) noexcept
// Let's enable this hack for Aggressive only since it's an upscaling issue for both renders.
skip = 95;
}
else if (fi.TZTST == 1 && fi.TME && (fi.FBP == 0x02bc0 || fi.FBP == 0x02be0 || fi.FBP == 0x02d00 || fi.FBP == 0x03480 || fi.FBP == 0x034a0) && fi.FPSM == fi.TPSM && fi.TBP0 == 0x00000 && fi.TPSM == PSM_PSMCT32)
else if (fi.ZTST == 1 && fi.TME && (fi.FBP == 0x02bc0 || fi.FBP == 0x02be0 || fi.FBP == 0x02d00 || fi.FBP == 0x03480 || fi.FBP == 0x034a0) && fi.FPSM == fi.TPSM && fi.TBP0 == 0x00000 && fi.TPSM == PSM_PSMCT32)
{
// The moving display effect(flames) is not emulated properly in the entire screen so let's remove the effect in the stage: Burning Temple. Related to half screen bottom issue.
// Fixes black lines in the stage: Burning Temple - caused by upscaling. Note the black lines can also be fixed with Merge Sprite hack.
@ -271,7 +274,7 @@ bool GSC_Tekken5(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_TombRaiderAnniversary(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_TombRaiderAnniversary(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -284,7 +287,7 @@ bool GSC_TombRaiderAnniversary(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_TombRaiderLegend(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_TombRaiderLegend(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -302,7 +305,7 @@ bool GSC_TombRaiderLegend(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_TombRaiderUnderWorld(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_TombRaiderUnderWorld(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -319,7 +322,7 @@ bool GSC_TombRaiderUnderWorld(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_BurnoutGames(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_BurnoutGames(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -335,7 +338,7 @@ bool GSC_BurnoutGames(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_MidnightClub3(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_MidnightClub3(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -351,7 +354,7 @@ bool GSC_MidnightClub3(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_TalesOfLegendia(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_TalesOfLegendia(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -380,7 +383,7 @@ bool GSC_TalesOfLegendia(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_Kunoichi(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_Kunoichi(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -405,7 +408,7 @@ bool GSC_Kunoichi(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_ZettaiZetsumeiToshi2(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_ZettaiZetsumeiToshi2(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -454,7 +457,7 @@ bool GSC_ZettaiZetsumeiToshi2(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_SakuraWarsSoLongMyLove(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_SakuraWarsSoLongMyLove(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -475,7 +478,7 @@ bool GSC_SakuraWarsSoLongMyLove(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_FightingBeautyWulong(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_FightingBeautyWulong(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -490,7 +493,7 @@ bool GSC_FightingBeautyWulong(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_GodHand(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_GodHand(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -503,7 +506,7 @@ bool GSC_GodHand(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_KnightsOfTheTemple2(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_KnightsOfTheTemple2(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -520,7 +523,7 @@ bool GSC_KnightsOfTheTemple2(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_UltramanFightingEvolution(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_UltramanFightingEvolution(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -534,7 +537,7 @@ bool GSC_UltramanFightingEvolution(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_TalesofSymphonia(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_TalesofSymphonia(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -551,7 +554,7 @@ bool GSC_TalesofSymphonia(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_Simple2000Vol114(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_Simple2000Vol114(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -571,7 +574,7 @@ bool GSC_Simple2000Vol114(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_UrbanReign(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_UrbanReign(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -584,7 +587,7 @@ bool GSC_UrbanReign(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_SteambotChronicles(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_SteambotChronicles(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -609,7 +612,7 @@ bool GSC_SteambotChronicles(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_YakuzaGames(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_YakuzaGames(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -631,7 +634,7 @@ bool GSC_YakuzaGames(const GSFrameInfo& fi, int& skip) noexcept
// Full level, correctly emulated on OpenGL/Vulkan but can be used as potential speed hack
////////////////////////////////////////////////////////////////////////////////
bool GSC_GetawayGames(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_GetawayGames(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -648,7 +651,7 @@ bool GSC_GetawayGames(const GSFrameInfo& fi, int& skip) noexcept
// Aggressive only hack
////////////////////////////////////////////////////////////////////////////////
bool GSC_AceCombat4(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_AceCombat4(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
// Removes clouds for a good speed boost, removes both 3D clouds(invisible with Hardware renderers, but cause slowdown) and 2D background clouds.
// Removes blur from player airplane.
@ -666,7 +669,7 @@ bool GSC_AceCombat4(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_FFXGames(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_FFXGames(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -685,7 +688,7 @@ bool GSC_FFXGames(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_Okami(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_Okami(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -705,7 +708,7 @@ bool GSC_Okami(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_RedDeadRevolver(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_RedDeadRevolver(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -718,7 +721,7 @@ bool GSC_RedDeadRevolver(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_ShinOnimusha(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_ShinOnimusha(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -747,7 +750,7 @@ bool GSC_ShinOnimusha(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
bool GSC_XenosagaE3(const GSFrameInfo& fi, int& skip) noexcept
bool GSHwHack::GSC_XenosagaE3(GSRendererHW& r, const GSFrameInfo& fi, int& skip)
{
if (skip == 0)
{
@ -777,113 +780,509 @@ bool GSC_XenosagaE3(const GSFrameInfo& fi, int& skip) noexcept
return true;
}
////////////////////////////////////////////////////////////////////////////////
void GSState::SetupCrcHack() noexcept
bool GSHwHack::OI_BigMuthaTruckers(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
GetSkipCount lut[CRC::TitleCount];
// 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.
s_nativeres = m_nativeres;
s_crc_hack_level = m_crc_hack_level;
const GIFRegTEX0& Texture = RCONTEXT->TEX0;
memset(lut, 0, sizeof(lut));
GIFRegTEX0 Frame = {};
Frame.TBW = RCONTEXT->FRAME.FBW;
Frame.TBP0 = RCONTEXT->FRAME.Block();
if (CRC_Partial)
if (RPRIM->TME && Frame.TBW == 10 && Texture.TBW == 10 && Frame.TBP0 == 0x00a00 && Texture.PSM == PSM_PSMT8H && (r.m_r.y == 256 || r.m_r.y == 224))
{
lut[CRC::GodHand] = GSC_GodHand;
lut[CRC::KnightsOfTheTemple2] = GSC_KnightsOfTheTemple2;
lut[CRC::Kunoichi] = GSC_Kunoichi;
lut[CRC::Manhunt2] = GSC_Manhunt2;
lut[CRC::MidnightClub3] = GSC_MidnightClub3;
lut[CRC::SacredBlaze] = GSC_SacredBlaze;
lut[CRC::SakuraTaisen] = GSC_SakuraTaisen;
lut[CRC::SakuraWarsSoLongMyLove] = GSC_SakuraWarsSoLongMyLove;
lut[CRC::Simple2000Vol114] = GSC_Simple2000Vol114;
lut[CRC::SFEX3] = GSC_SFEX3;
lut[CRC::TalesOfLegendia] = GSC_TalesOfLegendia;
lut[CRC::TalesofSymphonia] = GSC_TalesofSymphonia;
lut[CRC::TombRaiderAnniversary] = GSC_TombRaiderAnniversary;
lut[CRC::TombRaiderLegend] = GSC_TombRaiderLegend;
lut[CRC::TombRaiderUnderworld] = GSC_TombRaiderUnderWorld;
lut[CRC::UrbanReign] = GSC_UrbanReign;
lut[CRC::ZettaiZetsumeiToshi2] = GSC_ZettaiZetsumeiToshi2;
// 224 ntsc, 256 pal.
GL_INS("OI_BigMuthaTruckers half bottom offset");
// Channel Effect
lut[CRC::CrashBandicootWoC] = GSC_CrashBandicootWoC;
lut[CRC::GiTS] = GSC_GiTS;
lut[CRC::Spartan] = GSC_Spartan;
lut[CRC::SteambotChronicles] = GSC_SteambotChronicles;
const size_t count = r.m_vertex.next;
GSVertex* v = &r.m_vertex.buff[0];
const u16 offset = (u16)r.m_r.y * 16;
// Depth Issue
lut[CRC::BurnoutGames] = GSC_BurnoutGames;
// Half Screen bottom issue
lut[CRC::Tekken5] = GSC_Tekken5;
// Texture shuffle
lut[CRC::BigMuthaTruckers] = GSC_BigMuthaTruckers;
lut[CRC::DeathByDegreesTekkenNinaWilliams] = GSC_DeathByDegreesTekkenNinaWilliams; // + Upscaling issues
// Upscaling hacks
lut[CRC::FightingBeautyWulong] = GSC_FightingBeautyWulong;
lut[CRC::Oneechanbara2Special] = GSC_Oneechanbara2Special;
lut[CRC::UltramanFightingEvolution] = GSC_UltramanFightingEvolution;
lut[CRC::YakuzaGames] = GSC_YakuzaGames;
for (size_t i = 0; i < count; i++)
v[i].V += offset;
}
// CRC FULL is for Direct3D, they are fixed on OpenGL/Vulkan
if (CRC_Full)
{
// Accurate Blending
lut[CRC::GetawayGames] = GSC_GetawayGames; // Blending High
}
if (CRC_Aggressive)
{
lut[CRC::AceCombat4] = GSC_AceCombat4;
lut[CRC::FFX2] = GSC_FFXGames;
lut[CRC::FFX] = GSC_FFXGames;
lut[CRC::FFXII] = GSC_FFXGames;
lut[CRC::RedDeadRevolver] = GSC_RedDeadRevolver;
lut[CRC::ShinOnimusha] = GSC_ShinOnimusha;
lut[CRC::XenosagaE3] = GSC_XenosagaE3;
// Upscaling issues
lut[CRC::Okami] = GSC_Okami;
}
m_gsc = lut[m_game.title];
g_crc_region = m_game.region;
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
return true;
if (!((r.m_r == GSVector4i(0, 0, 16, 16)).alltrue() || (r.m_r == GSVector4i(0, 0, 64, 64)).alltrue()))
return true; // Only 16x16 or 64x64 draws.
// Sprite rendering
if (!r.CanUseSwSpriteRender())
return true;
r.SwSpriteRender();
return false; // Skip current draw
}
bool GSHwHack::OI_FFXII(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
static u32* video = nullptr;
static size_t lines = 0;
if (lines == 0)
{
if (r.m_vt.m_primclass == GS_LINE_CLASS && (r.m_vertex.next == 448 * 2 || r.m_vertex.next == 512 * 2))
{
lines = r.m_vertex.next / 2;
}
}
else
{
if (r.m_vt.m_primclass == GS_POINT_CLASS)
{
if (r.m_vertex.next >= 16 * 512)
{
// incoming pixels are stored in columns, one column is 16x512, total res 448x512 or 448x454
if (!video)
video = new u32[512 * 512];
const GSVertex* RESTRICT v = r.m_vertex.buff;
const int ox = RCONTEXT->XYOFFSET.OFX - 8;
const int oy = RCONTEXT->XYOFFSET.OFY - 8;
for (int i = (int)r.m_vertex.next; i > 0; i--, v++)
{
const int x = (v->XYZ.X - ox) >> 4;
const int y = (v->XYZ.Y - oy) >> 4;
if (x < 0 || x >= 448 || y < 0 || y >= (int)lines)
return false; // le sigh
video[(y << 8) + (y << 7) + (y << 6) + x] = v->RGBAQ.U32[0];
}
return false;
}
else
{
lines = 0;
}
}
else if (r.m_vt.m_primclass == GS_LINE_CLASS)
{
if (r.m_vertex.next == lines * 2)
{
// normally, this step would copy the video onto screen with 512 texture mapped horizontal lines,
// but we use the stored video data to create a new texture, and replace the lines with two triangles
g_gs_device->Recycle(t->m_texture);
t->m_texture = g_gs_device->CreateTexture(512, 512, 1, GSTexture::Format::Color);
t->m_texture->Update(GSVector4i(0, 0, 448, lines), video, 448 * 4);
r.m_vertex.buff[2] = r.m_vertex.buff[r.m_vertex.next - 2];
r.m_vertex.buff[3] = r.m_vertex.buff[r.m_vertex.next - 1];
r.m_index.buff[0] = 0;
r.m_index.buff[1] = 1;
r.m_index.buff[2] = 2;
r.m_index.buff[3] = 1;
r.m_index.buff[4] = 2;
r.m_index.buff[5] = 3;
r.m_vertex.head = r.m_vertex.tail = r.m_vertex.next = 4;
r.m_index.tail = 6;
r.m_vt.Update(r.m_vertex.buff, r.m_index.buff, r.m_vertex.tail, r.m_index.tail, GS_TRIANGLE_CLASS);
}
else
{
lines = 0;
}
}
}
return true;
}
bool GSHwHack::OI_FFX(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
const u32 FBP = RCONTEXT->FRAME.Block();
const u32 ZBP = RCONTEXT->ZBUF.Block();
const u32 TBP = RCONTEXT->TEX0.TBP0;
if ((FBP == 0x00d00 || FBP == 0x00000) && ZBP == 0x02100 && RPRIM->TME && TBP == 0x01a00 && RCONTEXT->TEX0.PSM == PSM_PSMCT16S)
{
// random battle transition (z buffer written directly, clear it now)
GL_INS("OI_FFX ZB clear");
g_gs_device->ClearDepth(ds);
}
return true;
}
bool GSHwHack::OI_MetalSlug6(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
// missing red channel fix (looks alright in pcsx2 r5000+)
GSVertex* RESTRICT v = r.m_vertex.buff;
for (size_t i = r.m_vertex.next; i > 0; i--, v++)
{
const u32 c = v->RGBAQ.U32[0];
const u32 r = (c >> 0) & 0xff;
const u32 g = (c >> 8) & 0xff;
const u32 b = (c >> 16) & 0xff;
if (r == 0 && g != 0 && b != 0)
{
v->RGBAQ.U32[0] = (c & 0xffffff00) | ((g + b + 1) >> 1);
}
}
r.m_vt.Update(r.m_vertex.buff, r.m_index.buff, r.m_vertex.tail, r.m_index.tail, r.m_vt.m_primclass);
return true;
}
bool GSHwHack::OI_RozenMaidenGebetGarden(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
if (!RPRIM->TME)
{
const u32 FBP = RCONTEXT->FRAME.Block();
const u32 ZBP = RCONTEXT->ZBUF.Block();
if (FBP == 0x008c0 && ZBP == 0x01a40)
{
// frame buffer clear, atst = fail, afail = write z only, z buffer points to frame buffer
GIFRegTEX0 TEX0 = {};
TEX0.TBP0 = ZBP;
TEX0.TBW = RCONTEXT->FRAME.FBW;
TEX0.PSM = RCONTEXT->FRAME.PSM;
if (GSTextureCache::Target* tmp_rt = r.m_tc->LookupTarget(TEX0, r.GetTargetSize(), GSTextureCache::RenderTarget, true))
{
GL_INS("OI_RozenMaidenGebetGarden FB clear");
g_gs_device->ClearRenderTarget(tmp_rt->m_texture, 0);
}
return false;
}
else if (FBP == 0x00000 && RCONTEXT->ZBUF.Block() == 0x01180)
{
// z buffer clear, frame buffer now points to the z buffer (how can they be so clever?)
GIFRegTEX0 TEX0 = {};
TEX0.TBP0 = FBP;
TEX0.TBW = RCONTEXT->FRAME.FBW;
TEX0.PSM = RCONTEXT->ZBUF.PSM;
if (GSTextureCache::Target* tmp_ds = r.m_tc->LookupTarget(TEX0, r.GetTargetSize(), GSTextureCache::DepthStencil, true))
{
GL_INS("OI_RozenMaidenGebetGarden ZB clear");
g_gs_device->ClearDepth(tmp_ds->m_texture);
}
return false;
}
}
return true;
}
bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
// Rendering pattern is:
// Save RG channel with a kind of a TS (replaced by a copy in this hack),
// compute shadow in RG,
// save result in alpha with a TS,
// Restore RG channel that we previously copied to render shadows.
const GIFRegTEX0& Texture = RCONTEXT->TEX0;
GIFRegTEX0 Frame = {};
Frame.TBW = RCONTEXT->FRAME.FBW;
Frame.TBP0 = RCONTEXT->FRAME.Block();
Frame.PSM = RCONTEXT->FRAME.PSM;
if ((!RPRIM->TME) || (GSLocalMemory::m_psm[Texture.PSM].bpp != 16) || (GSLocalMemory::m_psm[Frame.PSM].bpp != 16) || (Texture.TBP0 == Frame.TBP0) || (Frame.TBW != 16 && Texture.TBW != 16))
return true;
GL_INS("OI_SonicUnleashed replace draw by a copy");
GSTextureCache::Target* src = r.m_tc->LookupTarget(Texture, GSVector2i(1, 1), GSTextureCache::RenderTarget, true);
const GSVector2i rt_size(rt->GetSize());
const GSVector2i src_size(src->m_texture->GetSize());
const GSVector2i copy_size(std::min(rt_size.x, src_size.x), std::min(rt_size.y, src_size.y));
const GSVector4 sRect(0.0f, 0.0f, static_cast<float>(copy_size.x) / static_cast<float>(src_size.x), static_cast<float>(copy_size.y) / static_cast<float>(src_size.y));
const GSVector4 dRect(0, 0, copy_size.x, copy_size.y);
g_gs_device->StretchRect(src->m_texture, sRect, rt, dRect, true, true, true, false);
return false;
}
bool GSHwHack::OI_ArTonelico2(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
// world map clipping
//
// The bad draw call is a sprite rendering to clear the z buffer
/*
Depth buffer description
* width is 10 pages
* texture/scissor size is 640x448
* depth is 16 bits so it writes 70 (10w * 7h) pages of data.
following draw calls will use the buffer as 6 pages width with a scissor
test of 384x672. So the above texture can be seen as a
* texture width: 6 pages * 64 pixels/page = 384
* texture height: 70/6 pages * 64 pixels/page =746
So as you can see the GS issue a write of 640x448 but actually it
expects to clean a 384x746 area. Ideally the fix will transform the
buffer to adapt the page width properly.
*/
const GSVertex* v = &r.m_vertex.buff[0];
if (r.m_vertex.next == 2 && !RPRIM->TME && RCONTEXT->FRAME.FBW == 10 && v->XYZ.Z == 0 && RCONTEXT->TEST.ZTST == ZTST_ALWAYS)
{
GL_INS("OI_ArTonelico2");
g_gs_device->ClearDepth(ds);
}
return true;
}
bool GSHwHack::OI_JakGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
if (!(r.m_r == GSVector4i(0, 0, 16, 16)).alltrue())
return true; // Only 16x16 draws.
if (!r.CanUseSwSpriteRender())
return true;
// Render 16x16 palette via CPU.
r.SwSpriteRender();
return false; // Skip current draw.
}
bool GSHwHack::OI_BurnoutGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
if (!r.OI_PointListPalette(r, rt, ds, t))
return false; // Render point list palette.
if (t && t->m_from_target) // Avoid slow framebuffer readback
return true;
if (!r.CanUseSwSpriteRender())
return true;
// Render palette via CPU.
r.SwSpriteRender();
return false;
}
// OO (others output?) hacks: invalidate extra local memory after the draw call
void GSHwHack::OO_BurnoutGames(GSRendererHW& r)
{
const GIFRegTEX0& TEX0 = RCONTEXT->TEX0;
const GIFRegALPHA& ALPHA = RCONTEXT->ALPHA;
const GIFRegFRAME& FRAME = RCONTEXT->FRAME;
if (RPRIM->PRIM == GS_SPRITE && !RPRIM->IIP && RPRIM->TME && !RPRIM->FGE && RPRIM->ABE && !RPRIM->AA1 && !RPRIM->FST && !RPRIM->FIX && TEX0.TBW == 16 && TEX0.TW == 10 && TEX0.TCC && !TEX0.TFX && TEX0.PSM == PSM_PSMT8 && TEX0.CPSM == PSM_PSMCT32 && !TEX0.CSM && TEX0.TH == 8 && ALPHA.A == ALPHA.B && ALPHA.D == 0 && FRAME.FBW == 16 && FRAME.PSM == PSM_PSMCT32)
{
// Readback clouds being rendered during level loading.
// Later the alpha channel from the 32 bit frame buffer is used as an 8 bit indexed texture to draw
// the clouds on top of the sky at each frame.
// Burnout 3 PAL 50Hz: 0x3ba0 => 0x1e80.
GL_INS("OO_BurnoutGames - Readback clouds renderered from TEX0.TBP0 = 0x%04x (TEX0.CBP = 0x%04x) to FBP = 0x%04x", TEX0.TBP0, TEX0.CBP, FRAME.Block());
r.m_tc->InvalidateLocalMem(RCONTEXT->offset.fb, r.m_r);
}
}
#undef RCONTEXT
#undef RPRIM
#undef CRC_Partial
#undef CRC_Full
#undef CRC_Aggressive
bool GSState::IsBadFrame()
////////////////////////////////////////////////////////////////////////////////
const GSHwHack::Entry<GSRendererHW::GSC_Ptr> GSHwHack::s_gsc_functions[] = {
{CRC::GodHand, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_GodHand},
{CRC::KnightsOfTheTemple2, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_KnightsOfTheTemple2},
{CRC::Kunoichi, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_Kunoichi},
{CRC::Manhunt2, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_Manhunt2},
{CRC::MidnightClub3, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_MidnightClub3},
{CRC::SacredBlaze, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_SacredBlaze},
{CRC::SakuraTaisen, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_SakuraTaisen},
{CRC::SakuraWarsSoLongMyLove, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_SakuraWarsSoLongMyLove},
{CRC::Simple2000Vol114, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_Simple2000Vol114},
{CRC::SFEX3, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_SFEX3},
{CRC::TalesOfLegendia, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_TalesOfLegendia},
{CRC::TalesofSymphonia, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_TalesofSymphonia},
{CRC::TombRaiderAnniversary, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_TombRaiderAnniversary},
{CRC::TombRaiderLegend, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_TombRaiderLegend},
{CRC::TombRaiderUnderworld, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_TombRaiderUnderWorld},
{CRC::UrbanReign, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_UrbanReign},
{CRC::ZettaiZetsumeiToshi2, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_ZettaiZetsumeiToshi2},
// Channel Effect
{CRC::CrashBandicootWoC, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_CrashBandicootWoC},
{CRC::GiTS, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_GiTS},
{CRC::Spartan, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_Spartan},
{CRC::SteambotChronicles, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_SteambotChronicles},
// Depth Issue
{CRC::BurnoutGames, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_BurnoutGames},
// Half Screen bottom issue
{CRC::Tekken5, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_Tekken5},
// Texture shuffle
{CRC::BigMuthaTruckers, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_BigMuthaTruckers},
{CRC::DeathByDegreesTekkenNinaWilliams, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_DeathByDegreesTekkenNinaWilliams}, // + Upscaling issues
// Upscaling hacks
{CRC::FightingBeautyWulong, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_FightingBeautyWulong},
{CRC::Oneechanbara2Special, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_Oneechanbara2Special},
{CRC::UltramanFightingEvolution, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_UltramanFightingEvolution},
{CRC::YakuzaGames, CRC::RegionCount, CRCHackLevel::Partial, &GSHwHack::GSC_YakuzaGames},
// Accurate Blending
{CRC::GetawayGames, CRC::RegionCount, CRCHackLevel::Full, &GSHwHack::GSC_GetawayGames}, // Blending High
{CRC::AceCombat4, CRC::RegionCount, CRCHackLevel::Aggressive, &GSHwHack::GSC_AceCombat4},
{CRC::FFX2, CRC::RegionCount, CRCHackLevel::Aggressive, &GSHwHack::GSC_FFXGames},
{CRC::FFX, CRC::RegionCount, CRCHackLevel::Aggressive, &GSHwHack::GSC_FFXGames},
{CRC::FFXII, CRC::RegionCount, CRCHackLevel::Aggressive, &GSHwHack::GSC_FFXGames},
{CRC::RedDeadRevolver, CRC::RegionCount, CRCHackLevel::Aggressive, &GSHwHack::GSC_RedDeadRevolver},
{CRC::ShinOnimusha, CRC::RegionCount, CRCHackLevel::Aggressive, &GSHwHack::GSC_ShinOnimusha},
{CRC::XenosagaE3, CRC::RegionCount, CRCHackLevel::Aggressive, &GSHwHack::GSC_XenosagaE3},
// Upscaling issues
{CRC::Okami, CRC::RegionCount, CRCHackLevel::Aggressive, &GSHwHack::GSC_Okami},
};
const GSHwHack::Entry<GSRendererHW::OI_Ptr> GSHwHack::s_oi_functions[] = {
{CRC::BigMuthaTruckers, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_BigMuthaTruckers},
{CRC::DBZBT2, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_DBZBTGames},
{CRC::DBZBT3, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_DBZBTGames},
{CRC::FFXII, CRC::EU, CRCHackLevel::Minimum, &GSHwHack::OI_FFXII},
{CRC::FFX, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_FFX},
{CRC::MetalSlug6, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_MetalSlug6},
{CRC::RozenMaidenGebetGarden, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_RozenMaidenGebetGarden},
{CRC::SonicUnleashed, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_SonicUnleashed},
{CRC::ArTonelico2, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_ArTonelico2},
{CRC::Jak2, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_JakGames},
{CRC::Jak3, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_JakGames},
{CRC::JakX, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_JakGames},
{CRC::BurnoutGames, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_BurnoutGames},
{CRC::Black, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OI_BurnoutGames}};
const GSHwHack::Entry<GSRendererHW::OO_Ptr> GSHwHack::s_oo_functions[] = {
{CRC::BurnoutGames, CRC::RegionCount, CRCHackLevel::Minimum, &GSHwHack::OO_BurnoutGames},
};
void GSRendererHW::SetupCrcHack(CRCHackLevel level)
{
GSFrameInfo fi = {0};
s_nativeres = m_nativeres;
s_crc_hack_level = level;
fi.FBP = m_context->FRAME.Block();
fi.FPSM = m_context->FRAME.PSM;
fi.FBMSK = m_context->FRAME.FBMSK;
fi.TME = PRIM->TME;
fi.TBP0 = m_context->TEX0.TBP0;
fi.TPSM = m_context->TEX0.PSM;
fi.TZTST = m_context->TEST.ZTST;
if (m_gsc && !m_gsc(fi, m_skip))
m_gsc = nullptr;
if (level != CRCHackLevel::Off)
{
return false;
for (const auto& entry : GSHwHack::s_gsc_functions)
{
if (entry.Test(m_game.title, m_game.region, level))
{
m_gsc = entry.ptr;
break;
}
}
}
m_oi = nullptr;
if (level != CRCHackLevel::Off)
{
for (const auto& entry : GSHwHack::s_oi_functions)
{
if (entry.Test(m_game.title, m_game.region, level))
{
m_oi = entry.ptr;
break;
}
}
}
if (GSConfig.PointListPalette)
{
if (m_oi)
Console.Warning("Overriding m_oi with PointListPalette");
m_oi = &GSRendererHW::OI_PointListPalette;
}
m_oo = nullptr;
if (level != CRCHackLevel::Off)
{
for (const auto& entry : GSHwHack::s_oo_functions)
{
if (entry.Test(m_game.title, m_game.region, level))
{
m_oo = entry.ptr;
break;
}
}
}
}
bool GSRendererHW::IsBadFrame()
{
if (m_gsc)
{
const GSFrameInfo fi = {
m_context->FRAME.Block(),
m_context->FRAME.PSM,
m_context->FRAME.FBMSK,
m_context->ZBUF.Block(),
m_context->ZBUF.ZMSK,
m_context->TEST.ZTST,
PRIM->TME,
m_context->TEX0.TBP0,
m_context->TEX0.PSM,
};
if (!m_gsc(*this, fi, m_skip))
return false;
}
if (m_skip == 0 && GSConfig.SkipDrawEnd > 0)
{
if (fi.TME)
if (PRIM->TME)
{
// depth textures (bully, mgs3s1 intro, Front Mission 5)
// General, often problematic post processing
if (GSLocalMemory::m_psm[fi.TPSM].depth || GSUtil::HasSharedBits(fi.FBP, fi.FPSM, fi.TBP0, fi.TPSM))
if (GSLocalMemory::m_psm[m_context->TEX0.PSM].depth ||
GSUtil::HasSharedBits(m_context->FRAME.Block(), m_context->FRAME.PSM, m_context->TEX0.TBP0, m_context->TEX0.PSM))
{
m_skip_offset = GSConfig.SkipDrawStart;
m_skip = GSConfig.SkipDrawEnd;

View File

@ -0,0 +1,88 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2023 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "GS/Renderers/HW/GSRendererHW.h"
class GSHwHack
{
public:
static bool GSC_BigMuthaTruckers(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_DeathByDegreesTekkenNinaWilliams(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_GiTS(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_Manhunt2(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_CrashBandicootWoC(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_SacredBlaze(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_Spartan(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_Oneechanbara2Special(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_SakuraTaisen(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_SFEX3(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_Tekken5(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_TombRaiderAnniversary(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_TombRaiderLegend(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_TombRaiderUnderWorld(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_BurnoutGames(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_MidnightClub3(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_TalesOfLegendia(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_Kunoichi(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_ZettaiZetsumeiToshi2(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_SakuraWarsSoLongMyLove(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_FightingBeautyWulong(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_GodHand(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_KnightsOfTheTemple2(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_UltramanFightingEvolution(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_TalesofSymphonia(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_Simple2000Vol114(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_UrbanReign(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_SteambotChronicles(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_YakuzaGames(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_GetawayGames(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_AceCombat4(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_FFXGames(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_Okami(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_RedDeadRevolver(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_ShinOnimusha(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
static bool GSC_XenosagaE3(GSRendererHW& r, const GSFrameInfo& fi, int& skip);
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_FFXII(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_MetalSlug6(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
static bool OI_RozenMaidenGebetGarden(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
static bool OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
static bool OI_ArTonelico2(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
static bool OI_JakGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
static bool OI_BurnoutGames(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
static void OO_BurnoutGames(GSRendererHW& r);
template <typename F>
struct Entry
{
CRC::Title game;
CRC::Region region;
CRCHackLevel level;
F ptr;
__fi bool Test(CRC::Title title_, CRC::Region region_, CRCHackLevel level_) const
{
return (game == title_ && (region == CRC::RegionCount || region == region_) && level_ >= level);
}
};
static const Entry<GSRendererHW::GSC_Ptr> s_gsc_functions[];
static const Entry<GSRendererHW::OI_Ptr> s_oi_functions[];
static const Entry<GSRendererHW::OO_Ptr> s_oo_functions[];
};

View File

@ -159,11 +159,11 @@ bool GSRendererHW::IsPossibleTextureShuffle(GSTextureCache::Target* dst, const G
GSLocalMemory::m_psm[m_context->FRAME.PSM].bpp == 16);
}
void GSRendererHW::SetGameCRC(u32 crc, int options)
void GSRendererHW::SetGameCRC(u32 crc, CRCHackLevel level)
{
GSRenderer::SetGameCRC(crc, options);
GSRenderer::SetGameCRC(crc, level);
m_hacks.SetGameCRC(m_game);
SetupCrcHack(level);
GSTextureReplacements::GameChanged();
}
@ -1830,7 +1830,7 @@ void GSRendererHW::Draw()
}
}
if (m_hacks.m_oi && !(this->*m_hacks.m_oi)(rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, m_src))
if (m_oi && !m_oi(*this, rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, m_src))
{
GL_INS("Warning skipping a draw call (%d)", s_n);
return;
@ -1945,10 +1945,8 @@ void GSRendererHW::Draw()
//
if (m_hacks.m_oo)
{
(this->*m_hacks.m_oo)();
}
if (m_oo)
m_oo(*this);
if (GSConfig.DumpGSData)
{
@ -4128,49 +4126,6 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t
return true;
}
// hacks
GSRendererHW::Hacks::Hacks()
: m_oi_map(m_oi_list)
, m_oo_map(m_oo_list)
, m_oi(nullptr)
, m_oo(nullptr)
{
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::BigMuthaTruckers, CRC::RegionCount, &GSRendererHW::OI_BigMuthaTruckers));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::DBZBT2, CRC::RegionCount, &GSRendererHW::OI_DBZBTGames));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::DBZBT3, CRC::RegionCount, &GSRendererHW::OI_DBZBTGames));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::FFXII, CRC::EU, &GSRendererHW::OI_FFXII));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::FFX, CRC::RegionCount, &GSRendererHW::OI_FFX));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::MetalSlug6, CRC::RegionCount, &GSRendererHW::OI_MetalSlug6));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::RozenMaidenGebetGarden, CRC::RegionCount, &GSRendererHW::OI_RozenMaidenGebetGarden));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::SonicUnleashed, CRC::RegionCount, &GSRendererHW::OI_SonicUnleashed));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::ArTonelico2, CRC::RegionCount, &GSRendererHW::OI_ArTonelico2));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::Jak2, CRC::RegionCount, &GSRendererHW::OI_JakGames));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::Jak3, CRC::RegionCount, &GSRendererHW::OI_JakGames));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::JakX, CRC::RegionCount, &GSRendererHW::OI_JakGames));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::BurnoutGames, CRC::RegionCount, &GSRendererHW::OI_BurnoutGames));
m_oi_list.push_back(HackEntry<OI_Ptr>(CRC::Black, CRC::RegionCount, &GSRendererHW::OI_BurnoutGames));
m_oo_list.push_back(HackEntry<OO_Ptr>(CRC::BurnoutGames, CRC::RegionCount, &GSRendererHW::OO_BurnoutGames));
m_oo_list.push_back(HackEntry<OO_Ptr>(CRC::Black, CRC::RegionCount, &GSRendererHW::OO_BurnoutGames));
}
void GSRendererHW::Hacks::SetGameCRC(const CRC::Game& game)
{
const u32 hash = (u32)((game.region << 24) | game.title);
m_oi = m_oi_map[hash];
m_oo = m_oo_map[hash];
if (GSConfig.PointListPalette)
{
if (m_oi)
Console.Warning("Overriding m_oi with PointListPalette");
m_oi = &GSRendererHW::OI_PointListPalette;
}
}
// Trick to do a fast clear on the GS
// Set frame buffer pointer on the start of the buffer. Set depth buffer pointer on the half buffer
// FB + depth write will fill the full buffer.
@ -4415,421 +4370,55 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc
return true;
}
// OI (others input?/implementation?) hacks replace current draw call
bool GSRendererHW::OI_BigMuthaTruckers(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
bool GSRendererHW::OI_PointListPalette(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 = m_context->TEX0;
GIFRegTEX0 Frame = {};
Frame.TBW = m_context->FRAME.FBW;
Frame.TBP0 = m_context->FRAME.Block();
if (PRIM->TME && Frame.TBW == 10 && Texture.TBW == 10 && Frame.TBP0 == 0x00a00 && Texture.PSM == PSM_PSMT8H && (m_r.y == 256 || m_r.y == 224))
{
// 224 ntsc, 256 pal.
GL_INS("OI_BigMuthaTruckers half bottom offset");
const size_t count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
const u16 offset = (u16)m_r.y * 16;
for (size_t i = 0; i < count; i++)
v[i].V += offset;
}
return true;
}
bool GSRendererHW::OI_DBZBTGames(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
if (t && t->m_from_target) // Avoid slow framebuffer readback
return true;
if (!((m_r == GSVector4i(0, 0, 16, 16)).alltrue() || (m_r == GSVector4i(0, 0, 64, 64)).alltrue()))
return true; // Only 16x16 or 64x64 draws.
// Sprite rendering
if (!CanUseSwSpriteRender())
return true;
SwSpriteRender();
return false; // Skip current draw
}
bool GSRendererHW::OI_FFXII(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
static u32* video = nullptr;
static size_t lines = 0;
if (lines == 0)
{
if (m_vt.m_primclass == GS_LINE_CLASS && (m_vertex.next == 448 * 2 || m_vertex.next == 512 * 2))
{
lines = m_vertex.next / 2;
}
}
else
{
if (m_vt.m_primclass == GS_POINT_CLASS)
{
if (m_vertex.next >= 16 * 512)
{
// incoming pixels are stored in columns, one column is 16x512, total res 448x512 or 448x454
if (!video)
video = new u32[512 * 512];
const GSVertex* RESTRICT v = m_vertex.buff;
const int ox = m_context->XYOFFSET.OFX - 8;
const int oy = m_context->XYOFFSET.OFY - 8;
for (int i = (int)m_vertex.next; i > 0; i--, v++)
{
const int x = (v->XYZ.X - ox) >> 4;
const int y = (v->XYZ.Y - oy) >> 4;
if (x < 0 || x >= 448 || y < 0 || y >= (int)lines)
return false; // le sigh
video[(y << 8) + (y << 7) + (y << 6) + x] = v->RGBAQ.U32[0];
}
return false;
}
else
{
lines = 0;
}
}
else if (m_vt.m_primclass == GS_LINE_CLASS)
{
if (m_vertex.next == lines * 2)
{
// normally, this step would copy the video onto screen with 512 texture mapped horizontal lines,
// but we use the stored video data to create a new texture, and replace the lines with two triangles
g_gs_device->Recycle(t->m_texture);
t->m_texture = g_gs_device->CreateTexture(512, 512, 1, GSTexture::Format::Color);
t->m_texture->Update(GSVector4i(0, 0, 448, lines), video, 448 * 4);
m_vertex.buff[2] = m_vertex.buff[m_vertex.next - 2];
m_vertex.buff[3] = m_vertex.buff[m_vertex.next - 1];
m_index.buff[0] = 0;
m_index.buff[1] = 1;
m_index.buff[2] = 2;
m_index.buff[3] = 1;
m_index.buff[4] = 2;
m_index.buff[5] = 3;
m_vertex.head = m_vertex.tail = m_vertex.next = 4;
m_index.tail = 6;
m_vt.Update(m_vertex.buff, m_index.buff, m_vertex.tail, m_index.tail, GS_TRIANGLE_CLASS);
}
else
{
lines = 0;
}
}
}
return true;
}
bool GSRendererHW::OI_FFX(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
const u32 FBP = m_context->FRAME.Block();
const u32 ZBP = m_context->ZBUF.Block();
const u32 TBP = m_context->TEX0.TBP0;
if ((FBP == 0x00d00 || FBP == 0x00000) && ZBP == 0x02100 && PRIM->TME && TBP == 0x01a00 && m_context->TEX0.PSM == PSM_PSMCT16S)
{
// random battle transition (z buffer written directly, clear it now)
GL_INS("OI_FFX ZB clear");
g_gs_device->ClearDepth(ds);
}
return true;
}
bool GSRendererHW::OI_MetalSlug6(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
// missing red channel fix (looks alright in pcsx2 r5000+)
GSVertex* RESTRICT v = m_vertex.buff;
for (size_t i = m_vertex.next; i > 0; i--, v++)
{
const u32 c = v->RGBAQ.U32[0];
const u32 r = (c >> 0) & 0xff;
const u32 g = (c >> 8) & 0xff;
const u32 b = (c >> 16) & 0xff;
if (r == 0 && g != 0 && b != 0)
{
v->RGBAQ.U32[0] = (c & 0xffffff00) | ((g + b + 1) >> 1);
}
}
m_vt.Update(m_vertex.buff, m_index.buff, m_vertex.tail, m_index.tail, m_vt.m_primclass);
return true;
}
bool GSRendererHW::OI_RozenMaidenGebetGarden(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
if (!PRIM->TME)
{
const u32 FBP = m_context->FRAME.Block();
const u32 ZBP = m_context->ZBUF.Block();
if (FBP == 0x008c0 && ZBP == 0x01a40)
{
// frame buffer clear, atst = fail, afail = write z only, z buffer points to frame buffer
GIFRegTEX0 TEX0 = {};
TEX0.TBP0 = ZBP;
TEX0.TBW = m_context->FRAME.FBW;
TEX0.PSM = m_context->FRAME.PSM;
if (GSTextureCache::Target* tmp_rt = m_tc->LookupTarget(TEX0, GetTargetSize(), GSTextureCache::RenderTarget, true))
{
GL_INS("OI_RozenMaidenGebetGarden FB clear");
g_gs_device->ClearRenderTarget(tmp_rt->m_texture, 0);
}
return false;
}
else if (FBP == 0x00000 && m_context->ZBUF.Block() == 0x01180)
{
// z buffer clear, frame buffer now points to the z buffer (how can they be so clever?)
GIFRegTEX0 TEX0 = {};
TEX0.TBP0 = FBP;
TEX0.TBW = m_context->FRAME.FBW;
TEX0.PSM = m_context->ZBUF.PSM;
if (GSTextureCache::Target* tmp_ds = m_tc->LookupTarget(TEX0, GetTargetSize(), GSTextureCache::DepthStencil, true))
{
GL_INS("OI_RozenMaidenGebetGarden ZB clear");
g_gs_device->ClearDepth(tmp_ds->m_texture);
}
return false;
}
}
return true;
}
bool GSRendererHW::OI_SonicUnleashed(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
// Rendering pattern is:
// Save RG channel with a kind of a TS (replaced by a copy in this hack),
// compute shadow in RG,
// save result in alpha with a TS,
// Restore RG channel that we previously copied to render shadows.
const GIFRegTEX0& Texture = m_context->TEX0;
GIFRegTEX0 Frame = {};
Frame.TBW = m_context->FRAME.FBW;
Frame.TBP0 = m_context->FRAME.Block();
Frame.PSM = m_context->FRAME.PSM;
if ((!PRIM->TME)
|| (GSLocalMemory::m_psm[Texture.PSM].bpp != 16)
|| (GSLocalMemory::m_psm[Frame.PSM].bpp != 16)
|| (Texture.TBP0 == Frame.TBP0)
|| (Frame.TBW != 16 && Texture.TBW != 16))
return true;
GL_INS("OI_SonicUnleashed replace draw by a copy");
GSTextureCache::Target* src = m_tc->LookupTarget(Texture, GSVector2i(1, 1), GSTextureCache::RenderTarget, true);
const GSVector2i rt_size(rt->GetSize());
const GSVector2i src_size(src->m_texture->GetSize());
const GSVector2i copy_size(std::min(rt_size.x, src_size.x), std::min(rt_size.y, src_size.y));
const GSVector4 sRect(0.0f, 0.0f, static_cast<float>(copy_size.x) / static_cast<float>(src_size.x), static_cast<float>(copy_size.y) / static_cast<float>(src_size.y));
const GSVector4 dRect(0, 0, copy_size.x, copy_size.y);
g_gs_device->StretchRect(src->m_texture, sRect, rt, dRect, true, true, true, false);
return false;
}
bool GSRendererHW::OI_PointListPalette(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
const size_t n_vertices = m_vertex.next;
const int w = m_r.width();
const int h = m_r.height();
const bool is_copy = !PRIM->ABE || (
m_context->ALPHA.A == m_context->ALPHA.B // (A - B) == 0 in blending equation, makes C value irrelevant.
&& m_context->ALPHA.D == 0 // Copy source RGB(A) color into frame buffer.
const size_t n_vertices = r.m_vertex.next;
const int w = r.m_r.width();
const int h = r.m_r.height();
const bool is_copy = !r.PRIM->ABE || (
r.m_context->ALPHA.A == r.m_context->ALPHA.B // (A - B) == 0 in blending equation, makes C value irrelevant.
&& r.m_context->ALPHA.D == 0 // Copy source RGB(A) color into frame buffer.
);
if (m_vt.m_primclass == GS_POINT_CLASS && w <= 64 // Small draws.
if (r.m_vt.m_primclass == GS_POINT_CLASS && w <= 64 // Small draws.
&& h <= 64 // Small draws.
&& n_vertices <= 256 // Small draws.
&& is_copy // Copy (no blending).
&& !PRIM->TME // No texturing please.
&& m_context->FRAME.PSM == PSM_PSMCT32 // Only 32-bit pixel format (CLUT format).
&& !PRIM->FGE // No FOG.
&& !PRIM->AA1 // No antialiasing.
&& !PRIM->FIX // Normal fragment value control.
&& !m_env.DTHE.DTHE // No dithering.
&& !m_context->TEST.ATE // No alpha test.
&& !m_context->TEST.DATE // No destination alpha test.
&& (!m_context->DepthRead() && !m_context->DepthWrite()) // No depth handling.
&& !m_context->TEX0.CSM // No CLUT usage.
&& !m_env.PABE.PABE // No PABE.
&& m_context->FBA.FBA == 0 // No Alpha Correction.
&& m_context->FRAME.FBMSK == 0 // No frame buffer masking.
&& !r.PRIM->TME // No texturing please.
&& r.m_context->FRAME.PSM == PSM_PSMCT32 // Only 32-bit pixel format (CLUT format).
&& !r.PRIM->FGE // No FOG.
&& !r.PRIM->AA1 // No antialiasing.
&& !r.PRIM->FIX // Normal fragment value control.
&& !r.m_env.DTHE.DTHE // No dithering.
&& !r.m_context->TEST.ATE // No alpha test.
&& !r.m_context->TEST.DATE // No destination alpha test.
&& (!r.m_context->DepthRead() && !r.m_context->DepthWrite()) // No depth handling.
&& !r.m_context->TEX0.CSM // No CLUT usage.
&& !r.m_env.PABE.PABE // No PABE.
&& r.m_context->FBA.FBA == 0 // No Alpha Correction.
&& r.m_context->FRAME.FBMSK == 0 // No frame buffer masking.
)
{
const u32 FBP = m_context->FRAME.Block();
const u32 FBW = m_context->FRAME.FBW;
GL_INS("PointListPalette - m_r = <%d, %d => %d, %d>, n_vertices = %zu, FBP = 0x%x, FBW = %u", m_r.x, m_r.y, m_r.z, m_r.w, n_vertices, FBP, FBW);
const GSVertex* RESTRICT v = m_vertex.buff;
const int ox(m_context->XYOFFSET.OFX);
const int oy(m_context->XYOFFSET.OFY);
const u32 FBP = r.m_context->FRAME.Block();
const u32 FBW = r.m_context->FRAME.FBW;
GL_INS("PointListPalette - m_r = <%d, %d => %d, %d>, n_vertices = %zu, FBP = 0x%x, FBW = %u", r.m_r.x, r.m_r.y, r.m_r.z, r.m_r.w, n_vertices, FBP, FBW);
const GSVertex* RESTRICT v = r.m_vertex.buff;
const int ox(r.m_context->XYOFFSET.OFX);
const int oy(r.m_context->XYOFFSET.OFY);
for (size_t i = 0; i < n_vertices; ++i)
{
const GSVertex& vi = v[i];
const GIFRegXYZ& xyz = vi.XYZ;
const int x = (int(xyz.X) - ox) / 16;
const int y = (int(xyz.Y) - oy) / 16;
if (x < m_r.x || x > m_r.z)
if (x < r.m_r.x || x > r.m_r.z)
continue;
if (y < m_r.y || y > m_r.w)
if (y < r.m_r.y || y > r.m_r.w)
continue;
const u32 c = vi.RGBAQ.U32[0];
m_mem.WritePixel32(x, y, c, FBP, FBW);
r.m_mem.WritePixel32(x, y, c, FBP, FBW);
}
m_tc->InvalidateVideoMem(m_context->offset.fb, m_r);
r.m_tc->InvalidateVideoMem(r.m_context->offset.fb, r.m_r);
return false;
}
return true;
}
bool GSRendererHW::OI_ArTonelico2(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
// world map clipping
//
// The bad draw call is a sprite rendering to clear the z buffer
/*
Depth buffer description
* width is 10 pages
* texture/scissor size is 640x448
* depth is 16 bits so it writes 70 (10w * 7h) pages of data.
following draw calls will use the buffer as 6 pages width with a scissor
test of 384x672. So the above texture can be seen as a
* texture width: 6 pages * 64 pixels/page = 384
* texture height: 70/6 pages * 64 pixels/page =746
So as you can see the GS issue a write of 640x448 but actually it
expects to clean a 384x746 area. Ideally the fix will transform the
buffer to adapt the page width properly.
*/
const GSVertex* v = &m_vertex.buff[0];
if (m_vertex.next == 2 && !PRIM->TME && m_context->FRAME.FBW == 10 && v->XYZ.Z == 0 && m_context->TEST.ZTST == ZTST_ALWAYS)
{
GL_INS("OI_ArTonelico2");
g_gs_device->ClearDepth(ds);
}
return true;
}
bool GSRendererHW::OI_JakGames(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
if (!(m_r == GSVector4i(0, 0, 16, 16)).alltrue())
return true; // Only 16x16 draws.
if (!CanUseSwSpriteRender())
return true;
// Render 16x16 palette via CPU.
SwSpriteRender();
return false; // Skip current draw.
}
bool GSRendererHW::OI_BurnoutGames(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
if (!OI_PointListPalette(rt, ds, t))
return false; // Render point list palette.
if (t && t->m_from_target) // Avoid slow framebuffer readback
return true;
if (!CanUseSwSpriteRender())
return true;
// Render palette via CPU.
SwSpriteRender();
return false;
}
// OO (others output?) hacks: invalidate extra local memory after the draw call
void GSRendererHW::OO_BurnoutGames()
{
const GIFRegTEX0& TEX0 = m_context->TEX0;
const GIFRegALPHA& ALPHA = m_context->ALPHA;
const GIFRegFRAME& FRAME = m_context->FRAME;
if (PRIM->PRIM == GS_SPRITE
&& !PRIM->IIP
&& PRIM->TME
&& !PRIM->FGE
&& PRIM->ABE
&& !PRIM->AA1
&& !PRIM->FST
&& !PRIM->FIX
&& TEX0.TBW == 16
&& TEX0.TW == 10
&& TEX0.TCC
&& !TEX0.TFX
&& TEX0.PSM == PSM_PSMT8
&& TEX0.CPSM == PSM_PSMCT32
&& !TEX0.CSM
&& TEX0.TH == 8
&& ALPHA.A == ALPHA.B
&& ALPHA.D == 0
&& FRAME.FBW == 16
&& FRAME.PSM == PSM_PSMCT32)
{
// Readback clouds being rendered during level loading.
// Later the alpha channel from the 32 bit frame buffer is used as an 8 bit indexed texture to draw
// the clouds on top of the sky at each frame.
// Burnout 3 PAL 50Hz: 0x3ba0 => 0x1e80.
GL_INS("OO_BurnoutGames - Readback clouds renderered from TEX0.TBP0 = 0x%04x (TEX0.CBP = 0x%04x) to FBP = 0x%04x", TEX0.TBP0, TEX0.CBP, FRAME.Block());
m_tc->InvalidateLocalMem(m_context->offset.fb, m_r);
}
}
// Can Upscale hacks: disable upscaling for some draw calls
// None required.

View File

@ -26,103 +26,41 @@ class GSRendererHW;
MULTI_ISA_DEF(class GSRendererHWFunctions;)
MULTI_ISA_DEF(void GSRendererHWPopulateFunctions(GSRendererHW& renderer);)
class GSHwHack;
struct GSFrameInfo
{
u32 FBP;
u32 FPSM;
u32 FBMSK;
u32 ZBP;
u32 ZMSK;
u32 ZTST;
u32 TME;
u32 TBP0;
u32 TPSM;
};
class GSRendererHW : public GSRenderer
{
MULTI_ISA_FRIEND(GSRendererHWFunctions);
friend GSHwHack;
public:
static constexpr int MAX_FRAMEBUFFER_HEIGHT = 1280;
private:
static constexpr float SSR_UV_TOLERANCE = 1.0f;
#pragma region hacks
typedef bool (GSRendererHW::*OI_Ptr)(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
typedef void (GSRendererHW::*OO_Ptr)();
typedef bool (GSRendererHW::*CU_Ptr)();
using GSC_Ptr = bool(*)(GSRendererHW& r, const GSFrameInfo& fi, int& skip); // GSC - Get Skip Count
using OI_Ptr = bool(*)(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); // OI - Before draw
using OO_Ptr = void(*)(GSRendererHW& r); // OO - After draw
// Require special argument
bool OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Source* t, const GSVector4i& r_draw);
bool OI_GsMemClear(); // always on
void OI_DoubleHalfClear(GSTextureCache::Target*& rt, GSTextureCache::Target*& ds); // always on
bool OI_BigMuthaTruckers(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_DBZBTGames(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_FFXII(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_FFX(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_MetalSlug6(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_RozenMaidenGebetGarden(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_SonicUnleashed(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_PointListPalette(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_ArTonelico2(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_JakGames(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
bool OI_BurnoutGames(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
void OO_BurnoutGames();
class Hacks
{
template <class T>
class HackEntry
{
public:
CRC::Title title;
CRC::Region region;
T func;
HackEntry(CRC::Title t, CRC::Region r, T f)
{
title = t;
region = r;
func = f;
}
};
template <class T>
class FunctionMap : public GSFunctionMap<u32, T>
{
std::list<HackEntry<T>>& m_tbl;
T GetDefaultFunction(u32 key)
{
CRC::Title title = (CRC::Title)(key & 0xffffff);
CRC::Region region = (CRC::Region)(key >> 24);
for (const auto& entry : m_tbl)
{
if (entry.title == title && (entry.region == CRC::RegionCount || entry.region == region))
{
return entry.func;
}
}
return NULL;
}
public:
FunctionMap(std::list<HackEntry<T>>& tbl)
: m_tbl(tbl)
{
}
};
std::list<HackEntry<OI_Ptr>> m_oi_list;
std::list<HackEntry<OO_Ptr>> m_oo_list;
FunctionMap<OI_Ptr> m_oi_map;
FunctionMap<OO_Ptr> m_oo_map;
public:
OI_Ptr m_oi;
OO_Ptr m_oo;
Hacks();
void SetGameCRC(const CRC::Game& game);
} m_hacks;
#pragma endregion
static bool OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
u16 Interpolate_UV(float alpha, int t0, int t1);
float alpha0(int L, int X0, int X1);
@ -155,6 +93,16 @@ private:
GSVector4i m_r;
GSTextureCache::Source* m_src;
// CRC Hacks
bool IsBadFrame();
void SetupCrcHack(CRCHackLevel level);
GSC_Ptr m_gsc = nullptr;
OI_Ptr m_oi = nullptr;
OO_Ptr m_oo = nullptr;
int m_skip = 0;
int m_skip_offset = 0;
bool m_reset;
bool m_tex_is_fb;
bool m_channel_shuffle;
@ -180,7 +128,7 @@ public:
void Destroy() override;
void SetGameCRC(u32 crc, int options) override;
void SetGameCRC(u32 crc, CRCHackLevel level) override;
bool CanUpscale() override;
float GetUpscaleMultiplier() override;
void Lines2Sprites();

View File

@ -276,7 +276,7 @@ bool SysMtgsThread::TryOpenGS()
if (!GSopen(EmuConfig.GS, EmuConfig.GS.Renderer, RingBuffer.Regs))
return false;
GSsetGameCRC(ElfCRC, 0);
GSsetGameCRC(ElfCRC);
return true;
}
@ -511,7 +511,7 @@ void SysMtgsThread::MainLoop()
break;
case GS_RINGTYPE_CRC:
GSsetGameCRC(tag.data[0], 0);
GSsetGameCRC(tag.data[0]);
break;
case GS_RINGTYPE_INIT_AND_READ_FIFO:

View File

@ -555,6 +555,7 @@
<ClInclude Include="Frontend\imgui_impl_dx12.h" />
<ClInclude Include="Frontend\imgui_impl_opengl3.h" />
<ClInclude Include="Frontend\imgui_impl_vulkan.h" />
<ClInclude Include="GS\Renderers\HW\GSHwHack.h" />
<ClInclude Include="INISettingsInterface.h" />
<ClInclude Include="Frontend\InputManager.h" />
<ClInclude Include="Frontend\InputSource.h" />

View File

@ -2342,6 +2342,9 @@
<ClInclude Include="SPU2\Dma.h">
<Filter>System\Ps2\SPU2</Filter>
</ClInclude>
<ClInclude Include="GS\Renderers\HW\GSHwHack.h">
<Filter>System\Ps2\GS\Renderers\Hardware</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuildStep Include="rdebug\deci2.h">