diff --git a/bin/resources/GameIndex.yaml b/bin/resources/GameIndex.yaml index 15c17a5ae9..4271b5a74d 100644 --- a/bin/resources/GameIndex.yaml +++ b/bin/resources/GameIndex.yaml @@ -13621,6 +13621,7 @@ SLED-53731: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLED-53732: name: "Spartan - Total Warrior [Demo]" region: "PAL" @@ -23621,6 +23622,7 @@ SLES-53729: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLES-53730: name: "Battlefield 2 - Modern Combat" region: "PAL-M3" @@ -23630,6 +23632,7 @@ SLES-53730: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLES-53734: name: "50 Cent - Bulletproof" region: "PAL-E" @@ -31306,6 +31309,7 @@ SLKA-25330: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLKA-25331: name: "Marc Ecko's Getting Up - Contents Under Pressure" region: "NTSC-K" @@ -32194,6 +32198,7 @@ SLPM-55034: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLPM-55035: name: "ファイトナイト ラウンド2 [EA:SY! 1980]" name-sort: "ふぁいとないと らうんど2 [EA:SY! 1980]" @@ -46305,6 +46310,7 @@ SLPM-66206: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLPM-66207: name: "どろろ [SEGA THE BEST 2800]" name-sort: "どろろ [SEGA THE BEST 2800]" @@ -49096,6 +49102,7 @@ SLPM-66651: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLPM-66652: name: "バーンアウト リベンジ [EA BEST HITS]" name-sort: "ばーんあうと りべんじ [EA BEST HITS]" @@ -66986,6 +66993,7 @@ SLUS-21026: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLUS-21027: name: "The Lord of the Rings - The Third Age" name-sort: "Lord of the Rings, The - The Third Age" @@ -72938,6 +72946,7 @@ SLUS-29117: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLUS-29118: name: "Need for Speed - Underground 2 [Demo]" region: "NTSC-U" @@ -73090,6 +73099,7 @@ SLUS-29152: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLUS-29153: name: "Burnout Revenge [Demo]" region: "NTSC-U" @@ -73205,6 +73215,7 @@ SLUS-29172: halfPixelOffset: 2 # Offset post-processing. texturePreloading: 1 # Improves performance. textureInsideRT: 1 # Fixes per line drawing. + getSkipCount: "GSC_Battlefield2" # Depth clear. SLUS-29173: name: "The Sims 2 [Demo]" name-sort: "Sims 2, The [Demo]" diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index b23000d94b..adb28d0777 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -4468,7 +4468,7 @@ bool GSState::GSTransferBuffer::Update(int tw, int th, int bpp, int& len) { if (len > packet_size) { -#if defined(PCSX2_DEVBUILD) || defined(_DEBUG) +#if defined(_DEBUG) Console.Warning("GS transfer buffer overflow len %d remaining %d, tex_size %d tw %d th %d bpp %d", len, remaining, tex_size, tw, th, bpp); #endif } diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.cpp b/pcsx2/GS/Renderers/HW/GSHwHack.cpp index 8341864503..d7db96ba77 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.cpp +++ b/pcsx2/GS/Renderers/HW/GSHwHack.cpp @@ -742,7 +742,7 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip) for (u32 channel = 0; channel < 3; channel++) { - const GIFRegTEX0 TEX0 = GIFRegTEX0::Create(base + channel * page_offset, RTEX0.TBW, PSMCT32); + const GIFRegTEX0 TEX0 = GIFRegTEX0::Create(base + channel * page_offset, 10, PSMCT32); GSTextureCache::Target* dst = g_texture_cache->LookupTarget(TEX0, src->GetUnscaledSize(), src->GetScale(), GSTextureCache::RenderTarget, true, fbmsk); if (!dst) { @@ -775,6 +775,35 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip) } } + +bool GSHwHack::GSC_Battlefield2(GSRendererHW& r, int& skip) +{ + if (skip == 0) + { + if (RZBP >= RFBP && RFBP >= 0x2000 && RZBP >= 0x2700 && ((RZBP - RFBP) == 0x700)) + { + skip = 7; + + GIFRegTEX0 TEX0 = {}; + TEX0.TBP0 = RFBP; + TEX0.TBW = 8; + GSTextureCache::Target* dst = g_texture_cache->LookupTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil); + + if (!dst) + dst = g_texture_cache->CreateTarget(TEX0, r.GetTargetSize(), r.GetValidSize(nullptr), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil, + true, 0, false, false, false, GSVector4i(0,0,1,1), nullptr); + + if (dst) + { + float dc = r.m_vertex.buff[1].XYZ.Z; + g_gs_device->ClearDepth(dst->m_texture, dc * std::exp2(-32.0f)); + } + } + } + + return true; +} + bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, int& skip) { GSDrawingContext* context = r.m_context; @@ -1064,16 +1093,29 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Target* rt_again = g_texture_cache->LookupTarget(Frame, src_size, src->m_scale, GSTextureCache::RenderTarget); if ((rt_again->m_TEX0.PSM & 0x3) == PSMCT16) { - GSVector4i dRect = rt_again->m_valid; + GSVector4 dRect; - dRect = GSVector4i(GSVector4(GSVector4i::loadh(rt_again->m_unscaled_size)) * rt_again->m_scale); + GSVector4 source_rect = GSVector4(static_cast(rt_again->m_valid.x) / static_cast(rt_again->m_unscaled_size.x), static_cast(rt_again->m_valid.y) / static_cast(rt_again->m_unscaled_size.y), + static_cast(rt_again->m_valid.z) / static_cast(rt_again->m_unscaled_size.x), static_cast(rt_again->m_valid.w) / static_cast(rt_again->m_unscaled_size.y)); + + dRect = GSVector4(rt_again->m_valid) * rt_again->m_scale; dRect.y /= 2; dRect.w /= 2; rt_again->m_valid.y /= 2; rt_again->m_valid.w /= 2; rt_again->m_TEX0.PSM = PSMCT32; - rt_again->ResizeTexture(rt_again->m_unscaled_size.x, rt_again->m_unscaled_size.y / 2, true, true, dRect, false); - rt = rt_again->m_texture; + GSTexture* tex = g_gs_device->CreateRenderTarget(rt_again->m_unscaled_size.x * rt_again->m_scale, rt_again->m_unscaled_size.y * rt_again->m_scale, GSTexture::Format::Color, false); + + if (!tex) + return false; + + + g_gs_device->StretchRect(rt_again->m_texture, source_rect, tex, dRect, ShaderConvert::COPY, false); + + + g_gs_device->Recycle(rt_again->m_texture); + rt_again->m_texture = tex; + rt = tex; } GSVector2i rt_size(rt->GetSize()); @@ -1457,6 +1499,7 @@ const GSHwHack::Entry GSHwHack::s_get_skip_count_function CRC_F(GSC_PolyphonyDigitalGames), CRC_F(GSC_MetalGearSolid3), CRC_F(GSC_HitmanBloodMoney), + CRC_F(GSC_Battlefield2), // Channel Effect CRC_F(GSC_SteambotChronicles), diff --git a/pcsx2/GS/Renderers/HW/GSHwHack.h b/pcsx2/GS/Renderers/HW/GSHwHack.h index 68c5cbb5fc..bd1e24bec0 100644 --- a/pcsx2/GS/Renderers/HW/GSHwHack.h +++ b/pcsx2/GS/Renderers/HW/GSHwHack.h @@ -26,6 +26,7 @@ public: static bool GSC_SteambotChronicles(GSRendererHW& r, int& skip); static bool GSC_BlueTongueGames(GSRendererHW& r, int& skip); static bool GSC_NFSUndercover(GSRendererHW& r, int& skip); + static bool GSC_Battlefield2(GSRendererHW& r, int& skip); static bool GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip); static bool GSC_MetalGearSolid3(GSRendererHW& r, int& skip); static bool GSC_HitmanBloodMoney(GSRendererHW& r, int& skip); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index b567d6d462..9dc0988ae9 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -2252,19 +2252,29 @@ void GSRendererHW::Draw() //DevCon.Warning("Skipped %d draw %d was abort %d", num_skipped_channel_shuffle_draws, s_n, (int)m_channel_shuffle_abort); // Some games like Tomb raider abort early, we're never going to know the real height, and the system doesn't work right for partials. // But it's good enough for games like Hitman Blood Money which only shuffle part of the screen + const int width = std::max(static_cast(m_last_rt->m_TEX0.TBW) * 64, 64); + const int shuffle_height = (((num_skipped_channel_shuffle_draws + 1 + (std::max(1, (width / 64) - 1))) * 64) / width) * 32; + const int shuffle_width = std::min((num_skipped_channel_shuffle_draws + 1) * 64, static_cast(width)); + GSVector4i valid_area = GSVector4i::loadh(GSVector2i(shuffle_width, shuffle_height)); + const int offset = (((m_last_channel_shuffle_fbp + 0x20) - m_last_rt->m_TEX0.TBP0) >> 5) - (num_skipped_channel_shuffle_draws + 1); + + if (offset) + { + int vertical_offset = (offset / std::max(1U, m_channel_shuffle_width)) * 32; + valid_area.y += vertical_offset; + valid_area.w += vertical_offset; + } if (!m_full_screen_shuffle) { - const u32 width_pages = ((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64; - ; - m_conf.scissor.w = m_conf.scissor.y + (((num_skipped_channel_shuffle_draws + 1 + (m_channel_shuffle_width - 1)) / std::max(1U, m_channel_shuffle_width)) * 32) * m_conf.cb_ps.ScaleFactor.z; - if (width_pages) - m_conf.scissor.z = m_conf.scissor.x + (((num_skipped_channel_shuffle_draws + 1) % std::max(1U, m_channel_shuffle_width) % std::max(1U, m_channel_shuffle_width)) * 64) * m_conf.cb_ps.ScaleFactor.z; - - m_last_rt->UpdateValidity(GSVector4i::loadh(m_last_rt->m_unscaled_size).rintersect(GSVector4i(GSVector4(m_conf.scissor) / m_conf.cb_ps.ScaleFactor.z)), true); + m_conf.scissor.w = m_conf.scissor.y + shuffle_height * m_conf.cb_ps.ScaleFactor.z; + if (shuffle_width) + m_conf.scissor.z = m_conf.scissor.x + (shuffle_width * m_conf.cb_ps.ScaleFactor.z); + else + m_conf.scissor.z = std::min(m_conf.scissor.z, static_cast((m_channel_shuffle_width * 64) * m_conf.cb_ps.ScaleFactor.z)); } - else - m_last_rt->UpdateValidity(m_channel_shuffle_src_valid); + + m_last_rt->UpdateValidity(valid_area); g_gs_device->RenderHW(m_conf); @@ -2303,6 +2313,7 @@ void GSRendererHW::Draw() m_channel_shuffle_width = 0; m_full_screen_shuffle = false; m_channel_shuffle_abort = false; + m_channel_shuffle_src_valid = GSVector4i::zero(); GL_PUSH("HW Draw %d (Context %u)", s_n, PRIM->CTXT); GL_INS("FLUSH REASON: %s%s", GetFlushReasonString(m_state_flush_reason), @@ -3092,7 +3103,7 @@ void GSRendererHW::Draw() // FBW is going to be wrong for channel shuffling into a new target, so take it from the source. FRAME_TEX0.U64 = 0; FRAME_TEX0.TBP0 = ((m_last_channel_shuffle_end_block + 1) == m_cached_ctx.FRAME.Block() && possible_shuffle) ? m_last_channel_shuffle_fbp : m_cached_ctx.FRAME.Block(); - FRAME_TEX0.TBW = (possible_horizontal_texture_shuffle || (possible_shuffle && src && src->m_from_target && IsPossibleChannelShuffle())) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; + FRAME_TEX0.TBW = (possible_horizontal_texture_shuffle || (possible_shuffle && src && src->m_from_target && IsPossibleChannelShuffle()&& m_cached_ctx.FRAME.FBW <= 2)) ? src->m_from_target_TEX0.TBW : m_cached_ctx.FRAME.FBW; FRAME_TEX0.PSM = m_cached_ctx.FRAME.PSM; // Don't clamp on shuffle, the height cache may troll us with the REAL height. @@ -3189,6 +3200,11 @@ void GSRendererHW::Draw() CleanupDraw(true); return; } + + if (IsPageCopy() && m_cached_ctx.FRAME.FBW == 1) + { + rt->UpdateValidity(GSVector4i::loadh(GSVector2i(GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.x, GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.y)), true); + } } else if (rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block()) { @@ -3220,6 +3236,15 @@ void GSRendererHW::Draw() rt->m_drawn_since_read.y += new_offset; rt->m_drawn_since_read.w += new_offset; + if (rt->m_dirty.size()) + { + for (int i = 0; i < rt->m_dirty.size(); i++) + { + rt->m_dirty[i].r.y += new_offset; + rt->m_dirty[i].r.w += new_offset; + } + } + t_size.y += std::abs(vertical_offset); vertical_offset = 0; } @@ -3231,63 +3256,66 @@ void GSRendererHW::Draw() rt->m_TEX0.TBP0 += horizontal_offset; horizontal_offset = 0; } - // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? - if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) + + if (vertical_offset || horizontal_offset) { + // Z isn't offset but RT is, so we need a temp Z to align it, hopefully nothing will ever write to the Z too, right?? + if (ds && vertical_offset && (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) != (m_cached_ctx.FRAME.Block() - rt->m_TEX0.TBP0)) + { - const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; - GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); - GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(vertical_offset + m_r.w + 1, vertical_offset + ds->m_unscaled_size.y) * ds->m_scale); - const int new_height = std::max(static_cast(ds->m_unscaled_size.y * ds->m_scale), dRect.w); - GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); - g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, std::min(z_vertical_offset + m_r.w + 1, ds->m_unscaled_size.y) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); + const int z_vertical_offset = ((static_cast(m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0) / 32) / std::max(rt->m_TEX0.TBW, 1U)) * GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].pgs.y; + GL_CACHE("RT in RT Z copy on draw %d z_vert_offset %d z_offset %d", s_n, z_vertical_offset, vertical_offset); + GSVector4i dRect = GSVector4i(0, vertical_offset * ds->m_scale, ds->m_unscaled_size.x * ds->m_scale, std::min(vertical_offset + m_r.w + 1, vertical_offset + ds->m_unscaled_size.y) * ds->m_scale); + const int new_height = std::max(static_cast(ds->m_unscaled_size.y * ds->m_scale), dRect.w); + GSTexture* tex = g_gs_device->CreateDepthStencil(ds->m_unscaled_size.x * ds->m_scale, new_height, GSTexture::Format::DepthStencil, true); + g_gs_device->StretchRect(ds->m_texture, GSVector4(0.0f, z_vertical_offset / static_cast(ds->m_unscaled_size.y), 1.0f, std::min(z_vertical_offset + m_r.w + 1, ds->m_unscaled_size.y) / static_cast(ds->m_unscaled_size.y)), tex, GSVector4(dRect), ShaderConvert::DEPTH_COPY, false); - g_texture_cache->SetTemporaryZ(tex); - } - - GSVertex* v = &m_vertex.buff[0]; - - for (u32 i = 0; i < m_vertex.tail; i++) - { - v[i].XYZ.X += horizontal_offset << 4; - v[i].XYZ.Y += vertical_offset << 4; - } - - if (texture_offset && src && src->m_from_target && src->m_target_direct && src->m_from_target == rt) - { - GSVector4i src_region = src->GetRegionRect(); - - if (src_region.rempty()) - { - src_region = GSVector4i::loadh(rt->m_unscaled_size); - src_region.y += texture_offset; + g_texture_cache->SetTemporaryZ(tex); } - else + + GSVertex* v = &m_vertex.buff[0]; + + for (u32 i = 0; i < m_vertex.tail; i++) { - src_region.y += texture_offset; - src_region.w += texture_offset; + v[i].XYZ.X += horizontal_offset << 4; + v[i].XYZ.Y += vertical_offset << 4; } - src->m_region.SetX(src_region.x, src_region.z); - src->m_region.SetY(src_region.y, src_region.w); + + if (texture_offset && src && src->m_from_target && src->m_target_direct && src->m_from_target == rt) + { + GSVector4i src_region = src->GetRegionRect(); + + if (src_region.rempty()) + { + src_region = GSVector4i::loadh(rt->m_unscaled_size); + src_region.y += texture_offset; + } + else + { + src_region.y += texture_offset; + src_region.w += texture_offset; + } + src->m_region.SetX(src_region.x, src_region.z); + src->m_region.SetY(src_region.y, src_region.w); + } + + m_context->scissor.in.x += horizontal_offset; + m_context->scissor.in.z += horizontal_offset; + m_context->scissor.in.y += vertical_offset; + m_context->scissor.in.w += vertical_offset; + m_r.y += vertical_offset; + m_r.w += vertical_offset; + m_r.x += horizontal_offset; + m_r.z += horizontal_offset; + m_in_target_draw = rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block(); + m_vt.m_min.p.x += horizontal_offset; + m_vt.m_max.p.x += horizontal_offset; + m_vt.m_min.p.y += vertical_offset; + m_vt.m_max.p.y += vertical_offset; + + t_size.x = rt->m_unscaled_size.x - horizontal_offset; + t_size.y = rt->m_unscaled_size.y - vertical_offset; } - - m_context->scissor.in.x += horizontal_offset; - m_context->scissor.in.z += horizontal_offset; - m_context->scissor.in.y += vertical_offset; - m_context->scissor.in.w += vertical_offset; - m_r.y += vertical_offset; - m_r.w += vertical_offset; - m_r.x += horizontal_offset; - m_r.z += horizontal_offset; - m_in_target_draw = rt->m_TEX0.TBP0 != m_cached_ctx.FRAME.Block(); - m_vt.m_min.p.x += horizontal_offset; - m_vt.m_max.p.x += horizontal_offset; - m_vt.m_min.p.y += vertical_offset; - m_vt.m_max.p.y += vertical_offset; - - t_size.x = rt->m_unscaled_size.x - horizontal_offset; - t_size.y = rt->m_unscaled_size.y - vertical_offset; - // Don't resize if the BPP don't match. if (frame_psm.bpp == GSLocalMemory::m_psm[rt->m_TEX0.PSM].bpp) { @@ -3304,6 +3332,10 @@ void GSRendererHW::Draw() rt->UpdateValidity(m_r, !frame_masked); rt->UpdateDrawn(m_r, !frame_masked); } + else if (IsPageCopy() && m_cached_ctx.FRAME.FBW == 1) + { + rt->UpdateValidity(GSVector4i::loadh(GSVector2i(GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.x, GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs.y + vertical_offset)), true); + } } } @@ -3320,6 +3352,7 @@ void GSRendererHW::Draw() if (ds && ds->m_scale != target_scale) { const GSVector2i unscaled_size(ds->m_unscaled_size.x, ds->m_unscaled_size.y); + ds->m_scale = 1; ds->ResizeTexture(ds->m_unscaled_size.x * target_scale, ds->m_unscaled_size.y * target_scale, true); // Slightly abusing the texture resize. ds->m_scale = target_scale; @@ -3668,7 +3701,7 @@ void GSRendererHW::Draw() // The FBW should also be okay, since it's coming from the source. if (rt) { - const bool update_fbw = !m_in_target_draw && (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack()); + const bool update_fbw = (FRAME_TEX0.TBW != rt->m_TEX0.TBW || rt->m_TEX0.TBW == 1) && !m_in_target_draw && (m_channel_shuffle && src->m_target) && (!PRIM->ABE || IsOpaque() || m_context->ALPHA.IsBlack()); rt->m_TEX0.TBW = update_fbw ? ((src && src->m_from_target && src->m_32_bits_fmt) ? src->m_from_target->m_TEX0.TBW : FRAME_TEX0.TBW) : std::max(rt->m_TEX0.TBW, FRAME_TEX0.TBW); rt->m_TEX0.PSM = FRAME_TEX0.PSM; } @@ -3799,8 +3832,8 @@ void GSRendererHW::Draw() g_texture_cache->GetTargetSize(rt->m_TEX0.TBP0, rt->m_TEX0.TBW, rt->m_TEX0.PSM, 0, new_h); } - rt->ResizeValidity(rt->GetUnscaledRect()); - rt->ResizeDrawn(rt->GetUnscaledRect()); + rt->ResizeValidity(rt->m_valid.rintersect(rt->GetUnscaledRect())); + rt->ResizeDrawn(rt->m_drawn_since_read.rintersect(rt->GetUnscaledRect())); } const bool rt_update = can_update_size || (m_texture_shuffle && (src && rt && src->m_from_target != rt)); @@ -3917,6 +3950,7 @@ void GSRendererHW::Draw() src->m_shared_texture = false; src->m_texture = new_tex; src->m_unscaled_size = new_size; + src->m_TEX0.PSM = m_cached_ctx.TEX0.PSM; } } } @@ -3958,8 +3992,8 @@ void GSRendererHW::Draw() if (GSConfig.SaveTexture && src) { - s = GetDrawDumpPath("%05d_f%05lld_itex_%05x_%s_%d%d_%02x_%02x_%02x_%02x.dds", - s_n, frame, static_cast(m_cached_ctx.TEX0.TBP0), psm_str(m_cached_ctx.TEX0.PSM), + s = GetDrawDumpPath("%05d_f%05lld_itex_%s_%05x(%05x)_%s_%d%d_%02x_%02x_%02x_%02x.dds", + s_n, frame, (src->m_from_target ? "tgt" : "gs"), static_cast(m_cached_ctx.TEX0.TBP0), (src->m_from_target ? src->m_from_target->m_TEX0.TBP0 : src->m_TEX0.TBP0), psm_str(m_cached_ctx.TEX0.PSM), static_cast(m_cached_ctx.CLAMP.WMS), static_cast(m_cached_ctx.CLAMP.WMT), static_cast(m_cached_ctx.CLAMP.MINU), static_cast(m_cached_ctx.CLAMP.MAXU), static_cast(m_cached_ctx.CLAMP.MINV), static_cast(m_cached_ctx.CLAMP.MAXV)); @@ -4134,7 +4168,7 @@ void GSRendererHW::Draw() if (rt && GSConfig.SaveRT && !m_last_rt) { - s = GetDrawDumpPath("%05d_f%05lld_rt1_%05x_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), psm_str(m_cached_ctx.FRAME.PSM)); + s = GetDrawDumpPath("%05d_f%05lld_rt1_%05x_(%05x)_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), rt->m_TEX0.TBP0, psm_str(m_cached_ctx.FRAME.PSM)); rt->m_texture->Save(s); } @@ -4790,7 +4824,7 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool const GSLocalMemory::psm_t frame_psm = GSLocalMemory::m_psm[m_context->FRAME.PSM]; m_full_screen_shuffle = (m_r.height() > frame_psm.pgs.y) || (m_r.width() > frame_psm.pgs.x) || GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled; m_channel_shuffle_src_valid = src->m_valid; - if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || (!m_in_target_draw && IsPageCopy()) || m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle) + if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || ((src->m_TEX0.TBW == rt->m_TEX0.TBW) && (!m_in_target_draw && IsPageCopy())) || m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle) { GSVertex* s = &m_vertex.buff[0]; s[0].XYZ.X = static_cast(m_context->XYOFFSET.OFX + 0); @@ -4859,9 +4893,15 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool GSVector4i new_valid = rt->m_valid; int offset_height = static_cast((((frame_offset - rt->m_TEX0.TBP0) >> 5) / rt->m_TEX0.TBW) * frame_psm.pgs.y) + frame_psm.pgs.y; + const int get_next_ctx = (m_state_flush_reason == CONTEXTCHANGE) ? m_env.PRIM.CTXT : m_backed_up_ctx; + const GSDrawingContext& next_ctx = m_env.CTXT[get_next_ctx]; + const u32 safe_TBW = std::max(rt->m_TEX0.TBW, 1U); // This is an annoying case where the draw is offset to draw on the right hand side of a texture (Hitman Blood Money pause screen). - if (!IsPageCopy() && NextDrawMatchesShuffle() && ((frame_offset >> 5) + 1) % rt->m_TEX0.TBW == 0) + if (m_state_flush_reason == GSFlushReason::CONTEXTCHANGE && !IsPageCopy() && NextDrawMatchesShuffle() && next_ctx.FRAME.FBP > m_cached_ctx.FRAME.FBP && (next_ctx.FRAME.FBP < (m_cached_ctx.FRAME.FBP + safe_TBW)) && + (next_ctx.FRAME.FBP - m_cached_ctx.FRAME.FBP) < safe_TBW && (next_ctx.FRAME.FBP % safe_TBW) != ((m_cached_ctx.FRAME.FBP % safe_TBW) + 1)) + { offset_height += frame_psm.pgs.y; + } new_valid.w = std::max(new_valid.w, offset_height); rt->UpdateValidity(new_valid, true); diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index cc75a6ff76..3bda1bc33e 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -309,7 +309,7 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw const int vertical_offset = in_rect.y / t_psm.pgs.y; const int horizontal_offset = in_rect.x / t_psm.pgs.x; const int rect_offset = horizontal_offset + (vertical_offset * src_pgw); - const int rect_pages = ((in_rect.width() / t_psm.pgs.x) % src_pgw) + ((in_rect.height() / t_psm.pgs.y) * src_pgw); + const int rect_pages = std::max(((in_rect.width() / t_psm.pgs.x) % src_pgw) + ((in_rect.height() / t_psm.pgs.y) * src_pgw), 1); page_offset += rect_offset; in_rect -= GSVector4i(horizontal_offset * t_psm.pgs.x, vertical_offset * t_psm.pgs.y).xyxy(); @@ -387,7 +387,6 @@ GSVector4i GSTextureCache::TranslateAlignedRectByPage(u32 tbp, u32 tebp, u32 tbw { //TODO: Maybe control dirty blocks directly and add them page at a time for better granularity. const GSVector2i start_page = GSVector2i((page_offset + rect_offset) % dst_pgw, page_offset / dst_pgw); - DevCon.Warning("Fudging start position"); // Not easily translatable full pages and make sure the height is rounded upto encompass the half row. new_rect.x = start_page.x * dst_page_size.x; new_rect.z = new_rect.x + in_rect.z; @@ -1646,6 +1645,12 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const DevCon.Warning("BP %x - 8bit bad match for target bp %x bw %d src %d format %d", bp, t->m_TEX0.TBP0, t->m_TEX0.TBW, bw, t->m_TEX0.PSM); continue; } + else if (!possible_shuffle && GSLocalMemory::m_psm[psm].trbpp == 8 && TEX0.TBW == 1) + { + DevCon.Warning("Too small for relocation, skipping"); + continue; + } + // PSM equality needed because CreateSource does not handle PSM conversion. // Only inclusive hit to limit false hits. GSVector4i rect = block_boundary_rect; @@ -1672,6 +1677,12 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const } if (bp > t->m_TEX0.TBP0) { + if (!region.HasEither() && GSLocalMemory::m_psm[psm].bpp == 32 && (t->m_TEX0.TBW - (((bp - t->m_TEX0.TBP0) >> 5) % t->m_TEX0.TBW)) < static_cast((block_boundary_rect.width() + 63) / 64)) + { + DevCon.Warning("Bad alignmenet"); + continue; + } + GSVector4i new_rect = (GSLocalMemory::m_psm[color_psm].bpp != GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && (psm & 0x7) != PSMCT16) ? block_boundary_rect : rect; // Check if it is possible to hit with valid offset on the given Target. @@ -2089,7 +2100,6 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe i++; continue; } - // if It's an old target and it's being completely overwritten, kill it. // Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But, // it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing @@ -2107,7 +2117,23 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe can_use = !t->m_dirty.GetTotalRect(TEX0, size).rintersect(size_rect).eq(size_rect); } } + else if (type == RenderTarget && (fbmask == 0xffffff && !t->m_was_dst_matched && TEX0.TBW != t->m_TEX0.TBW)) + { + // When returning to being matched with the Z buffer in width, we need to make sure the RGB is up to date as it could get used later (Hitman Contracts). + auto& rev_list = m_dst[1 - type]; + Target* dst_match = nullptr; + for (auto j = rev_list.begin(); j != rev_list.end(); ++j) + { + Target* ds = *j; + if (t->m_TEX0.TBP0 != ds->m_TEX0.TBP0 || !ds->m_valid_rgb || TEX0.TBW != ds->m_TEX0.TBW) + continue; + + t->m_was_dst_matched = true; + t->m_valid_rgb = false; + break; + } + } // TODO: What might be a nicer solution than this, is to rearrange the targets to match the new layout, however this comes with some caviets: // 1. They can draw wider than the FBW // 2. The dirty+valid rects will need to also be rearranged @@ -2175,7 +2201,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM]; // I know what you're thinking, and I hate the guy who wrote it too (me). Project Snowblind, Tomb Raider etc decide to offset where they're drawing using a channel shuffle, and this gets messy, so best just to kill the old target. - if (is_shuffle && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src && src->m_from_target && src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 && widthpage_offset && src->m_from_target != t) + if (is_shuffle && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src && src->m_from_target && (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 || (((src->m_TEX0.TBP0 - src->m_from_target->m_TEX0.TBP0) >> 5) % std::max(src->m_from_target->m_TEX0.TBW, 1U) == 0)) && widthpage_offset && src->m_from_target != t) { GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s offset overwrite shuffle", t->m_TEX0.TBP0, t->m_TEX0.TBW, psm_str(t->m_TEX0.PSM)); InvalidateSourcesFromTarget(t); @@ -2195,7 +2221,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe continue; } - else if (t->m_dirty.empty()) + else if (t->m_dirty.empty() || (t->m_TEX0.TBP0 <= bp && t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size).rintersect(GSVector4i(0, 0, 0, 0) .max_i32(TranslateAlignedRectByPage(t, TEX0.TBP0, TEX0.PSM, TEX0.TBW, min_rect))).rempty())) { if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63)/ 64) > 1) { @@ -2362,7 +2388,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->m_unscaled_size = new_size; dst->m_downscaled = scale == 1.0f && g_gs_renderer->GetUpscaleMultiplier() > 1.0f; - if (src && src->m_target && src->m_from_target == dst) + if (src && src->m_target && src->m_from_target == dst && src->m_shared_texture) { src->m_texture = dst->m_texture; src->m_scale = dst->m_scale; @@ -2391,32 +2417,39 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe dst->Update(dst->m_alpha_max <= 128); const bool scale_down = GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp > GSLocalMemory::m_psm[TEX0.PSM].bpp; + bool req_copy = true; new_size = dst->m_unscaled_size; new_scaled_size = ScaleRenderTargetSize(dst->m_unscaled_size, dst->m_scale); - dRect = (GSVector4(GSVector4i::loadh(dst->m_unscaled_size)) * GSVector4(dst->m_scale)).ceil(); + dRect = (GSVector4(dst->m_valid) * GSVector4(dst->m_scale)).ceil(); + GSVector4 source_rect = GSVector4(static_cast(dst->m_valid.x) / static_cast(dst->m_unscaled_size.x), static_cast(dst->m_valid.y) / static_cast(dst->m_unscaled_size.y), + static_cast(dst->m_valid.z) / static_cast(dst->m_unscaled_size.x), static_cast(dst->m_valid.w) / static_cast(dst->m_unscaled_size.y)); if (!is_shuffle || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16) { if (scale_down) { - new_scaled_size.y *= 2; dst->m_valid.y *= 2; dst->m_valid.w *= 2; dRect.y *= 2; dRect.w *= 2; - new_size.y *= 2; - new_size.y = std::max(dst->m_valid.w, new_size.y); + if (new_size.y < dst->m_valid.w) + { + new_size.y = dst->m_valid.w; + new_scaled_size = ScaleRenderTargetSize(new_size, dst->m_scale); + // Using our resize texture only really works if we're scaling exactly. + req_copy = source_rect.w != 1.0f; + } } else { - new_scaled_size.y /= 2; - new_size.y /= 2; dRect.y /= 2; dRect.w /= 2; dst->m_valid.y /= 2; dst->m_valid.w /= 2; - new_size.y = std::max(dst->m_valid.w, new_size.y); + req_copy = true; + /*new_size.y /= 2; + new_scaled_size.y = new_size.y * dst->m_scale;*/ } } if (!is_shuffle) @@ -2431,11 +2464,49 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe src->m_target_direct = false; src->m_shared_texture = false; - dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect), true); + if(!req_copy) + dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect), true); + else + { + GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, clear) : + g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, clear); + if (!tex) + return nullptr; + + g_gs_device->StretchRect(dst->m_texture, source_rect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); + + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + m_target_memory_usage = m_target_memory_usage + tex->GetMemUsage(); + + // Don't kill the target here as it's being used for the source. + dst->m_texture = tex; + dst->m_unscaled_size = new_size; + } } else { - dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect)); + if (!req_copy) + dst->ResizeTexture(new_size.x, new_size.y, true, true, GSVector4i(dRect)); + else + { + GSTexture* tex = type == RenderTarget ? g_gs_device->CreateRenderTarget(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::Color, clear) : + g_gs_device->CreateDepthStencil(new_scaled_size.x, new_scaled_size.y, GSTexture::Format::DepthStencil, clear); + if (!tex) + return nullptr; + + if (scale_down) + g_gs_device->StretchRect(dst->m_texture, source_rect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); + else + g_gs_device->StretchRect(dst->m_texture, source_rect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false); + + g_perfmon.Put(GSPerfMon::TextureCopies, 1); + m_target_memory_usage = (m_target_memory_usage - dst->m_texture->GetMemUsage()) + tex->GetMemUsage(); + + g_gs_device->Recycle(dst->m_texture); + + dst->m_texture = tex; + dst->m_unscaled_size = new_size; + } } } @@ -2540,7 +2611,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe } else { - if (!dst->m_dirty.empty()) + if (!dst->m_dirty.empty() && bp == dst->m_TEX0.TBP0) { GL_INS("TC: Clearing dirty list for %s[%x] because we're overwriting the whole target.", to_string(type), dst->m_TEX0.TBP0); dst->m_dirty.clear(); @@ -2797,8 +2868,10 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe if (!is_frame) { - // Not *strictly* correct if RGB is masked, but we won't use it as a texture if not.. - dst->m_valid_rgb = true; + // Not having this valid could make things explode, but I do enjoy watching the world burn (and this is actually more correct). + dst->m_valid_rgb =true; + + const u32 mask = GSLocalMemory::m_psm[TEX0.PSM].fmsk; // If there is an opposite target without valid RGB, we need to match them up auto& rev_list = m_dst[1 - type]; @@ -2807,15 +2880,19 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(GIFRegTEX0 TEX0, const GSVe Target* const rev_t = *j; if (rev_t->m_TEX0.TBP0 == dst->m_TEX0.TBP0 && GSLocalMemory::m_psm[rev_t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp) { - if (!rev_t->m_valid_rgb) + if (GSLocalMemory::m_psm[rev_t->m_TEX0.PSM].trbpp == 24 && ((fbmask & 0x00FFFFFF) & mask) == (mask & 0x00FFFFFF)) + dst->m_valid_rgb = false; + + if (!rev_t->m_valid_rgb && dst->m_valid_rgb) rev_t->m_was_dst_matched = true; + break; } ++j; } const int bpp = GSLocalMemory::m_psm[TEX0.PSM].trbpp; - const u32 mask = GSLocalMemory::m_psm[TEX0.PSM].fmsk; + // If the alpha is masked and preloaded, we need to say it's valid else textures might fail to use the whole texture if RGB is valid. if (((fbmask & 0xFF000000) & mask) != (mask & 0xFF000000) && bpp != 24) { @@ -7060,7 +7137,7 @@ void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect, bool can_res bool GSTextureCache::Target::ResizeTexture(int new_unscaled_width, int new_unscaled_height, bool recycle_old, bool require_new_rect, GSVector4i new_rect, bool keep_old) { - if (m_unscaled_size.x == new_unscaled_width && m_unscaled_size.y == new_unscaled_height) + if (m_unscaled_size.x == new_unscaled_width && m_unscaled_size.y == new_unscaled_height && !require_new_rect) return true; const GSVector2i size = m_texture->GetSize();