From de8b23db4e5b09acc133740f17aafb7f5701c7b8 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Fri, 22 Jul 2022 02:35:08 +0100 Subject: [PATCH] GS-SW: Fix framebuffer looping at 2048 height. Also limit height read on hardware to 2048 --- pcsx2/GS/GSState.cpp | 21 +++++++---- pcsx2/GS/GSState.h | 2 +- pcsx2/GS/Renderers/Common/GSRenderer.cpp | 29 ++++++++------ pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 2 +- pcsx2/GS/Renderers/SW/GSRendererSW.cpp | 48 ++++++++++++++++++++++-- 5 files changed, 76 insertions(+), 26 deletions(-) diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index d41b0f168b..5830e668d3 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -507,11 +507,11 @@ GSVector2i GSState::GetResolution() return resolution; } -GSVector4i GSState::GetFrameRect(int i) +GSVector4i GSState::GetFrameRect(int i, bool ignore_off) { // If no specific context is requested then pass the merged rectangle as return value if (i == -1) - return GetFrameRect(0).runion(GetFrameRect(1)); + return GetFrameRect(0, ignore_off).runion(GetFrameRect(1, ignore_off)); GSVector4i rectangle = { 0, 0, 0, 0 }; @@ -525,13 +525,19 @@ GSVector4i GSState::GetFrameRect(int i) const GSVector2i magnification(DISP.MAGH+1, DISP.MAGV + 1); const u32 DBX = m_regs->DISP[i].DISPFB.DBX; - const u32 DBY = m_regs->DISP[i].DISPFB.DBY; + int DBY = m_regs->DISP[i].DISPFB.DBY; + int w = DW / magnification.x; int h = DH / magnification.y; - rectangle.left = DBX; - rectangle.top = DBY; + // If the combined height overflows 2048, it's likely adding a bit of extra data before the picture for offsetting the interlace + // only game known to do this is NASCAR '08 + if (!ignore_off && (DBY + h) >= 2048) + DBY = DBY - 2048; + + rectangle.left = (ignore_off) ? 0 : DBX; + rectangle.top = (ignore_off) ? 0 : DBY; rectangle.right = rectangle.left + w; rectangle.bottom = rectangle.top + h; @@ -550,9 +556,8 @@ int GSState::GetFramebufferHeight() // Framebuffer height is 11 bits max constexpr int height_limit = (1 << 11); - const GSVector4i disp1_rect = GetFrameRect(0); - const GSVector4i disp2_rect = GetFrameRect(1); - + const GSVector4i disp1_rect = GetFrameRect(0, true); + const GSVector4i disp2_rect = GetFrameRect(1, true); const GSVector4i combined = disp1_rect.runion(disp2_rect); // DBY isn't an offset to the frame memory but rather an offset to read output circuit inside diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index f32c86e680..773b1f39c2 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -327,7 +327,7 @@ public: GSVector4i GetFrameMagnifiedRect(int i = -1); GSVector2i GetResolutionOffset(int i = -1); GSVector2i GetResolution(); - GSVector4i GetFrameRect(int i = -1); + GSVector4i GetFrameRect(int i = -1, bool ignore_off = false); GSVideoMode GetVideoMode(); bool IsEnabled(int i); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index bf94813acd..971a842e3f 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -112,8 +112,8 @@ bool GSRenderer::Merge(int field) display_baseline.x = std::min(display_offsets[i].x, display_baseline.x); display_baseline.y = std::min(display_offsets[i].y, display_baseline.y); - frame_baseline.x = std::min(fr[i].left, frame_baseline.x); - frame_baseline.y = std::min(fr[i].top, frame_baseline.y); + frame_baseline.x = std::min(std::max(fr[i].left, 0), frame_baseline.x); + frame_baseline.y = std::min(std::max(fr[i].top, 0), frame_baseline.y); display_offset |= std::abs(display_baseline.y - display_offsets[i].y) == 1; /*DevCon.Warning("Read offset was X %d(left %d) Y %d(top %d)", display_baseline.x, dr[i].left, display_baseline.y, dr[i].top); @@ -147,7 +147,7 @@ bool GSRenderer::Merge(int field) s_n++; - if (samesrc && fr[0].bottom == fr[1].bottom && !feedback_merge) + if (samesrc && fr[0].bottom == fr[1].bottom && !feedback_merge && fr[0].right == fr[1].right) { tex[0] = GetOutput(0, y_offset[0]); tex[1] = tex[0]; // saves one texture fetch @@ -200,6 +200,12 @@ bool GSRenderer::Merge(int field) GSVector2i display_diff(display_offsets[i].x - display_baseline.x, display_offsets[i].y - display_baseline.y); GSVector2i frame_diff(fr[i].left - frame_baseline.x, fr[i].top - frame_baseline.y); + // Clear any frame offsets, we don't need them now. + fr[i].right -= fr[i].left; + fr[i].left = 0; + fr[i].bottom -= fr[i].top; + fr[i].top = 0; + // If using scanmsk we have to keep the single line offset, regardless of upscale // so we handle this separately after the rect calculations. float interlace_offset = 0.0f; @@ -234,10 +240,10 @@ bool GSRenderer::Merge(int field) off.y -= display_diff.y; // Offset by DISPFB setting - if (frame_diff.x == 1) - off.x += 1; - if (frame_diff.y == 1) - off.y += 1; + if (abs(frame_diff.x) < 4) + off.x += frame_diff.x; + if (abs(frame_diff.y) < 4) + off.y += frame_diff.y; } } } @@ -286,11 +292,10 @@ bool GSRenderer::Merge(int field) if (GSConfig.PCRTCAntiBlur) { // Offset by DISPFB setting - if (frame_diff.x == 1) - off.x += 1; - - if (frame_diff.y == 1) - off.y += 1; + if (abs(frame_diff.x) < 4) + off.x += frame_diff.x; + if (abs(frame_diff.y) < 4) + off.y += frame_diff.y; } } } diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index 48e2250e56..c76b27fced 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -275,7 +275,7 @@ GSTexture* GSRendererHW::GetOutput(int i, int& y_offset) const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; const int display_height = offsets.y * ((isinterlaced() && !m_regs->SMODE2.FFMD) ? 2 : 1); const int display_offset = GetResolutionOffset(i).y; - int fb_height = std::min(GetFramebufferHeight(), display_height) + DISPFB.DBY; + int fb_height = std::min(std::min(GetFramebufferHeight(), display_height) + (int)DISPFB.DBY, 2048); // If there is a negative vertical offset on the picture, we need to read more. if (display_offset < 0) diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp index 3ba63c5708..a8bc4255f1 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp @@ -35,6 +35,7 @@ GSRendererSW::GSRendererSW(int threads) m_tc = std::make_unique(); m_rl = GSRasterizerList::Create(threads); + // Max framebuffer size can be 1024x2048 (it will loop at 2048). m_output = (u8*)_aligned_malloc(1024 * 1024 * sizeof(u32), 32); std::fill(std::begin(m_fzb_pages), std::end(m_fzb_pages), 0); @@ -132,7 +133,7 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset) const int display_offset = GetResolutionOffset(i).y; const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; const int display_height = offsets.y * ((isinterlaced() && !m_regs->SMODE2.FFMD) ? 2 : 1); - int h = std::min(GetFramebufferHeight(), display_height) + DISPFB.DBY; + int h = std::min(GetFramebufferHeight(), display_height); // If there is a negative vertical offset on the picture, we need to read more. if (display_offset < 0) @@ -142,15 +143,54 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset) if (g_gs_device->ResizeTarget(&m_texture[i], w, h)) { - static int pitch = 1024 * 4; + constexpr int pitch = 1024 * 4; + const int off_x = DISPFB.DBX & 0x7ff; + const int off_y = DISPFB.DBY & 0x7ff; + bool part2_h = false; + bool part2_w = false; + GSVector4i r(off_x, off_y, w + off_x, h + off_y); + GSVector4i rh(off_x, off_y, w + off_x, (h + off_y) & 0x7FF); + GSVector4i rw(off_x, off_y, (w + off_x) & 0x7FF, h + off_y); + GSVector4i out_r(0, 0, w, h); - GSVector4i r(0, 0, w, h); + // Need to read it in 2 parts, since you can't do a split rect. + if (r.bottom >= 2048) + { + r.bottom = 2048; + rw.bottom = 2048; + part2_h = true; + rh.top = 0; + } + if (r.right >= 2048) + { + r.right = 2048; + rh.right = 2048; + part2_w = true; + rw.left = 0; + + } const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[DISPFB.PSM]; (m_mem.*psm.rtx)(m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), r.ralign(psm.bs), m_output, pitch, m_env.TEXA); - m_texture[i]->Update(r, m_output, pitch); + int top = (part2_h) ? ((r.bottom - r.top) * pitch) : 0; + int left = (part2_w) ? (r.right - r.left) * (GSLocalMemory::m_psm[DISPFB.PSM].bpp / 8) : 0; + + if (part2_w) + (m_mem.*psm.rtx)(m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rw.ralign(psm.bs), &m_output[left], pitch, m_env.TEXA); + + if (part2_h) + (m_mem.*psm.rtx)(m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rh.ralign(psm.bs), &m_output[top], pitch, m_env.TEXA); + + if (part2_h && part2_w) + { + // needs also rw with the start/end height of rh, fills in the bottom right rect which will be missing if both overflow. + const GSVector4i rwh(rw.left, rh.top, rw.right, rh.bottom); + (m_mem.*psm.rtx)(m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rwh.ralign(psm.bs), &m_output[top + left], pitch, m_env.TEXA); + } + + m_texture[i]->Update(out_r, m_output, pitch); if (s_dump) {