From 78a60934516731349fab92836ced42e5be775a3d Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 28 Jul 2026 20:13:09 +0100 Subject: [PATCH 1/4] GS/HW: Consider FBMSK combos when using CSBW --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 8 ++++++-- pcsx2/GS/Renderers/HW/GSRendererHWMultiISA.cpp | 14 +++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 98483f2b8d034..f0d4de0208545 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -9826,8 +9826,12 @@ bool GSRendererHW::CanUseSwPrimRender(bool no_rt, bool no_ds, bool draw_sprite_t return false; } } - - if (PRIM->ABE && m_vt.m_eq.rgba == 0xffff && !m_context->ALPHA.IsOpaque(GetAlphaMinMax().min, GetAlphaMinMax().max)) + const u32 frame_mask = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk; + const u32 alpha_mask = ((m_cached_ctx.FRAME.FBMSK & frame_mask) & 0xFF000000); + const u32 rgb_mask = ((m_cached_ctx.FRAME.FBMSK & frame_mask) & 0x00FFFFFF); + const bool difficult_fbmsk = m_cached_ctx.FRAME.FBMSK != 0 && (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 || (alpha_mask != 0 && alpha_mask != (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk & 0xFF000000)) || + (rgb_mask != 0 && rgb_mask != (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk & 0x00FFFFFF))); + if (difficult_fbmsk || (PRIM->ABE && m_vt.m_eq.rgba == 0xffff && !m_context->ALPHA.IsOpaque(GetAlphaMinMax().min, GetAlphaMinMax().max))) { GSTextureCache::Target* rt = g_texture_cache->GetTargetWithSharedBits(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.PSM); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHWMultiISA.cpp b/pcsx2/GS/Renderers/HW/GSRendererHWMultiISA.cpp index 5afd08eeac8d5..b4e943eb46f6b 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHWMultiISA.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHWMultiISA.cpp @@ -545,7 +545,19 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b static_cast(hw.m_sw_rasterizer.get())->Draw(data); if (invalidate_tc) - g_texture_cache->InvalidateVideoMem(context->offset.fb, bbox); + { + GSOffset frame_offs = context->offset.fb; + + if (GSLocalMemory::m_psm[context->FRAME.PSM].trbpp == 32 && context->FRAME.FBMSK) + { + if (context->FRAME.FBMSK == 0xFF000000) + frame_offs = GSRendererHW::GetInstance()->m_mem.GetOffset(context->FRAME.Block(), context->FRAME.FBW, PSMCT24); + else if (context->FRAME.FBMSK == 0x00FFFFFF) + frame_offs = GSRendererHW::GetInstance()->m_mem.GetOffset(context->FRAME.Block(), context->FRAME.FBW, PSMT8H); + } + + g_texture_cache->InvalidateVideoMem(frame_offs, bbox); + } // Jak does sw prim render, then draws to the same target, and it needs to be uploaded. if (add_ee_transfer) From 6cd31245ac114439acc0ceb9897c426ce316a7b8 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 28 Jul 2026 20:17:35 +0100 Subject: [PATCH 2/4] GS/TC: Detect alpha channel source width mismatches --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 440e2794c2df7..2a7af6d779012 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -1734,6 +1734,11 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const if (found_t && (bw != t->m_TEX0.TBW || t->m_TEX0.PSM != psm)) match = false; + // Situations where font has been uploaded to the alpha channel and they use a different buffer width, this can't currently be translated correctly. + GSLocalMemory::psm_t req_psm = GSLocalMemory::m_psm[psm]; + if (!possible_shuffle && req_psm.trbpp <= 8 && req_psm.bpp == 32 && t->m_TEX0.TBW != bw && (bw * 64) != t->m_valid.z && r.w > req_psm.pgs.y) + match = false; + // Different swizzle, different width, and dirty, so probably not what we want. // DevCon.Warning("Expected %x Got %x shuffle %d draw %d", psm, t_psm, possible_shuffle, GSState::s_n); if (match) @@ -7282,7 +7287,7 @@ GSTexture* GSTextureCache::LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVec else if (GSConfig.UserHacks_GPUTargetCLUTMode == GSGPUTargetCLUTMode::InsideTarget && t->m_TEX0.TBP0 < CBP && t->m_end_block >= CBP) { - // Somewhere within this target, can we find it? + // If it's more than one group of 4 blocks, it's probably going to fail, horribly. const GSVector4i rc(0, 0, size.x, size.y); SurfaceOffset so = ComputeSurfaceOffset(CBP, std::max(CBW, 0), CPSM, rc, t); if (!so.is_valid) @@ -7294,7 +7299,7 @@ GSTexture* GSTextureCache::LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVec } else { - // Not inside this target, skip. + // Not inside this target or not aligned, skip. continue; } From aec599e043187b45d5d459a149a8c2b06c1fbd37 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 28 Jul 2026 20:18:19 +0100 Subject: [PATCH 3/4] GS/TC: Disallow badly misaligned GPU CLUT textures --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 2a7af6d779012..83d90cef8aa14 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -7288,6 +7288,9 @@ GSTexture* GSTextureCache::LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVec t->m_TEX0.TBP0 < CBP && t->m_end_block >= CBP) { // If it's more than one group of 4 blocks, it's probably going to fail, horribly. + if ((size.x > GSLocalMemory::m_psm[CPSM].bs.x || size.y > GSLocalMemory::m_psm[CPSM].bs.y) && (CBP & 0x3) != (t->m_TEX0.TBP0 & 0x3)) + continue; + // Somewhere within this target, can we find it? const GSVector4i rc(0, 0, size.x, size.y); SurfaceOffset so = ComputeSurfaceOffset(CBP, std::max(CBW, 0), CPSM, rc, t); if (!so.is_valid) From 9474a25cb9983b9c607581f9326a9c638c994a70 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Tue, 28 Jul 2026 20:19:18 +0100 Subject: [PATCH 4/4] GS/TC: Update alpha channel of 24bit target is alpha is dirty --- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 83d90cef8aa14..fcc597dde90eb 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -7970,7 +7970,9 @@ void GSTextureCache::Target::Update(bool cannot_scale) alloca(sizeof(GSDevice::MultiStretchRect) * static_cast(m_dirty.size()))); u32 ndrects = 0; - const GSOffset off(g_gs_renderer->m_mem.GetOffset(m_TEX0.TBP0, m_TEX0.TBW, m_TEX0.PSM)); + // There are cases where the alpha gets populated with font data, and the target has previously been PSMCT32 (or will be) so we need to make sure it gets filled in. + const u32 psm = (m_TEX0.PSM == PSMCT24) ? PSMCT32 : m_TEX0.PSM; + const GSOffset off(g_gs_renderer->m_mem.GetOffset(m_TEX0.TBP0, m_TEX0.TBW, psm)); const u32 bpp = GSLocalMemory::m_psm[m_TEX0.PSM].bpp; std::pair alpha_minmax = {255, 0}; @@ -7993,7 +7995,7 @@ void GSTextureCache::Target::Update(bool cannot_scale) const GSVector4i t_r(read_r - t_offset); if (mapped) { - if ((m_TEX0.PSM & 0xf) != PSMCT24 && m_dirty[i].rgba.c.a && bpp >= 16) + if (((m_TEX0.PSM & 0xf) != PSMCT24 || m_valid_alpha_high || m_valid_alpha_low) && m_dirty[i].rgba.c.a && bpp >= 16) { // TODO: Only read once in 32bit and copy to the mapped texture. Bit out of scope of this PR and not a huge impact. const int pitch = VectorAlign(read_r.width() * sizeof(u32)); @@ -8012,7 +8014,7 @@ void GSTextureCache::Target::Update(bool cannot_scale) const int pitch = VectorAlign(read_r.width() * sizeof(u32)); g_gs_renderer->m_mem.ReadTexture(off, read_r, s_unswizzle_buffer, pitch, TEXA); - if ((m_TEX0.PSM & 0xf) != PSMCT24 && m_dirty[i].rgba.c.a && bpp >= 16) + if (((m_TEX0.PSM & 0xf) != PSMCT24 || m_valid_alpha_high || m_valid_alpha_low) &&m_dirty[i].rgba.c.a && bpp >= 16) { std::pair new_alpha_minmax = GSGetRGBA8AlphaMinMax(s_unswizzle_buffer, read_r.width(), read_r.height(), pitch); alpha_minmax.first = std::min(alpha_minmax.first, new_alpha_minmax.first);