From 353124d82ddc49c9eda6057f0eecbc56a53841e1 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 9 Dec 2022 00:26:07 +1000 Subject: [PATCH] GS-HW: Make memory clear work for Burnout 3 --- pcsx2/GS/GSRegs.h | 6 +-- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 61 ++++++++++-------------- pcsx2/GS/Renderers/HW/GSRendererHW.h | 1 - pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 19 +++++++- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/pcsx2/GS/GSRegs.h b/pcsx2/GS/GSRegs.h index c5b74cc371..f5f9d7c47a 100644 --- a/pcsx2/GS/GSRegs.h +++ b/pcsx2/GS/GSRegs.h @@ -536,9 +536,9 @@ REG64_(GIFReg, ALPHA) u8 FIX; u8 _PAD2[3]; REG_END2 - // opaque => output will be Cs/As - __forceinline bool IsOpaque() const { return ((A == B || (C == 2 && FIX == 0)) && D == 0) || (A == 0 && B == D && C == 2 && FIX == 0x80); } - __forceinline bool IsOpaque(int amin, int amax) const { return ((A == B || amax == 0) && D == 0) || (A == 0 && B == D && amin == 0x80 && amax == 0x80); } + // opaque => output will be Cs/As/zero + __forceinline bool IsOpaque() const { return ((A == B || (C == 2 && FIX == 0)) && D == 0) || (A == 0 && B == D && C == 2 && FIX == 0x80) || (C == 2 && D != 1 && FIX == 0x00); } + __forceinline bool IsOpaque(int amin, int amax) const { return ((A == B || amax == 0) && D == 0) || (A == 0 && B == D && amin == 0x80 && amax == 0x80) || (C == 2 && D != 1 && FIX == 0x00); } __forceinline bool IsCd() { return (A == B) && (D == 1); } REG_END2 diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index e808f4bff5..9e0f645f54 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1693,7 +1693,7 @@ void GSRendererHW::Draw() { // Constant Direct Write without texture/test/blending (aka a GS mem clear) if ((m_vt.m_primclass == GS_SPRITE_CLASS) && !PRIM->TME // Direct write - && (!PRIM->ABE || m_context->ALPHA.IsOpaque()) // No transparency + && (!PRIM->ABE || IsOpaque()) // No transparency && (m_context->FRAME.FBMSK == 0) // no color mask && !m_context->TEST.ATE // no alpha test && (!m_context->TEST.ZTE || m_context->TEST.ZTST == ZTST_ALWAYS) // no depth test @@ -1701,18 +1701,32 @@ void GSRendererHW::Draw() && m_r.x == 0 && m_r.y == 0) // Likely full buffer write { // Likely doing a huge single page width clear, which never goes well. (Superman) - if ((m_r.w > 1024) && context->FRAME.FBW == 1) + // Burnout 3 does a 32x1024 double width clear on its reflection targets. + const bool clear_height_valid = (m_r.w >= 1024); + if (clear_height_valid && context->FRAME.FBW == 1) { - m_r.z = GetResolution().x; - m_r.w = GetResolution().y; + m_r.w = GetFramebufferHeight(); + m_r.z = GetFramebufferWidth(); context->FRAME.FBW = (m_r.z + 63) / 64; } - if (OI_GsMemClear() && m_r.w > 1024) + + // Superman does a clear to white, not black, on its depth buffer. + // Since we don't preload depth, OI_GsMemClear() won't work here, since we invalidate the target later + // on. So, instead, let the draw go through with the expanded rectangle, and copy color->depth. + const bool is_zero_clear = (((GSLocalMemory::m_psm[m_context->FRAME.PSM].fmt == 0) ? + m_vertex.buff[1].RGBAQ.U32[0] : + (m_vertex.buff[1].RGBAQ.U32[0] & ~0xFF000000)) == 0); + if (is_zero_clear && OI_GsMemClear() && clear_height_valid) { m_tc->InvalidateVideoMem(context->offset.fb, m_r, false, true); m_tc->InvalidateVideoMemType(GSTextureCache::RenderTarget, context->FRAME.Block()); - m_tc->InvalidateVideoMem(context->offset.zb, m_r, false, false); - m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, context->ZBUF.Block()); + + if (m_context->ZBUF.ZMSK == 0) + { + m_tc->InvalidateVideoMem(context->offset.zb, m_r, false, false); + m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, context->ZBUF.Block()); + } + return; } } @@ -1831,7 +1845,7 @@ void GSRendererHW::Draw() { // Constant Direct Write without texture/test/blending (aka a GS mem clear) if ((m_vt.m_primclass == GS_SPRITE_CLASS) && !PRIM->TME // Direct write - && (!PRIM->ABE || m_context->ALPHA.IsOpaque()) // No transparency + && (!PRIM->ABE || IsOpaque()) // No transparency && (m_context->FRAME.FBMSK == 0) // no color mask && !m_context->TEST.ATE // no alpha test && (!m_context->TEST.ZTE || m_context->TEST.ZTST == ZTST_ALWAYS) // no depth test @@ -4129,7 +4143,6 @@ GSRendererHW::Hacks::Hacks() m_oi_list.push_back(HackEntry(CRC::MetalSlug6, CRC::RegionCount, &GSRendererHW::OI_MetalSlug6)); m_oi_list.push_back(HackEntry(CRC::RozenMaidenGebetGarden, CRC::RegionCount, &GSRendererHW::OI_RozenMaidenGebetGarden)); m_oi_list.push_back(HackEntry(CRC::SonicUnleashed, CRC::RegionCount, &GSRendererHW::OI_SonicUnleashed)); - m_oi_list.push_back(HackEntry(CRC::SuperManReturns, CRC::RegionCount, &GSRendererHW::OI_SuperManReturns)); m_oi_list.push_back(HackEntry(CRC::ArTonelico2, CRC::RegionCount, &GSRendererHW::OI_ArTonelico2)); m_oi_list.push_back(HackEntry(CRC::Jak2, CRC::RegionCount, &GSRendererHW::OI_JakGames)); m_oi_list.push_back(HackEntry(CRC::Jak3, CRC::RegionCount, &GSRendererHW::OI_JakGames)); @@ -4240,7 +4253,7 @@ void GSRendererHW::OI_DoubleHalfClear(GSTextureCache::Target*& rt, GSTextureCach } // Striped double clear done by Powerdrome and Snoopy Vs Red Baron, it will clear in 32 pixel stripes half done by the Z and half done by the FRAME else if (rt && !ds && m_context->FRAME.FBP == m_context->ZBUF.ZBP && (m_context->FRAME.PSM & 0x30) != (m_context->ZBUF.PSM & 0x30) - && (m_context->FRAME.PSM & 0xF) == (m_context->ZBUF.PSM & 0xF) && (u32)(GSVector4i(m_vt.m_max.p).z) == 0) + && (m_context->FRAME.PSM & 0xF) == (m_context->ZBUF.PSM & 0xF) && m_vt.m_eq.z == 1) { const GSVertex* v = &m_vertex.buff[0]; @@ -4274,7 +4287,7 @@ bool GSRendererHW::OI_GsMemClear() r.z += 32; // Limit the hack to a single full buffer clear. Some games might use severals column to clear a screen // but hopefully it will be enough. - if (r.width() < ((static_cast(m_context->FRAME.FBW) - 1) * 64) || r.height() <= 128) + if (m_r.width() < ((static_cast(m_context->FRAME.FBW) - 1) * 64) || r.height() <= 128) return false; GL_INS("OI_GsMemClear (%d,%d => %d,%d)", r.x, r.y, r.z, r.w); @@ -4704,32 +4717,6 @@ bool GSRendererHW::OI_PointListPalette(GSTexture* rt, GSTexture* ds, GSTextureCa return true; } -bool GSRendererHW::OI_SuperManReturns(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) -{ - // Instead to use a fullscreen rectangle they use a 32 pixels, 4096 pixels with a FBW of 1. - // Technically the FB wrap/overlap on itself... - const GSDrawingContext* ctx = m_context; -#ifndef NDEBUG - GSVertex* v = &m_vertex.buff[0]; -#endif - - if (!(ctx->FRAME.FBP == ctx->ZBUF.ZBP && !PRIM->TME && !ctx->ZBUF.ZMSK && !ctx->FRAME.FBMSK && m_vt.m_eq.rgba == 0xFFFF)) - return true; - - // Please kill those crazy devs! - ASSERT(m_vertex.next == 2); - ASSERT(m_vt.m_primclass == GS_SPRITE_CLASS); - ASSERT((v->RGBAQ.A << 24 | v->RGBAQ.B << 16 | v->RGBAQ.G << 8 | v->RGBAQ.R) == (int)v->XYZ.Z); - - // Do a direct write - g_gs_device->ClearRenderTarget(rt, GSVector4(m_vt.m_min.c)); - - m_tc->InvalidateVideoMemType(GSTextureCache::DepthStencil, ctx->FRAME.Block()); - GL_INS("OI_SuperManReturns"); - - return false; -} - bool GSRendererHW::OI_ArTonelico2(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t) { // world map clipping diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 941d43a3bb..cfc7b09d49 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -54,7 +54,6 @@ private: 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_SuperManReturns(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); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 9349823bcb..5f1c373020 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -713,6 +713,8 @@ void GSTextureCache::ScaleTargetForDisplay(Target* t, const GIFRegTEX0& dispfb, // Take that into consideration to find the extent of the target which will be sampled. GSTexture* old_texture = t->m_texture; + const int old_width = static_cast(static_cast(old_texture->GetWidth()) / old_texture->GetScale().x); + const int old_height = static_cast(static_cast(old_texture->GetHeight()) / old_texture->GetScale().y); const int needed_height = std::min(real_h + y_offset, GSRendererHW::MAX_FRAMEBUFFER_HEIGHT); const int scaled_needed_height = std::max(static_cast(static_cast(needed_height) * old_texture->GetScale().y), old_texture->GetHeight()); const int needed_width = std::min(real_w, static_cast(dispfb.TBW * 64)); @@ -737,9 +739,22 @@ void GSTextureCache::ScaleTargetForDisplay(Target* t, const GIFRegTEX0& dispfb, g_gs_device->Recycle(old_texture); t->m_texture = new_texture; - const GSVector4i newrect = GSVector4i(0, 0, t->m_TEX0.TBW * 64, needed_height); // We unconditionally preload the frame here, because otherwise we'll end up with blackness for one frame (when the expand happens). - AddDirtyRectTarget(t, newrect, t->m_TEX0.PSM, t->m_TEX0.TBW); + const int preload_width = t->m_TEX0.TBW * 64; + if (old_width < preload_width && old_height < needed_height) + { + const GSVector4i right(old_width, 0, preload_width, needed_height); + const GSVector4i bottom(0, old_height, old_width, needed_height); + AddDirtyRectTarget(t, right, t->m_TEX0.PSM, t->m_TEX0.TBW); + AddDirtyRectTarget(t, bottom, t->m_TEX0.PSM, t->m_TEX0.TBW); + } + else + { + const GSVector4i newrect = GSVector4i((old_height < scaled_needed_height) ? 0 : old_width, + (old_width < preload_width) ? 0 : old_height, + preload_width, needed_height); + AddDirtyRectTarget(t, newrect, t->m_TEX0.PSM, t->m_TEX0.TBW); + } // Inject the new height back into the cache. GetTargetHeight(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, static_cast(needed_height));