From 2550ad7fd1009dc0f36c0a6be7946588708e7ec2 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 29 Apr 2023 15:29:14 +1000 Subject: [PATCH] GS/HW: Detect clears spanning multiple sprites Fixes GT4 rendering to >1000x1000 targets. --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 37 +++++++++++++++++++++++--- pcsx2/GS/Renderers/HW/GSRendererHW.h | 1 + 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index a8343678c9..2eb16a1055 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -1667,6 +1667,8 @@ void GSRendererHW::Draw() if (is_zero_clear && OI_GsMemClear() && clear_height_valid) { + GL_INS("Clear draw with mem clear and valid clear height, invalidating."); + g_texture_cache->InvalidateVideoMem(context->offset.fb, m_r, false, true); g_texture_cache->InvalidateVideoMemType(GSTextureCache::RenderTarget, m_cached_ctx.FRAME.Block()); @@ -1835,8 +1837,7 @@ void GSRendererHW::Draw() // Normally we would use 1024 here to match the clear above, but The Godfather does a 1023x1023 draw instead // (very close to 1024x1024, but apparently the GS rounds down..). So, catch that here, we don't want to // create that target, because the clear isn't black, it'll hang around and never get invalidated. - const bool is_square = (t_size.y == t_size.x) && m_r.w >= 1023 && - ((m_index.tail == 2 && m_vt.m_primclass == GS_SPRITE_CLASS) || (m_index.tail == 6 && m_vt.m_primclass == GS_TRIANGLE_CLASS)); + const bool is_square = (t_size.y == t_size.x) && m_r.w >= 1023 && PrimitiveCoversWithoutGaps(); const bool is_clear = is_possible_mem_clear && is_square; rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, target_scale, GSTextureCache::RenderTarget, true, fm, false, is_clear, force_preload); @@ -1844,6 +1845,7 @@ void GSRendererHW::Draw() // Draw skipped because it was a clear and there was no target. if (!rt) { + GL_INS("Clear draw with no target, skipping."); cleanup_cancelled_draw(); OI_GsMemClear(); return; @@ -5055,7 +5057,7 @@ bool GSRendererHW::OI_GsMemClear() && (m_cached_ctx.FRAME.PSM & 0xF) == (m_cached_ctx.ZBUF.PSM & 0xF) && m_vt.m_eq.z == 1 && m_vertex.buff[1].XYZ.Z == m_vertex.buff[1].RGBAQ.U32[0]; // Limit it further to a full screen 0 write - if (((m_vertex.next == 2) || ZisFrame) && m_vt.m_eq.rgba == 0xFFFF) + if ((PrimitiveCoversWithoutGaps() || ZisFrame) && m_vt.m_eq.rgba == 0xFFFF) { const GSOffset& off = m_context->offset.fb; GSVector4i r = GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in)); @@ -5194,6 +5196,35 @@ bool GSRendererHW::IsBlendedOrOpaque() return (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsCdOutput()); } +bool GSRendererHW::PrimitiveCoversWithoutGaps() const +{ + // Draw shouldn't be offset. + if ((m_r.eq32(GSVector4i::zero())).mask() & 0xff00) + return false; + + // This is potentially wrong for fans/strips... + if (m_vt.m_primclass == GS_TRIANGLE_CLASS) + return (m_index.tail == 6); + else if (m_vt.m_primclass != GS_SPRITE_CLASS) + return false; + + // Simple case: one sprite. + if (m_index.tail == 2) + return true; + + // Borrowed from MergeSprite(). + const GSVertex* v = &m_vertex.buff[0]; + const int first_dpX = v[1].XYZ.X - v[0].XYZ.X; + for (u32 i = 0; i < m_vertex.next; i += 2) + { + const int dpX = v[i + 1].XYZ.X - v[i].XYZ.X; + if (dpX != first_dpX) + return false; + } + + return true; +} + bool GSRendererHW::IsConstantDirectWriteMemClear() { const bool direct_draw = (m_vt.m_primclass == GS_SPRITE_CLASS) || (m_index.tail == 6 && m_vt.m_primclass == GS_TRIANGLE_CLASS); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 8bc59117d2..826cbc29e0 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -54,6 +54,7 @@ private: bool CanUseSwSpriteRender(); bool IsConstantDirectWriteMemClear(); bool IsBlendedOrOpaque(); + bool PrimitiveCoversWithoutGaps() const; enum class CLUTDrawTestResult {