GS/HW: HLE the Burnout games bloom effect

This commit is contained in:
Stenzek 2023-07-24 23:06:59 +10:00 committed by Connor McLaughlin
parent 1edca6235c
commit 247b3ed740
1 changed files with 95 additions and 12 deletions

View File

@ -216,20 +216,103 @@ bool GSHwHack::GSC_Tekken5(GSRendererHW& r, int& skip)
bool GSHwHack::GSC_BurnoutGames(GSRendererHW& r, int& skip)
{
if (RFBW == 2 && std::abs(static_cast<int>(RFBP) - static_cast<int>(RZBP)) <= static_cast<int>(BLOCKS_PER_PAGE) && (!RTME || RTPSM != PSMT8))
{
skip = 2;
return true;
}
// Burnout has a... creative way of achieving its bloom effect, to avoid horizontal page breaks.
// First they double strip clear a single page column to (191, 191, 191), then blend the main
// framebuffer into this column, with (Cs - Cd) * 2. So anything lower than 191 clamps to zero,
// and anything larger boosts up a bit. Then that column gets downsampled to half size, makes
// sense right? The fun bit is when they move to the next page, instead of being sensible and
// using another double strip clear, they write Z to the next page as part of the blended draw,
// setting it to 191 (in Z24 terms). Then the buffers are swapped for the next column, 0x1a40
// and 0x1a60 in US.
//
// We _could_ handle that, except for the fact that instead of pointing the texture at 0x1a60
// for the downsample of the second column, they point it at 0x1a40, and offset the coordinates
// by a page. This would need "tex outside RT", and no way that's happening.
//
// So, I present to you, dear reader, the first state machine within a CRC hack, in all its
// disgusting glory. This effectively reduces the multi-pass effect to a single pass, by replacing
// the column-wide draws with a fullscreen sprite, and skipping the extra passes.
//
// After this, they do a blur on the buffer, which is fine, because all the buffer swap BS has
// finished, so we can return to normal.
// We don't check if we already have a skip here, because it gets confused when auto flush is on.
if (RTME && (RFBP == 0x01dc0 || RFBP == 0x01c00 || RFBP == 0x01f00 || RFBP == 0x01d40 || RFBP == 0x02200 || RFBP == 0x02000) && RFPSM == RTPSM && (RTBP0 == 0x01dc0 || RTBP0 == 0x01c00 || RTBP0 == 0x01f00 || RTBP0 == 0x01d40 || RTBP0 == 0x02200 || RTBP0 == 0x02000) && RTPSM == PSMCT32)
static u32 state = 0;
static GIFRegTEX0 main_fb;
static GSVector2i main_fb_size;
static GIFRegTEX0 downsample_fb;
static GIFRegTEX0 bloom_fb;
switch (state)
{
// 0x01dc0 01c00(MP) ntsc, 0x01f00 0x01d40(MP) ntsc progressive, 0x02200(MP) pal.
// Yellow stripes.
// Multiplayer tested only on Takedown.
skip = 3;
return true;
case 0: // waiting for double striped clear
{
if (RFBW != 2 || RFBP != RZBP || RTME)
break;
// Need a backed up context to grab the framebuffer.
if (r.m_backed_up_ctx < 0)
break;
// Next draw should contain our source.
GSTextureCache::Target* tgt = g_texture_cache->LookupTarget(r.m_env.CTXT[r.m_backed_up_ctx].TEX0,
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget);
if (!tgt)
break;
// Clear temp render target.
main_fb = tgt->m_TEX0;
main_fb_size = tgt->GetUnscaledSize();
r.m_cached_ctx.FRAME.FBW = tgt->m_TEX0.TBW;
r.m_cached_ctx.ZBUF.ZMSK = true;
r.ReplaceVerticesWithSprite(GSVector4i::loadh(main_fb_size), main_fb_size);
bloom_fb = GIFRegTEX0::Create(RFBP, RFBW, RFPSM);
state = 1;
GL_INS("GSC_BurnoutGames(): Initial double-striped clear.");
return true;
}
case 1: // reverse blend to extract bright pixels
{
r.ReplaceVerticesWithSprite(GSVector4i::loadh(main_fb_size), main_fb_size);
r.m_cached_ctx.ZBUF.ZMSK = true;
state = 2;
GL_INS("GSC_BurnoutGames(): Extract Bright Pixels.");
return true;
}
case 2: // downsample
{
const GSVector4i downsample_rect = GSVector4i(0, 0, main_fb_size.x / 2, main_fb_size.y / 2);
r.ReplaceVerticesWithSprite(downsample_rect, GSVector4i::loadh(main_fb_size), main_fb_size, downsample_rect);
downsample_fb = GIFRegTEX0::Create(RFBP, RFBW, RFPSM);
state = 3;
GL_INS("GSC_BurnoutGames(): Downsampling.");
return true;
}
case 3:
{
// Kill the downsample source, because we made it way larger than it was supposed to be.
// That way we don't risk confusing any other targets.
g_texture_cache->InvalidateVideoMemType(GSTextureCache::RenderTarget, bloom_fb.TBP0);
state = 4;
[[fallthrough]];
}
case 4: // Skip until it's downsampled again.
{
if (!RTME || RTBP0 != downsample_fb.TBP0)
{
GL_INS("GSC_BurnoutGames(): Skipping extra pass.");
skip = 1;
return true;
}
// Finally, we're done, let the game take over.
GL_INS("GSC_BurnoutGames(): Bloom effect done.");
skip = 0;
state = 0;
return true;
}
}
return GSC_BlackAndBurnoutSky(r, skip);