GS/HW: Replace Ico CRC hack with move handler

This commit is contained in:
Stenzek 2023-07-19 17:33:00 +10:00 committed by Connor McLaughlin
parent d5776e8946
commit 3109c5ef67
8 changed files with 79 additions and 57 deletions

View File

@ -383,6 +383,7 @@ PCPX-96322:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
PCPX-96328:
name: "3 Title Special Disc (Saru! Get You! 2 - PoPoLoCrois: Hajimari no Bouken - Boku no Natsuyasumi 2)"
region: "NTSC-J"
@ -959,6 +960,7 @@ SCAJ-20099:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCAJ-20100:
name: "Tenchu Kurenai"
region: "NTSC-Ch-J"
@ -1711,6 +1713,7 @@ SCCS-40005:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCCS-40006:
name: "Zhen Sanguo Wushuang 2"
region: "NTSC-C"
@ -2030,6 +2033,7 @@ SCED-50844:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCED-50907:
name: "Final Fantasy X [Bonus Disc - Beyond Final Fantasy]"
region: "PAL-Unk"
@ -3387,6 +3391,7 @@ SCES-50760:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCES-50781:
name: "Destruction Derby Arenas [Beta, Promo, & Full Retail]"
region: "PAL-M6"
@ -5325,6 +5330,7 @@ SCKA-20028:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCKA-20029:
name: "Gran Turismo - Concept 2002 Tokyo-Seoul [PlayStation 2 Big Hit Series]"
region: "NTSC-K"
@ -5847,6 +5853,7 @@ SCPS-11003:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCPS-11004:
name: "Bikkuri Mouse"
region: "NTSC-J"
@ -6688,6 +6695,7 @@ SCPS-19103:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCPS-19104:
name: "Pipo Saru 2001 [PlayStation 2 The Best]"
region: "NTSC-J"
@ -6704,6 +6712,7 @@ SCPS-19151:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCPS-19152:
name: "Saru Get You! 2 [PlayStation 2 The Best]"
region: "NTSC-J"
@ -7155,6 +7164,7 @@ SCPS-55001:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCPS-55002:
name: "Zero"
region: "NTSC-J"
@ -7427,6 +7437,7 @@ SCPS-56001:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCPS-56002:
name: "Tekken Tag Tournament"
region: "NTSC-K"
@ -7602,6 +7613,7 @@ SCUS-97113:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCUS-97114:
name: "NBA ShootOut 2001"
region: "NTSC-U"
@ -7787,6 +7799,7 @@ SCUS-97159:
gsHWFixes:
mipmap: 1
halfPixelOffset: 1 # Fixes effect misalignment.
moveHandler: "MV_Ico" # Fixes depth buffer post-processing.
SCUS-97160:
name: "Extermination [Demo]"
region: "NTSC-U"

View File

@ -26,13 +26,6 @@ const CRC::Game CRC::m_games[] =
{
// Note: IDs 0x7ACF7E03, 0x7D4EA48F, 0x37C53760 - shouldn't be added as it's from the multiloaders when packing games.
{0x00000000, NoTitle /* NoRegion */},
{0x6F8545DB, ICO /* US */},
{0x48CDF317, ICO /* US */}, // Demo
{0xB01A4C95, ICO /* JP */},
{0x2DF2C1EA, ICO /* KO */},
{0x5C991F4E, ICO /* EU */},
{0x788D8B4F, ICO /* EU */},
{0x29C28734, ICO /* CH */},
{0xFC46EA61, Tekken5 /* JP */},
{0x1F88EE37, Tekken5 /* EU */},
{0x1F88BECD, Tekken5 /* EU */}, // language selector...

View File

@ -23,7 +23,6 @@ public:
enum Title : u32
{
NoTitle,
ICO,
SMTNocturne,
Tekken5,
TitleCount,

View File

@ -616,7 +616,7 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip)
// have to set up the palette ourselves too, since GSC executes before it does
r.m_mem.m_clut.Read32(RTEX0, r.m_draw_env->TEXA);
std::shared_ptr<GSTextureCache::Palette> palette =
g_texture_cache->LookupPaletteObject(GSLocalMemory::m_psm[RTEX0.PSM].pal, true);
g_texture_cache->LookupPaletteObject(r.m_mem.m_clut, GSLocalMemory::m_psm[RTEX0.PSM].pal, true);
if (!palette)
return false;
@ -1167,7 +1167,6 @@ static bool GetMoveTargetPair(GSRendererHW& r, GSTextureCache::Target** src, GIF
return true;
}
#if 0
// Disabled to avoid compiler warnings, enable when it is needed.
static bool GetMoveTargetPair(GSRendererHW& r, GSTextureCache::Target** src, GSTextureCache::Target** dst,
bool req_target = false, bool preserve_target = false)
@ -1175,7 +1174,8 @@ static bool GetMoveTargetPair(GSRendererHW& r, GSTextureCache::Target** src, GST
return GetMoveTargetPair(r, src, GIFRegTEX0::Create(RSBP, RSBW, RSPSM), dst, GIFRegTEX0::Create(RDBP, RDBW, RDPSM),
req_target, preserve_target);
}
#endif
static int s_last_hacked_move_n = 0;
bool GSHwHack::MV_Growlanser(GSRendererHW& r)
{
@ -1192,8 +1192,7 @@ bool GSHwHack::MV_Growlanser(GSRendererHW& r)
return false;
// All the moves happen inbetween two draws, so we can take advantage of that to know when to stop.
static int last_hacked_move_n = 0;
if (r.s_n == last_hacked_move_n)
if (r.s_n == s_last_hacked_move_n)
return true;
GSTextureCache::Target *src, *dst;
@ -1212,7 +1211,53 @@ bool GSHwHack::MV_Growlanser(GSRendererHW& r)
g_gs_device->StretchRect(src->GetTexture(), GSVector4(rc) / GSVector4(src->GetUnscaledSize()).xyxy(),
dst->GetTexture(), GSVector4(rc) * GSVector4(dst->GetScale()), ShaderConvert::RGBA8_TO_FLOAT32, false);
last_hacked_move_n = r.s_n;
s_last_hacked_move_n = r.s_n;
return true;
}
bool GSHwHack::MV_Ico(GSRendererHW& r)
{
// Ico unswizzles the depth buffer (usually) 0x1800 to (usually) 0x2800 with a Z32->C32 move.
// Then it does a bunch of P4 moves to shift the bits in the blue channel to the alpha channel.
// The shifted target then gets used as a P8H texture, basically mapping depth bits 16..24 to a LUT.
// We can't currently HLE that in the usual move handler, so instead, emulate it with a channel shuffle.
// If we've started skipping moves (i.e. HLE'ed the first one), skip the others.
if (r.s_n == s_last_hacked_move_n && RSPSM == PSMT4 && RDPSM == PSMT4)
return true;
// 512x448 moves from C32->Z32.
if (RSPSM != PSMZ32 || RDPSM != PSMCT32 || RWIDTH < 512 || RHEIGHT < 448)
return false;
GL_PUSH("MV_Ico: %x -> %x %dx%d", RSBP, RDBP, RWIDTH, RHEIGHT);
GSTextureCache::Target *src, *dst;
if (!GetMoveTargetPair(r, &src, &dst, false, false))
return false;
// Store B -> A using a channel shuffle.
u32 pal[256];
for (u32 i = 0; i < std::size(pal); i++)
pal[i] = i << 24;
std::shared_ptr<GSTextureCache::Palette> palette = g_texture_cache->LookupPaletteObject(pal, 256, true);
if (!palette)
return false;
const GSVector4i draw_rc = GSVector4i(0, 0, RWIDTH, RHEIGHT);
dst->UpdateValidChannels(PSMCT32, 0);
dst->UpdateValidity(draw_rc);
GSHWDrawConfig& config = GSRendererHW::GetInstance()->BeginHLEHardwareDraw(dst->GetTexture(), nullptr,
dst->GetScale(), src->GetTexture(), src->GetScale(), draw_rc);
config.pal = palette->GetPaletteGSTexture();
config.ps.channel = ChannelFetch_BLUE;
config.ps.depth_fmt = 1;
config.ps.tfx = TFX_DECAL; // T -> A.
config.ps.tcc = true;
GSRendererHW::GetInstance()->EndHLEHardwareDraw(false);
s_last_hacked_move_n = r.s_n;
return true;
}
@ -1291,6 +1336,7 @@ const GSHwHack::Entry<GSRendererHW::OI_Ptr> GSHwHack::s_before_draw_functions[]
const GSHwHack::Entry<GSRendererHW::MV_Ptr> GSHwHack::s_move_handler_functions[] = {
CRC_F(MV_Growlanser),
CRC_F(MV_Ico),
};
#undef CRC_F

View File

@ -58,6 +58,7 @@ public:
static bool OI_HauntingGround(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t);
static bool MV_Growlanser(GSRendererHW& r);
static bool MV_Ico(GSRendererHW& r);
template <typename F>
struct Entry

View File

@ -3972,40 +3972,6 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
m_conf.tex = tex->m_texture;
m_conf.pal = tex->m_palette;
if (m_game.title == CRC::ICO)
{
const GSVertex* v = &m_vertex.buff[0];
const GSVideoMode mode = GetVideoMode();
if (tex && m_vt.m_primclass == GS_SPRITE_CLASS && m_vertex.next == 2 && PRIM->ABE && // Blend texture
((v[1].U == 8200 && v[1].V == 7176 && mode == GSVideoMode::NTSC) || // at display resolution 512x448
(v[1].U == 8200 && v[1].V == 8200 && mode == GSVideoMode::PAL)) && // at display resolution 512x512
tex->m_TEX0.PSM == PSMT8H) // i.e. read the alpha channel of a 32 bits texture
{
// Note potentially we can limit to TBP0:0x2800
// Depth buffer was moved so GS will invalide it which means a
// downscale. ICO uses the MSB depth bits as the texture alpha
// channel. However this depth of field effect requires
// texel:pixel mapping accuracy.
//
// Use an HLE shader to sample depth directly as the alpha channel
GL_INS("ICO sample depth as alpha");
m_conf.require_full_barrier = true;
// Extract the depth as palette index
m_conf.ps.depth_fmt = 1;
m_conf.ps.channel = ChannelFetch_BLUE;
m_conf.tex = ds->m_texture;
// We need the palette to convert the depth to the correct alpha value.
if (!tex->m_palette)
{
const u16 pal = GSLocalMemory::m_psm[tex->m_TEX0.PSM].pal;
g_texture_cache->AttachPaletteToSource(tex, pal, true);
m_conf.pal = tex->m_palette;
}
}
}
// Hazard handling (i.e. reading from the current RT/DS).
GSTextureCache::SourceRegion source_region = tex->GetRegion();
bool target_region = (tex->IsFromTarget() && source_region.HasEither());

View File

@ -4494,9 +4494,9 @@ GSTexture* GSTextureCache::LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVec
return nullptr;
}
std::shared_ptr<GSTextureCache::Palette> GSTextureCache::LookupPaletteObject(u16 pal, bool need_gs_texture)
std::shared_ptr<GSTextureCache::Palette> GSTextureCache::LookupPaletteObject(const u32* clut, u16 pal, bool need_gs_texture)
{
return m_palette_map.LookupPalette(pal, need_gs_texture);
return m_palette_map.LookupPalette(clut, pal, need_gs_texture);
}
void GSTextureCache::Read(Target* t, const GSVector4i& r)
@ -5615,13 +5615,13 @@ void GSTextureCache::InjectHashCacheTexture(const HashCacheKey& key, GSTexture*
// GSTextureCache::Palette
GSTextureCache::Palette::Palette(u16 pal, bool need_gs_texture)
GSTextureCache::Palette::Palette(const u32* clut, u16 pal, bool need_gs_texture)
: m_tex_palette(nullptr)
, m_pal(pal)
{
const u16 palette_size = pal * sizeof(u32);
m_clut = (u32*)_aligned_malloc(palette_size, 64);
memcpy(m_clut, (const u32*)g_gs_renderer->m_mem.m_clut, palette_size);
memcpy(m_clut, clut, palette_size);
if (need_gs_texture)
{
InitializeTexture();
@ -5699,6 +5699,11 @@ GSTextureCache::PaletteMap::PaletteMap()
}
std::shared_ptr<GSTextureCache::Palette> GSTextureCache::PaletteMap::LookupPalette(u16 pal, bool need_gs_texture)
{
return LookupPalette(g_gs_renderer->m_mem.m_clut, pal, need_gs_texture);
}
std::shared_ptr<GSTextureCache::Palette> GSTextureCache::PaletteMap::LookupPalette(const u32* clut, u16 pal, bool need_gs_texture)
{
ASSERT(pal == 16 || pal == 256);
@ -5707,8 +5712,6 @@ std::shared_ptr<GSTextureCache::Palette> GSTextureCache::PaletteMap::LookupPalet
// pal == 256 : index 1
auto& map = m_maps[pal == 16 ? 0 : 1];
const u32* clut = (const u32*)g_gs_renderer->m_mem.m_clut;
// Create PaletteKey for searching into map (clut is actually not copied, so do not store this key into the map)
const PaletteKey palette_key = {clut, pal};
@ -5763,7 +5766,7 @@ std::shared_ptr<GSTextureCache::Palette> GSTextureCache::PaletteMap::LookupPalet
}
}
std::shared_ptr<Palette> palette = std::make_shared<Palette>(pal, need_gs_texture);
std::shared_ptr<Palette> palette = std::make_shared<Palette>(clut, pal, need_gs_texture);
map.emplace(palette->GetPaletteKey(), palette);

View File

@ -181,7 +181,7 @@ public:
std::pair<u8, u8> m_alpha_minmax;
public:
Palette(u16 pal, bool need_gs_texture);
Palette(const u32* clut, u16 pal, bool need_gs_texture);
~Palette();
__fi std::pair<u8, u8> GetAlphaMinMax() const { return m_alpha_minmax; }
@ -335,6 +335,7 @@ public:
// Retrieves a shared pointer to a valid Palette from m_maps or creates a new one adding it to the data structure
std::shared_ptr<Palette> LookupPalette(u16 pal, bool need_gs_texture);
std::shared_ptr<Palette> LookupPalette(const u32* clut, u16 pal, bool need_gs_texture);
void Clear(); // Clears m_maps, thus deletes Palette objects
};
@ -470,7 +471,7 @@ public:
void DirtyRectByPage(u32 sbp, u32 spsm, u32 sbw, Target* t, GSVector4i src_r);
GSTexture* LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2i& offset, float* scale, const GSVector2i& size);
std::shared_ptr<Palette> LookupPaletteObject(u16 pal, bool need_gs_texture);
std::shared_ptr<Palette> 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);
Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GIFRegCLAMP& CLAMP, const GSVector4i& r, const bool possible_shuffle, bool palette = false);