Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GS/HW: Backport some fixes from RT in RT PR #12185

Merged
merged 5 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pcsx2-gsrunner/test_check_dumps.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def check_regression_test(baselinedir, testdir, name):
if not os.path.isfile(path2):
print("--- Frame %u for %s is missing in test set" % (framenum, name))
write("<h1>{}</h1>".format(name))
write("--- Frame %u for %s is missing in test set" % (framenum, name))
write("<pre>--- Frame %u for %s is missing in test set</pre>" % (framenum, name))
return False

if not compare_frames(path1, path2):
Expand Down
35 changes: 21 additions & 14 deletions pcsx2/GS/Renderers/HW/GSRendererHW.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -952,11 +952,11 @@ GSVector2i GSRendererHW::GetValidSize(const GSTextureCache::Source* tex)
return GSVector2i(width, height);
}

GSVector2i GSRendererHW::GetTargetSize(const GSTextureCache::Source* tex)
GSVector2i GSRendererHW::GetTargetSize(const GSTextureCache::Source* tex, const bool can_expand)
{
const GSVector2i valid_size = GetValidSize(tex);

return g_texture_cache->GetTargetSize(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, valid_size.x, valid_size.y);
return g_texture_cache->GetTargetSize(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, valid_size.x, valid_size.y, can_expand);
}

bool GSRendererHW::IsPossibleChannelShuffle() const
Expand Down Expand Up @@ -2553,7 +2553,7 @@ void GSRendererHW::Draw()
const u32 color_mask = (m_vt.m_max.c > GSVector4i::zero()).mask();
const bool texture_function_color = m_cached_ctx.TEX0.TFX == TFX_DECAL || (color_mask & 0xFFF) || (m_cached_ctx.TEX0.TFX > TFX_DECAL && (color_mask & 0xF000));
const bool texture_function_alpha = m_cached_ctx.TEX0.TFX != TFX_MODULATE || (color_mask & 0xF000);
const bool req_color = texture_function_color && (!PRIM->ABE || (PRIM->ABE && IsUsingCsInBlend())) && (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF)) || need_aem_color;
const bool req_color = (texture_function_color && (!PRIM->ABE || (PRIM->ABE && IsUsingCsInBlend())) && (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0x00FFFFFF)) != (fm_mask & 0x00FFFFFF))) || need_aem_color;
const bool alpha_used = (GSUtil::GetChannelMask(m_context->TEX0.PSM) == 0x8 || (m_context->TEX0.TCC && texture_function_alpha)) && ((PRIM->ABE && IsUsingAsInBlend()) || (m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > ATST_ALWAYS) || (possible_shuffle || (m_cached_ctx.FRAME.FBMSK & (fm_mask & 0xFF000000)) != (fm_mask & 0xFF000000)));
const bool req_alpha = (GSUtil::GetChannelMask(m_context->TEX0.PSM) & 0x8) && alpha_used;

Expand Down Expand Up @@ -2612,8 +2612,14 @@ void GSRendererHW::Draw()
}
}

// Urban Reign trolls by scissoring a draw to a target at 0x0-0x117F to 378x449 which ends up the size being rounded up to 640x480
// causing the buffer to expand to around 0x1400, which makes a later framebuffer at 0x1180 to fail to be created correctly.
// We can cheese this by checking if the Z is masked and the resultant colour is going to be black anyway.
const bool output_black = PRIM->ABE && ((m_context->ALPHA.A == 1 && m_context->ALPHA.B == 0 && GetAlphaMinMax().min >= 128) || m_context->ALPHA.IsBlack()) && m_draw_env->COLCLAMP.CLAMP == 1;
const bool can_expand = !(m_cached_ctx.ZBUF.ZMSK && output_black);

// Estimate size based on the scissor rectangle and height cache.
const GSVector2i t_size = GetTargetSize(src);
const GSVector2i t_size = GetTargetSize(src, can_expand);
const GSVector4i t_size_rect = GSVector4i::loadh(t_size);

// Ensure draw rect is clamped to framebuffer size. Necessary for updating valid area.
Expand Down Expand Up @@ -3404,20 +3410,18 @@ void GSRendererHW::Draw()

std::string s;

if (GSConfig.SaveRT && s_n >= GSConfig.SaveN)
if (rt && GSConfig.SaveRT && s_n >= GSConfig.SaveN)
{
s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), psm_str(m_cached_ctx.FRAME.PSM));
s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_(%05x)_%s.bmp", s_n, frame, m_cached_ctx.FRAME.Block(), rt->m_TEX0.TBP0, psm_str(m_cached_ctx.FRAME.PSM));

if (rt)
rt->m_texture->Save(s);
rt->m_texture->Save(s);
}

if (GSConfig.SaveDepth && s_n >= GSConfig.SaveN)
if (ds && GSConfig.SaveDepth && s_n >= GSConfig.SaveN)
{
s = GetDrawDumpPath("%05d_f%lld_rz1_%05x_%s.bmp", s_n, frame, m_cached_ctx.ZBUF.Block(), psm_str(m_cached_ctx.ZBUF.PSM));
s = GetDrawDumpPath("%05d_f%lld_rz1_%05x_(%05x)_%s.bmp", s_n, frame, m_cached_ctx.ZBUF.Block(), ds->m_TEX0.TBP0, psm_str(m_cached_ctx.ZBUF.PSM));

if (ds)
ds->m_texture->Save(s);
ds->m_texture->Save(s);
}

if (GSConfig.SaveL > 0 && (s_n - GSConfig.SaveN) > GSConfig.SaveL)
Expand All @@ -3429,6 +3433,8 @@ void GSRendererHW::Draw()
if (rt)
rt->m_last_draw = s_n;

if (ds)
ds->m_last_draw = s_n;
#ifdef DISABLE_HW_TEXTURE_CACHE
if (rt)
g_texture_cache->Read(rt, real_rect);
Expand Down Expand Up @@ -7407,9 +7413,10 @@ ClearType GSRendererHW::IsConstantDirectWriteMemClear()
&& !(m_draw_env->SCANMSK.MSK & 2) && !m_cached_ctx.TEST.ATE // no alpha test
&& !m_cached_ctx.TEST.DATE // no destination alpha test
&& (!m_cached_ctx.TEST.ZTE || m_cached_ctx.TEST.ZTST == ZTST_ALWAYS) // no depth test
&& (m_vt.m_eq.rgba == 0xFFFF || m_vertex.next == 2)) // constant color write
&& (m_vt.m_eq.rgba == 0xFFFF || m_vertex.next == 2) // constant color write
&& (!PRIM->FGE || m_vt.m_min.p.w == 255.0f)) // No fog effect
{
if (PRIM->ABE && (!m_context->ALPHA.IsOpaque() || m_cached_ctx.FRAME.FBMSK))
if ((PRIM->ABE && !m_context->ALPHA.IsOpaque()) || (m_cached_ctx.FRAME.FBMSK & GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk))
return ClearWithDraw;

return NormalClear;
Expand Down
2 changes: 1 addition & 1 deletion pcsx2/GS/Renderers/HW/GSRendererHW.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class GSRendererHW : public GSRenderer
void MergeSprite(GSTextureCache::Source* tex);
float GetTextureScaleFactor() override;
GSVector2i GetValidSize(const GSTextureCache::Source* tex = nullptr);
GSVector2i GetTargetSize(const GSTextureCache::Source* tex = nullptr);
GSVector2i GetTargetSize(const GSTextureCache::Source* tex = nullptr, const bool can_expand = true);

void Reset(bool hardware_reset) override;
void UpdateSettings(const Pcsx2Config::GSOptions& old_config) override;
Expand Down
108 changes: 73 additions & 35 deletions pcsx2/GS/Renderers/HW/GSTextureCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2569,7 +2569,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
{
const GSVector4i save_rect = preserve_target ? newrect : eerect;

if(!hw_clear)
if (!hw_clear)
dst->UpdateValidity(save_rect);
GL_INS("Preloading the RT DATA from updated GS Memory");
AddDirtyRectTarget(dst, save_rect, TEX0.PSM, TEX0.TBW, rgba, GSLocalMemory::m_psm[TEX0.PSM].trbpp >= 16);
Expand Down Expand Up @@ -2605,12 +2605,14 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
auto j = i;
Target* t = *j;

if (dst != t && t->m_TEX0.TBW == dst->m_TEX0.TBW && t->m_TEX0.PSM == dst->m_TEX0.PSM && t->m_TEX0.TBW > 4)
if (dst != t && t->m_TEX0.PSM == dst->m_TEX0.PSM/* && t->m_TEX0.TBW == dst->m_TEX0.TBW*/)
if (t->Overlaps(dst->m_TEX0.TBP0, dst->m_TEX0.TBW, dst->m_TEX0.PSM, dst->m_valid))
{
const u32 buffer_width = std::max(1U, dst->m_TEX0.TBW);

// If the two targets are misaligned, it's likely a relocation, so we can just kill the old target.
// Kill targets that are overlapping new targets, but ignore the copy if the old target is dirty because we favour GS memory.
if (((((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % dst->m_TEX0.TBW) != 0) && !t->m_dirty.empty())
if (((((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % buffer_width) != 0) && !t->m_dirty.empty())
{
InvalidateSourcesFromTarget(t);
i = list.erase(j);
Expand All @@ -2629,64 +2631,84 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
return hw_clear.value_or(false);
}
// The new texture is behind it but engulfs the whole thing, shrink the new target so it grows in the HW Draw resize.
else if (dst->m_TEX0.TBP0 < t->m_TEX0.TBP0 && (dst->UnwrappedEndBlock() + 1) > t->m_TEX0.TBP0 && dst->m_TEX0.TBP0 < (t->UnwrappedEndBlock() + 1))
else if (dst->m_TEX0.TBP0 < t->m_TEX0.TBP0 && (dst->UnwrappedEndBlock() + 1) > t->m_TEX0.TBP0)
{
const int rt_pages = ((t->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5;
const int overlapping_pages = std::min(rt_pages, static_cast<int>((dst->UnwrappedEndBlock() + 1) - t->m_TEX0.TBP0) >> 5);
const int overlapping_pages_height = (overlapping_pages / dst->m_TEX0.TBW) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
const int overlapping_pages_height = ((overlapping_pages + (buffer_width - 1)) / buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;

if (overlapping_pages_height == 0 || (overlapping_pages % dst->m_TEX0.TBW))
if (overlapping_pages_height == 0 || (overlapping_pages % buffer_width))
{
// No overlap top copy or the widths don't match.
i++;
continue;
}

const int dst_offset_width = (((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % dst->m_TEX0.TBW) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.x;
const int dst_offset_height = ((((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) / dst->m_TEX0.TBW) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y);
const int dst_offset_height = ((((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) / buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y);
const int texture_height = (dst->m_TEX0.TBW == t->m_TEX0.TBW) ? (dst_offset_height + t->m_valid.w) : (dst_offset_height + overlapping_pages_height);

if (texture_height > dst->m_unscaled_size.y && !dst->ResizeTexture(dst->m_unscaled_size.x, texture_height, true))
{
// Resize failed, probably ran out of VRAM, better luck next time. Fall back to CPU.
DevCon.Warning("Failed to resize target on preload? Draw %d", GSState::s_n);
i++;
continue;
}

const int dst_offset_width = (((t->m_TEX0.TBP0 - dst->m_TEX0.TBP0) >> 5) % buffer_width) * GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.x;
const int dst_offset_scaled_width = dst_offset_width * dst->m_scale;
const int dst_offset_scaled_height = dst_offset_height * dst->m_scale;
const GSVector4i dst_rect_scale = GSVector4i(t->m_valid.x, dst_offset_height, t->m_valid.z, dst_offset_height + overlapping_pages_height);
const GSVector4i dst_rect_scale = GSVector4i(t->m_valid.x, dst_offset_height, t->m_valid.z, texture_height);

if (((!hw_clear && (preserve_target || preload)) || dst_rect_scale.rintersect(draw_rect).rempty()) && dst->GetScale() == t->GetScale())
{
const int copy_width = ((t->m_texture->GetWidth()) > (dst->m_texture->GetWidth()) ? (dst->m_texture->GetWidth()) : t->m_texture->GetWidth()) - dst_offset_scaled_width;
const int copy_height = overlapping_pages_height * t->m_scale;
int copy_width = ((t->m_texture->GetWidth()) > (dst->m_texture->GetWidth()) ? (dst->m_texture->GetWidth()) : t->m_texture->GetWidth()) - dst_offset_scaled_width;
int copy_height = (texture_height - dst_offset_height) * t->m_scale;

GL_INS("RT double buffer copy from FBP 0x%x, %dx%d => %d,%d", t->m_TEX0.TBP0, copy_width, copy_height, 0, dst_offset_scaled_height);

// Clear the dirty first
t->Update();
dst->Update();

// Clamp it if it gets too small, shouldn't happen but stranger things have happened.
if (copy_width < 0)
{
copy_width = 0;
}

// Invalidate has been moved to after DrawPrims(), because we might kill the current sources' backing.
if (!t->m_valid_rgb || !(t->m_valid_alpha_high || t->m_valid_alpha_low) || t->m_scale != dst->m_scale)
{
const GSVector4 src_rect = GSVector4(0, 0, copy_width, copy_height) / (GSVector4(t->m_texture->GetSize()).xyxy());
const GSVector4 dst_rect = GSVector4(dst_offset_scaled_width, dst_offset_scaled_height, dst_offset_scaled_width + copy_width, dst_offset_scaled_width + copy_height);
const GSVector4 dst_rect = GSVector4(dst_offset_scaled_width, dst_offset_scaled_height, dst_offset_scaled_width + copy_width, dst_offset_scaled_height + copy_height);
g_gs_device->StretchRect(t->m_texture, src_rect, dst->m_texture, dst_rect, t->m_valid_rgb, t->m_valid_rgb, t->m_valid_rgb, t->m_valid_alpha_high || t->m_valid_alpha_low);
}
else
{
// Invalidate has been moved to after DrawPrims(), because we might kill the current sources' backing.
if ((copy_width + dst_offset_scaled_width) > (dst->m_unscaled_size.x * dst->m_scale) || (copy_height + dst_offset_scaled_height) > (dst->m_unscaled_size.y * dst->m_scale))
{
copy_width = std::min(copy_width, static_cast<int>((dst->m_unscaled_size.x * dst->m_scale) - dst_offset_scaled_width));
copy_height = std::min(copy_height, static_cast<int>((dst->m_unscaled_size.y * dst->m_scale) - dst_offset_scaled_height));
}

g_gs_device->CopyRect(t->m_texture, dst->m_texture, GSVector4i(0, 0, copy_width, copy_height), dst_offset_scaled_width, dst_offset_scaled_height);
}
}

if ((overlapping_pages < rt_pages) || (src && src->m_target && src->m_from_target == t))
{
// This should never happen as we're making a new target so the src should never be something it overlaps, but just incase..
GSVector4i new_valid = t->m_valid;
new_valid.y = std::max(new_valid.y - overlapping_pages_height, 0);
new_valid.w = std::max(new_valid.w - overlapping_pages_height, 0);
t->m_TEX0.TBP0 += (overlapping_pages_height / GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y) << 5;
t->ResizeValidity(new_valid);
}
else
// src is using this target, so point it at the new copy.
if (src && src->m_target && src->m_from_target == t)
{
InvalidateSourcesFromTarget(t);
i = list.erase(j);
delete t;
src->m_from_target = dst;
src->m_texture = dst->m_texture;
src->m_region.SetY(src->m_region.GetMinY() + dst_offset_height, src->m_region.GetMaxY() + dst_offset_height);
src->m_region.SetX(src->m_region.GetMinX() + dst_offset_width, src->m_region.GetMaxX() + dst_offset_width);
}
return hw_clear.value_or(false);

InvalidateSourcesFromTarget(t);
i = list.erase(j);
delete t;
continue;
}
}
i++;
Expand Down Expand Up @@ -3736,6 +3758,19 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
if (alpha_only && (!dst || GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp != 32))
return false;

// Beware of the case where a game might create a larger texture by moving a bunch of chunks around.
if (dst && DBP == SBP && dy > dst->m_unscaled_size.y)
{
const u32 new_DBP = DBP + (((dy / GSLocalMemory::m_psm[dst->m_TEX0.PSM].pgs.y) * DBW) << 5);

dst = nullptr;

DBP = new_DBP;
dy = 0;

dst = GetExactTarget(DBP, DBW, dpsm_s.depth ? DepthStencil : RenderTarget, DBP);
}

// Beware of the case where a game might create a larger texture by moving a bunch of chunks around.
// We use dx/dy == 0 and the TBW check as a safeguard to make sure these go through to local memory.
// We can also recreate the target if it's previously been created in the height cache with a valid size.
Expand Down Expand Up @@ -4182,7 +4217,7 @@ GSTextureCache::Target* GSTextureCache::FindOverlappingTarget(u32 BP, u32 BW, u3
return FindOverlappingTarget(BP, end_bp);
}

GSVector2i GSTextureCache::GetTargetSize(u32 bp, u32 fbw, u32 psm, s32 min_width, s32 min_height)
GSVector2i GSTextureCache::GetTargetSize(u32 bp, u32 fbw, u32 psm, s32 min_width, s32 min_height, bool can_expand)
{
TargetHeightElem search = {};
search.bp = bp;
Expand All @@ -4196,22 +4231,25 @@ GSVector2i GSTextureCache::GetTargetSize(u32 bp, u32 fbw, u32 psm, s32 min_width
TargetHeightElem& elem = const_cast<TargetHeightElem&>(*it);
if (elem.bits == search.bits)
{
if (elem.width < min_width || elem.height < min_height)
if (can_expand)
{
DbgCon.WriteLn("Expand size at %x %u %u from %ux%u to %ux%u", bp, fbw, psm, elem.width, elem.height,
min_width, min_height);
}
if (elem.width < min_width || elem.height < min_height)
{
DbgCon.WriteLn("Expand size at %x %u %u from %ux%u to %ux%u", bp, fbw, psm, elem.width, elem.height,
min_width, min_height);
}

elem.width = std::max(elem.width, min_width);
elem.height = std::max(elem.height, min_height);
elem.width = std::max(elem.width, min_width);
elem.height = std::max(elem.height, min_height);
}

m_target_heights.MoveFront(it.Index());
elem.age = 0;
return GSVector2i(elem.width, elem.height);
}
}

DbgCon.WriteLn("New size at %x %u %u: %ux%u", bp, fbw, psm, min_width, min_height);
DbgCon.WriteLn("New size at %x %u %u: %ux%u draw %d", bp, fbw, psm, min_width, min_height, GSState::s_n);
m_target_heights.push_front(search);
return GSVector2i(min_width, min_height);
}
Expand Down
2 changes: 1 addition & 1 deletion pcsx2/GS/Renderers/HW/GSTextureCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ class GSTextureCache
Target* FindOverlappingTarget(u32 BP, u32 end_bp) const;
Target* FindOverlappingTarget(u32 BP, u32 BW, u32 PSM, GSVector4i rc) const;

GSVector2i GetTargetSize(u32 bp, u32 fbw, u32 psm, s32 min_width, s32 min_height);
GSVector2i GetTargetSize(u32 bp, u32 fbw, u32 psm, s32 min_width, s32 min_height, bool can_expand = true);
bool HasTargetInHeightCache(u32 bp, u32 fbw, u32 psm, u32 max_age = std::numeric_limits<u32>::max(), bool move_front = true);
bool Has32BitTarget(u32 bp);

Expand Down
Loading