mirror of https://github.com/PCSX2/pcsx2.git
GS/HW: HLE the Burnout games bloom effect
This commit is contained in:
parent
1edca6235c
commit
247b3ed740
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue