diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 3d1d48bd91..439beb40be 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -232,6 +232,7 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem) } g_gs_renderer->SetRegsMem(basemem); + g_gs_renderer->ResetPCRTC(); } else { diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index 755da42574..9457b4511f 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -285,6 +285,14 @@ void GSState::ResetHandlers() m_fpGIFRegHandlers[GIF_A_D_REG_LABEL] = &GSState::GIFRegHandlerNull; } +void GSState::ResetPCRTC() +{ + PCRTCDisplays.SetVideoMode(GetVideoMode()); + PCRTCDisplays.EnableDisplays(m_regs->PMODE, m_regs->SMODE2, isReallyInterlaced()); + PCRTCDisplays.SetRects(0, m_regs->DISP[0].DISPLAY, m_regs->DISP[0].DISPFB); + PCRTCDisplays.SetRects(1, m_regs->DISP[1].DISPLAY, m_regs->DISP[1].DISPFB); +} + void GSState::UpdateSettings(const Pcsx2Config::GSOptions& old_config) { m_mipmap = GSConfig.Mipmap; @@ -340,264 +348,6 @@ GSVideoMode GSState::GetVideoMode() __assume(0); // unreachable } -bool GSState::IsAnalogue() -{ - return GetVideoMode() == GSVideoMode::NTSC || GetVideoMode() == GSVideoMode::PAL || GetVideoMode() == GSVideoMode::HDTV_1080I; -} - -GSVector4i GSState::GetFrameMagnifiedRect(int i) -{ - GSVector4i rectangle = { 0, 0, 0, 0 }; - - if (!IsEnabled(i)) - return rectangle; - - const int videomode = static_cast(GetVideoMode()) - 1; - const auto& DISP = m_regs->DISP[i].DISPLAY; - const bool ignore_offset = !GSConfig.PCRTCOffsets; - - const u32 DW = DISP.DW + 1; - const u32 DH = DISP.DH + 1; -; - // The number of sub pixels to draw are given in DH and DW, the MAGH/V relates to the size of the original square in the FB - // but the size it's drawn to uses the default size of the display mode (for PAL/NTSC this is a MAGH of 3) - // so for example a game will have a DW of 2559 and a MAGH of 4 to make it 512 (from the FB), but because it's drawing 2560 subpixels - // it will cover the entire 640 wide of the screen (2560 / (3+1)). - int width; - int height; - if (ignore_offset) - { - width = (DW / (DISP.MAGH + 1)); - height = (DH / (DISP.MAGV + 1)); - } - else - { - width = (DW / (VideoModeDividers[videomode].x + 1)); - height = (DH / (VideoModeDividers[videomode].y + 1)); - } - - int res_multi = 1; - - if (isinterlaced() && m_regs->SMODE2.FFMD && height > 1) - res_multi = 2; - - // Set up the display rectangle based on the values obtained from DISPLAY registers - rectangle.right = width; - rectangle.bottom = height / res_multi; - - return rectangle; -} - -int GSState::GetDisplayHMagnification() -{ - // Pick one of the DISPLAY's and hope that they are both the same. Favour DISPLAY[1] - for (int i = 1; i >= 0; i--) - { - if (IsEnabled(i)) - return m_regs->DISP[i].DISPLAY.MAGH + 1; - } - - // If neither DISPLAY is enabled, fallback to resolution offset (should never happen) - const int videomode = static_cast(GetVideoMode()) - 1; - return VideoModeDividers[videomode].x + 1; -} - -GSVector4i GSState::GetDisplayRect(int i) -{ - GSVector4i rectangle = { 0, 0, 0, 0 }; - - if (i == -1) - { - return GetDisplayRect(0).runion(GetDisplayRect(1)); - } - - if (!IsEnabled(i)) - return rectangle; - - const auto& DISP = m_regs->DISP[i].DISPLAY; - - const u32 DW = DISP.DW + 1; - const u32 DH = DISP.DH + 1; - const u32 MAGH = DISP.MAGH + 1; - const u32 MAGV = DISP.MAGV + 1; - - const GSVector2i magnification(MAGH, MAGV); - - const int width = DW / magnification.x; - const int height = DH / magnification.y; - - const GSVector2i offsets = GetResolutionOffset(i); - - // Set up the display rectangle based on the values obtained from DISPLAY registers - rectangle.left = offsets.x; - rectangle.top = offsets.y; - - rectangle.right = rectangle.left + width; - rectangle.bottom = rectangle.top + height; - - return rectangle; -} - -GSVector2i GSState::GetResolutionOffset(int i) -{ - GSVector2i offset = { 0, 0 }; - - if (!IsEnabled(i)) - return offset; - - const int videomode = static_cast(GetVideoMode()) - 1; - const auto& DISP = m_regs->DISP[i].DISPLAY; - - const auto& SMODE2 = m_regs->SMODE2; - const int res_multi = (SMODE2.INT + 1); - const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; - - offset.x = (static_cast(DISP.DX) - offsets.z) / (VideoModeDividers[videomode].x + 1); - offset.y = (static_cast(DISP.DY) - (offsets.w * ((IsAnalogue() && res_multi) ? res_multi : 1))) / (VideoModeDividers[videomode].y + 1); - - return offset; -} - -GSVector2i GSState::GetResolution() -{ - const int videomode = static_cast(GetVideoMode()) - 1; - const bool ignore_offset = !GSConfig.PCRTCOffsets; - - const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; - - GSVector2i resolution(offsets.x, offsets.y); - - // The resolution of the framebuffer is double when in FRAME mode and interlaced. - // Also we need a special check because no-interlace patches like to render in the original height, but in non-interlaced mode - // which means it would normally go off the bottom of the screen. Advantages of emulation, i guess... Limited to Ignore Offsets + Deinterlacing = Off. - if ((isinterlaced() && !m_regs->SMODE2.FFMD) || (GSConfig.InterlaceMode == GSInterlaceMode::Off && !GSConfig.PCRTCOffsets && !isinterlaced())) - resolution.y *= 2; - - if (ignore_offset) - { - // Ideally we'd just cut the width at the resolution, but of course we have to hack the hack... - // Some games (Mortal Kombat Armageddon) render the image at 834 pixels then shrink it to 624 pixels - // which does fit, but when we ignore offsets we go on framebuffer size and some other games - // such as Johnny Mosleys Mad Trix and Transformers render too much but design it to go off the screen. - int magnified_width = (VideoModeDividers[videomode].z + 1) / GetDisplayHMagnification(); - - // When viewing overscan allow up to the overscan size - if (GSConfig.PCRTCOverscan) - magnified_width = std::max(magnified_width, offsets.x); - - GSVector4i total_rect = GetDisplayRect(0).runion(GetDisplayRect(1)); - total_rect.z = total_rect.z - total_rect.x; - total_rect.w = total_rect.w - total_rect.y; - total_rect.z = std::min(total_rect.z, magnified_width); - total_rect.w = std::min(total_rect.w, resolution.y); - resolution.x = total_rect.z; - resolution.y = total_rect.w; - } - - return resolution; -} - -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, ignore_off).runion(GetFrameRect(1, ignore_off)); - - GSVector4i rectangle = { 0, 0, 0, 0 }; - - if (!IsEnabled(i)) - return rectangle; - - const auto& DISP = m_regs->DISP[i].DISPLAY; - - const u32 DW = DISP.DW + 1; - const u32 DH = DISP.DH + 1; - const GSVector2i magnification(DISP.MAGH+1, DISP.MAGV + 1); - - const u32 DBX = m_regs->DISP[i].DISPFB.DBX; - int DBY = m_regs->DISP[i].DISPFB.DBY; - - - const int w = DW / magnification.x; - const int h = DH / magnification.y; - - // 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; - - if (isinterlaced() && m_regs->SMODE2.FFMD && h > 1) - { - rectangle.bottom += 1; - rectangle.bottom >>= 1; - } - - return rectangle; -} - -int GSState::GetFramebufferHeight() -{ - // Framebuffer height is 11 bits max - constexpr int height_limit = (1 << 11); - - 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 - // the frame memory, hence the top offset should also be calculated for the total height of the - // frame memory. Also we need to wrap the value only when we're dealing with values with range of the - // frame memory (offset + read output circuit height, IOW bottom of merged_output) - const int max_height = std::max(disp1_rect.height(), disp2_rect.height()); - const int frame_memory_height = std::max(max_height, combined.bottom % height_limit); - - if (frame_memory_height > 1024) - GL_PERF("Massive framebuffer height detected! (height:%d)", frame_memory_height); - - return frame_memory_height; -} - -int GSState::GetFramebufferBitDepth() -{ - if (IsEnabled(0)) - return GSLocalMemory::m_psm[m_regs->DISP[0].DISPFB.PSM].bpp; - else if (IsEnabled(1)) - return GSLocalMemory::m_psm[m_regs->DISP[1].DISPFB.PSM].bpp; - - return 32; -} - -int GSState::GetFramebufferWidth() -{ - const GSVector4i disp1_rect = GetFrameRect(0, true); - const GSVector4i disp2_rect = GetFrameRect(1, true); - - const int max_width = std::max(disp1_rect.width(), disp2_rect.width()); - - return max_width; -} - -bool GSState::IsEnabled(int i) -{ - ASSERT(i >= 0 && i < 2); - - const auto& DISP = m_regs->DISP[i].DISPLAY; - - const bool disp1_enabled = m_regs->PMODE.EN1; - const bool disp2_enabled = m_regs->PMODE.EN2; - - if ((i == 0 && disp1_enabled) || (i == 1 && disp2_enabled)) - return DISP.DW && DISP.DH; - - return false; -} - float GSState::GetTvRefreshRate() { const GSVideoMode videomode = GetVideoMode(); @@ -2775,6 +2525,8 @@ int GSState::Defrost(const freezeData* fd) g_perfmon.SetFrame(5000); + ResetPCRTC(); + return 0; } diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index dec3122d9d..7a2109149c 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -290,59 +290,507 @@ public: PRIM_OVERLAP m_prim_overlap; std::vector m_drawlist; - // The horizontal offset values (under z) for PAL and NTSC have been tweaked - // they should be apparently 632 and 652 respectively, but that causes a thick black line on the left - // these values leave a small black line on the right in a bunch of games, but it's not so bad. - // The only conclusion I can come to is there is horizontal overscan expected so there would normally - // be black borders either side anyway, or both sides slightly covered. - const GSVector4i VideoModeOffsets[6] = { - GSVector4i(640, 224, 642, 25), - GSVector4i(640, 256, 676, 36), - GSVector4i(640, 480, 276, 34), - GSVector4i(720, 480, 232, 35), - GSVector4i(1280, 720, 302, 24), - GSVector4i(1920, 540, 238, 40) - }; + struct GSPCRTCRegs + { + // The horizontal offset values (under z) for PAL and NTSC have been tweaked + // they should be apparently 632 and 652 respectively, but that causes a thick black line on the left + // these values leave a small black line on the right in a bunch of games, but it's not so bad. + // The only conclusion I can come to is there is horizontal overscan expected so there would normally + // be black borders either side anyway, or both sides slightly covered. + const GSVector4i VideoModeOffsets[6] = { + GSVector4i(640, 224, 642, 25), + GSVector4i(640, 256, 676, 36), + GSVector4i(640, 480, 276, 34), + GSVector4i(720, 480, 232, 35), + GSVector4i(1280, 720, 302, 24), + GSVector4i(1920, 540, 238, 40) + }; - const GSVector4i VideoModeOffsetsOverscan[6] = { - GSVector4i(711, 243, 498, 12), - GSVector4i(702, 288, 532, 18), - GSVector4i(640, 480, 276, 34), - GSVector4i(720, 480, 232, 35), - GSVector4i(1280, 720, 302, 24), - GSVector4i(1920, 540, 238, 40) - }; + const GSVector4i VideoModeOffsetsOverscan[6] = { + GSVector4i(711, 243, 498, 12), + GSVector4i(702, 288, 532, 18), + GSVector4i(640, 480, 276, 34), + GSVector4i(720, 480, 232, 35), + GSVector4i(1280, 720, 302, 24), + GSVector4i(1920, 540, 238, 40) + }; - const GSVector4i VideoModeDividers[6] = { - GSVector4i(3, 0, 2559, 239), - GSVector4i(3, 0, 2559, 287), - GSVector4i(1, 0, 1279, 479), - GSVector4i(1, 0, 1439, 479), - GSVector4i(0, 0, 1279, 719), - GSVector4i(0, 0, 1919, 1079) - }; + const GSVector4i VideoModeDividers[6] = { + GSVector4i(3, 0, 2559, 239), + GSVector4i(3, 0, 2559, 287), + GSVector4i(1, 0, 1279, 479), + GSVector4i(1, 0, 1439, 479), + GSVector4i(0, 0, 1279, 719), + GSVector4i(0, 0, 1919, 1079) + }; + + struct PCRTCDisplay + { + bool enabled; + int FBP; + int FBW; + int PSM; + GSVector2i prevDisplayOffset; + GSVector2i displayOffset; + GSVector4i displayRect; + GSVector2i magnification; + GSVector2i prevFramebufferOffsets; + GSVector2i framebufferOffsets; + GSVector4i framebufferRect; + + int Block() + { + return FBP << 5; + } + }; + + int videomode; + int interlaced; + int FFMD; + bool PCRTCSameSrc; + bool toggling_field; + PCRTCDisplay PCRTCDisplays[2]; + + bool IsAnalogue() + { + GSVideoMode video = static_cast(videomode + 1); + return video == GSVideoMode::NTSC || video == GSVideoMode::PAL || video == GSVideoMode::HDTV_1080I; + } + + // Calculates which display is closest to matching zero offsets in either direction. + GSVector2i NearestToZeroOffset() + { + GSVector2i returnValue = { 1, 1 }; + + if (!PCRTCDisplays[0].enabled && !PCRTCDisplays[1].enabled) + return returnValue; + + for (int i = 0; i < 2; i++) + { + if (!PCRTCDisplays[i].enabled) + { + returnValue.x = 1 - i; + returnValue.y = 1 - i; + return returnValue; + } + } + + if (abs(PCRTCDisplays[0].displayOffset.x - VideoModeOffsets[videomode].z) < + abs(PCRTCDisplays[1].displayOffset.x - VideoModeOffsets[videomode].z)) + returnValue.x = 0; + + // When interlaced, the vertical base offset is doubled + int verticalOffset = VideoModeOffsets[videomode].w * (1 << interlaced); + + if (abs(PCRTCDisplays[0].displayOffset.y - verticalOffset) < + abs(PCRTCDisplays[1].displayOffset.y - verticalOffset)) + returnValue.y = 0; + + return returnValue; + } + + void SetVideoMode(GSVideoMode videoModeIn) + { + videomode = static_cast(videoModeIn) - 1; + } + + // Enable each of the displays. + void EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle) + { + PCRTCDisplays[0].enabled = pmode.EN1; + PCRTCDisplays[1].enabled = pmode.EN2; + + interlaced = smode2.INT && IsAnalogue(); + FFMD = smode2.FFMD; + toggling_field = smodetoggle && IsAnalogue(); + } + + void CheckSameSource() + { + if (PCRTCDisplays[0].enabled != PCRTCDisplays[1].enabled || (PCRTCDisplays[0].enabled | PCRTCDisplays[1].enabled) == false) + { + PCRTCSameSrc = false; + return; + } + + PCRTCSameSrc = PCRTCDisplays[0].FBP == PCRTCDisplays[1].FBP && + PCRTCDisplays[0].FBW == PCRTCDisplays[1].FBW && + GSUtil::HasCompatibleBits(PCRTCDisplays[0].PSM, PCRTCDisplays[1].PSM); + } + + bool FrameWrap() + { + GSVector4i combined_rect = GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect)); + return combined_rect.w >= 2048 || combined_rect.z >= 2048; + } + + // If the start point of both frames match, we can do a single read + bool FrameRectMatch() + { + return PCRTCSameSrc; + } + + GSVector2i GetResolution() + { + GSVector2i resolution; + + const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; + const bool is_full_height = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off) || GSConfig.InterlaceMode == GSInterlaceMode::Off; + + if (!GSConfig.PCRTCOffsets) + { + if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled) + { + GSVector4i combined_size = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect); + resolution = { combined_size.width(), combined_size.height() }; + } + else if (PCRTCDisplays[0].enabled) + { + resolution = { PCRTCDisplays[0].displayRect.width(), PCRTCDisplays[0].displayRect.height() }; + } + else + { + resolution = { PCRTCDisplays[1].displayRect.width(), PCRTCDisplays[1].displayRect.height() }; + } + } + else + { + const int shift = is_full_height ? 1 : 0; + resolution = { offsets.x, offsets.y << shift }; + } + + resolution.x = std::min(resolution.x, offsets.x); + resolution.y = std::min(resolution.y, is_full_height ? offsets.y << 1 : offsets.y); + + return resolution; + } + + GSVector4i GetFramebufferRect(int display) + { + if (display == -1) + { + return GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect)); + } + else + { + return PCRTCDisplays[display].framebufferRect; + } + } + + int GetFramebufferBitDepth() + { + if (PCRTCDisplays[0].enabled) + return GSLocalMemory::m_psm[PCRTCDisplays[0].PSM].bpp; + else if (PCRTCDisplays[1].enabled) + return GSLocalMemory::m_psm[PCRTCDisplays[1].PSM].bpp; + + return 32; + } + + GSVector2i GetFramebufferSize(int display) + { + if (display == -1) + { + GSVector4i combined_rect = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect); + + if (combined_rect.z >= 2048) + { + int high_x = (PCRTCDisplays[0].framebufferRect.x > PCRTCDisplays[1].framebufferRect.x) ? PCRTCDisplays[0].framebufferRect.x : PCRTCDisplays[1].framebufferRect.x; + combined_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : high_x; + combined_rect.x = 0; + } + + if (combined_rect.w >= 2048) + { + int high_y = (PCRTCDisplays[0].framebufferRect.y > PCRTCDisplays[1].framebufferRect.y) ? PCRTCDisplays[0].framebufferRect.y : PCRTCDisplays[1].framebufferRect.y; + combined_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : high_y; + combined_rect.y = 0; + } + return GSVector2i(combined_rect.z, combined_rect.w); + } + else + { + GSVector4i out_rect = PCRTCDisplays[display].framebufferRect; + + if (out_rect.z >= 2048) + { + out_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : out_rect.x; + out_rect.x = 0; + } + + if (out_rect.w >= 2048) + { + out_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : out_rect.y; + out_rect.y = 0; + } + return GSVector2i(out_rect.z, out_rect.w); + } + } + + // Sets up the rectangles for both the framebuffer read and the displays for the merge circuit. + void SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg) + { + // Save framebuffer information first, while we're here. + PCRTCDisplays[display].FBP = framebufferReg.FBP; + PCRTCDisplays[display].FBW = framebufferReg.FBW; + PCRTCDisplays[display].PSM = framebufferReg.PSM; + + // Probably not really enabled but will cause a mess. + // Q-Ball Billiards enables both circuits but doesn't set one of them up. + if (PCRTCDisplays[display].FBW == 0 && displayReg.DW == 0 && displayReg.DH == 0 && displayReg.MAGH == 0) + { + PCRTCDisplays[display].enabled = false; + return; + } + PCRTCDisplays[display].magnification = GSVector2i(displayReg.MAGH + 1, displayReg.MAGV + 1); + const u32 DW = displayReg.DW + 1; + const u32 DH = displayReg.DH + 1; + + const int renderWidth = DW / PCRTCDisplays[display].magnification.x; + const int renderHeight = DH / PCRTCDisplays[display].magnification.y; + + u32 finalDisplayWidth = renderWidth; + u32 finalDisplayHeight = renderHeight; + // When using screen offsets the screen gets squashed/resized in to the actual screen size. + if (GSConfig.PCRTCOffsets) + { + finalDisplayWidth = DW / (VideoModeDividers[videomode].x + 1); + finalDisplayHeight = DH / (VideoModeDividers[videomode].y + 1); + } + else + { + finalDisplayWidth = std::min(finalDisplayWidth ,DW / (VideoModeDividers[videomode].x + 1)); + finalDisplayHeight = std::min(finalDisplayHeight, DH / (VideoModeDividers[videomode].y + 1)); + } + + // Framebuffer size and offsets. + PCRTCDisplays[display].prevFramebufferOffsets = PCRTCDisplays[display].framebufferOffsets; + PCRTCDisplays[display].framebufferRect.x = 0; + PCRTCDisplays[display].framebufferRect.y = 0; + PCRTCDisplays[display].framebufferRect.z = renderWidth; + + if(FFMD && interlaced) // Round up the height as if it's an odd value, this will cause havok with the merge circuit. + PCRTCDisplays[display].framebufferRect.w = (renderHeight + 1) >> (FFMD * interlaced); // Half height read if FFMD + INT enabled. + else + PCRTCDisplays[display].framebufferRect.w = renderHeight; + PCRTCDisplays[display].framebufferOffsets.x = framebufferReg.DBX; + PCRTCDisplays[display].framebufferOffsets.y = framebufferReg.DBY; + + const bool is_interlaced_resolution = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off); + + // If the interlace flag isn't set, but it's still interlacing, the height is likely reported wrong. + // Q-Ball Billiards. + if (is_interlaced_resolution && !interlaced) + finalDisplayHeight *= 2; + + // Display size and offsets. + PCRTCDisplays[display].displayRect.x = 0; + PCRTCDisplays[display].displayRect.y = 0; + PCRTCDisplays[display].displayRect.z = finalDisplayWidth; + PCRTCDisplays[display].displayRect.w = finalDisplayHeight; + PCRTCDisplays[display].prevDisplayOffset = PCRTCDisplays[display].displayOffset; + PCRTCDisplays[display].displayOffset.x = displayReg.DX; + PCRTCDisplays[display].displayOffset.y = displayReg.DY; + } + + // Calculate framebuffer read offsets, should be considered if only one circuit is enabled, or difference is more than 1 line. + // Only considered if "Anti-blur" is enabled. + void CalculateFramebufferOffset() + { + if (GSConfig.PCRTCAntiBlur && PCRTCDisplays[0].enabled && PCRTCSameSrc) + { + if (abs(PCRTCDisplays[1].framebufferOffsets.y - PCRTCDisplays[0].framebufferOffsets.y) == 1 + && PCRTCDisplays[0].displayOffset.y == PCRTCDisplays[1].displayOffset.y) + { + if (PCRTCDisplays[1].framebufferOffsets.y < PCRTCDisplays[0].framebufferOffsets.y) + PCRTCDisplays[0].framebufferOffsets.y = PCRTCDisplays[1].framebufferOffsets.y; + else + PCRTCDisplays[1].framebufferOffsets.y = PCRTCDisplays[0].framebufferOffsets.y; + } + } + PCRTCDisplays[0].framebufferRect.x += PCRTCDisplays[0].framebufferOffsets.x; + PCRTCDisplays[0].framebufferRect.z += PCRTCDisplays[0].framebufferOffsets.x; + PCRTCDisplays[0].framebufferRect.y += PCRTCDisplays[0].framebufferOffsets.y; + PCRTCDisplays[0].framebufferRect.w += PCRTCDisplays[0].framebufferOffsets.y; + + PCRTCDisplays[1].framebufferRect.x += PCRTCDisplays[1].framebufferOffsets.x; + PCRTCDisplays[1].framebufferRect.z += PCRTCDisplays[1].framebufferOffsets.x; + PCRTCDisplays[1].framebufferRect.y += PCRTCDisplays[1].framebufferOffsets.y; + PCRTCDisplays[1].framebufferRect.w += PCRTCDisplays[1].framebufferOffsets.y; + } + + // Used in software mode to align the buffer when reading. Offset is accounted for (block aligned) by GetOutput. + void RemoveFramebufferOffset(int display) + { + if (display >= 0) + { + // Hardware needs nothing but handling for wrapped framebuffers. + if (GSConfig.UseHardwareRenderer()) + { + if (PCRTCDisplays[display].framebufferRect.z >= 2048) + { + PCRTCDisplays[display].framebufferRect.x = 0; + PCRTCDisplays[display].framebufferRect.z -= 2048; + } + if (PCRTCDisplays[display].framebufferRect.w >= 2048) + { + PCRTCDisplays[display].framebufferRect.y = 0; + PCRTCDisplays[display].framebufferRect.w -= 2048; + } + } + else + { + const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[display].PSM]; + + GSVector4i r = PCRTCDisplays[display].framebufferRect; + r = r.ralign(psm.bs); + + PCRTCDisplays[display].framebufferRect.z -= r.x; + PCRTCDisplays[display].framebufferRect.w -= r.y; + PCRTCDisplays[display].framebufferRect.x -= r.x; + PCRTCDisplays[display].framebufferRect.y -= r.y; + } + } + else + { + // This code is to read the framebuffer nicely block aligned in software, then leave the remaining offset in to the block. + // In hardware mode this doesn't happen, it reads the whole framebuffer, so we need to keep the offset. + if (!GSConfig.UseHardwareRenderer()) + { + const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[1].PSM]; + + GSVector4i r = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect); + r = r.ralign(psm.bs); + + PCRTCDisplays[0].framebufferRect.x -= r.x; + PCRTCDisplays[0].framebufferRect.y -= r.y; + PCRTCDisplays[0].framebufferRect.z -= r.x; + PCRTCDisplays[0].framebufferRect.w -= r.y; + PCRTCDisplays[1].framebufferRect.x -= r.x; + PCRTCDisplays[1].framebufferRect.y -= r.y; + PCRTCDisplays[1].framebufferRect.z -= r.x; + PCRTCDisplays[1].framebufferRect.w -= r.y; + } + } + } + + // If the two displays are offset from each other, move them to the correct offsets. + // If using screen offsets, calculate the positions here. + void CalculateDisplayOffset(bool scanmask) + { + // Offsets are generally ignored, the "hacky" way of doing the displays, but direct to framebuffers. + if (!GSConfig.PCRTCOffsets) + { + const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; + int int_off[2] = { 0, 0 }; + GSVector2i zeroDisplay = NearestToZeroOffset(); + GSVector2i baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset; + + int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y); + if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4) + { + if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y) + PCRTCDisplays[1].displayOffset.y -= blurOffset; + else + PCRTCDisplays[0].displayOffset.y -= blurOffset; + } + + // If there's a single pixel offset, account for it else it can throw interlacing out. + for (int i = 0; i < 2; i++) + { + if (!PCRTCDisplays[i].enabled) + continue; + + // Should this be MAGV/H in the DISPLAY register rather than the "default" magnification? + const int offset = (PCRTCDisplays[i].displayOffset.y - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1); + + if (offset > 4) + continue; + + int_off[i] = offset & 1; + if (offset < 0) + int_off[i] = -int_off[i]; + + PCRTCDisplays[i].displayRect.y += int_off[i]; + PCRTCDisplays[i].displayRect.w += int_off[i]; + } + + // Handle difference in offset between the two displays, used in games like DmC and Time Crisis 2 (for split screen). + // Offset is not screen based, but relative to each other. + if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled) + { + GSVector2i offset; + + offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayOffset.x - PCRTCDisplays[zeroDisplay.x].displayOffset.x) / (VideoModeDividers[videomode].x + 1); + offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayOffset.y - PCRTCDisplays[zeroDisplay.y].displayOffset.y) / (VideoModeDividers[videomode].y + 1); + + if (offset.x >= 4 || !GSConfig.PCRTCAntiBlur) + { + PCRTCDisplays[1 - zeroDisplay.x].displayRect.x += offset.x; + PCRTCDisplays[1 - zeroDisplay.x].displayRect.z += offset.x; + } + if (offset.y >= 4 || !GSConfig.PCRTCAntiBlur) + { + PCRTCDisplays[1 - zeroDisplay.y].displayRect.y += offset.y - int_off[1 - zeroDisplay.y]; + PCRTCDisplays[1 - zeroDisplay.y].displayRect.w += offset.y - int_off[1 - zeroDisplay.y]; + } + + baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset; + } + + // Handle any large vertical offset from the zero position on the screen. + // Example: Hokuto no Ken, does a rougly -14 offset to bring the screen up. + // Ignore the lowest bit, we've already accounted for this + int vOffset = ((static_cast(baseOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1)); + + if(vOffset <= 4 && vOffset != 0) + { + PCRTCDisplays[0].displayRect.y += vOffset - int_off[0]; + PCRTCDisplays[0].displayRect.w += vOffset - int_off[0]; + PCRTCDisplays[1].displayRect.y += vOffset - int_off[1]; + PCRTCDisplays[1].displayRect.w += vOffset - int_off[1]; + } + } + else // We're using screen offsets, so just calculate the entire offset. + { + const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; + GSVector2i offset = { 0, 0 }; + + int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y); + if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4) + { + if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y) + PCRTCDisplays[1].displayOffset.y -= blurOffset; + else + PCRTCDisplays[0].displayOffset.y -= blurOffset; + } + + for (int i = 0; i < 2; i++) + { + // Should this be MAGV/H in the DISPLAY register rather than the "default" magnification? + offset.x = (static_cast(PCRTCDisplays[i].displayOffset.x) - offsets.z) / (VideoModeDividers[videomode].x + 1); + offset.y = (static_cast(PCRTCDisplays[i].displayOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1); + + PCRTCDisplays[i].displayRect.x += offset.x; + PCRTCDisplays[i].displayRect.z += offset.x; + PCRTCDisplays[i].displayRect.y += offset.y; + PCRTCDisplays[i].displayRect.w += offset.y; + } + } + } + } PCRTCDisplays; public: /// Returns the appropriate directory for draw dumping. static std::string GetDrawDumpPath(const char* format, ...); void ResetHandlers(); + void ResetPCRTC(); - int GetFramebufferHeight(); - int GetFramebufferWidth(); - int GetFramebufferBitDepth(); - int GetDisplayHMagnification(); - GSVector4i GetDisplayRect(int i = -1); - GSVector4i GetFrameMagnifiedRect(int i = -1); - GSVector2i GetResolutionOffset(int i = -1); - GSVector2i GetResolution(); - GSVector4i GetFrameRect(int i = -1, bool ignore_off = false); GSVideoMode GetVideoMode(); - bool IsEnabled(int i); bool isinterlaced(); bool isReallyInterlaced(); - bool IsAnalogue(); float GetTvRefreshRate(); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index a93af88802..4f75e189fa 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -77,82 +77,37 @@ void GSRenderer::Destroy() bool GSRenderer::Merge(int field) { - bool en[2]; - - GSVector4i fr[2]; - GSVector4i dr[2]; - GSVector2i display_offsets[2]; - - GSVector2i display_baseline = {INT_MAX, INT_MAX}; - GSVector2i frame_baseline = {INT_MAX, INT_MAX}; - GSVector2i display_combined = {0, 0}; - bool feedback_merge = m_regs->EXTWRITE.WRITE == 1; - bool display_offset = false; - - for (int i = 0; i < 2; i++) - { - en[i] = IsEnabled(i) || (m_regs->EXTBUF.FBIN == i && feedback_merge); - - if (en[i]) - { - fr[i] = GetFrameRect(i); - dr[i] = GetDisplayRect(i); - display_offsets[i] = GetResolutionOffset(i); - - display_combined.x = std::max(((dr[i].right) - dr[i].left) + display_offsets[i].x, display_combined.x); - display_combined.y = std::max((dr[i].bottom - dr[i].top) + display_offsets[i].y, display_combined.y); - - 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(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); - DevCon.Warning("[%d]: %d %d %d %d, %d %d %d %d\n", i, fr[i].x,fr[i].y,fr[i].z,fr[i].w , dr[i].x,dr[i].y,dr[i].z,dr[i].w); - DevCon.Warning("Offset X %d Offset Y %d", display_offsets[i].x, display_offsets[i].y);*/ - } - } - - if (!en[0] && !en[1]) - { - return false; - } - - GL_PUSH("Renderer Merge %d (0: enabled %d 0x%x, 1: enabled %d 0x%x)", s_n, en[0], m_regs->DISP[0].DISPFB.Block(), en[1], m_regs->DISP[1].DISPFB.Block()); - - // try to avoid fullscreen blur, could be nice on tv but on a monitor it's like double vision, hurts my eyes (persona 4, guitar hero) - // - // NOTE: probably the technique explained in graphtip.pdf (Antialiasing by Supersampling / 4. Reading Odd/Even Scan Lines Separately with the PCRTC then Blending) - - const bool samesrc = - en[0] && en[1] && - m_regs->DISP[0].DISPFB.FBP == m_regs->DISP[1].DISPFB.FBP && - m_regs->DISP[0].DISPFB.FBW == m_regs->DISP[1].DISPFB.FBW && - GSUtil::HasCompatibleBits(m_regs->DISP[0].DISPFB.PSM, m_regs->DISP[1].DISPFB.PSM); - bool single_fetch = false; - - GSVector2i fs(0, 0); - GSVector2i ds(0, 0); - - GSTexture* tex[3] = {NULL, NULL, NULL}; - int y_offset[3] = {0, 0, 0}; - s_n++; + GSVector2i fs(0, 0); + GSTexture* tex[3] = { NULL, NULL, NULL }; + int y_offset[3] = { 0, 0, 0 }; + bool feedback_merge = m_regs->EXTWRITE.WRITE == 1; + + PCRTCDisplays.SetVideoMode(GetVideoMode()); + PCRTCDisplays.EnableDisplays(m_regs->PMODE, m_regs->SMODE2, isReallyInterlaced()); + + if (!PCRTCDisplays.PCRTCDisplays[0].enabled && !PCRTCDisplays.PCRTCDisplays[1].enabled) + return false; + + PCRTCDisplays.SetRects(0, m_regs->DISP[0].DISPLAY, m_regs->DISP[0].DISPFB); + PCRTCDisplays.SetRects(1, m_regs->DISP[1].DISPLAY, m_regs->DISP[1].DISPFB); + PCRTCDisplays.CalculateDisplayOffset(m_scanmask_used); + PCRTCDisplays.CalculateFramebufferOffset(); + PCRTCDisplays.CheckSameSource(); + // Only need to check the right/bottom on software renderer, hardware always gets the full texture then cuts a bit out later. - if (samesrc && !feedback_merge && (GSConfig.UseHardwareRenderer() || (fr[0].right == fr[1].right && fr[0].bottom == fr[1].bottom))) + if (PCRTCDisplays.FrameRectMatch() && !PCRTCDisplays.FrameWrap() && !feedback_merge) { - tex[0] = GetOutput(0, y_offset[0]); + tex[0] = GetOutput(-1, y_offset[0]); tex[1] = tex[0]; // saves one texture fetch y_offset[1] = y_offset[0]; - single_fetch = true; } else { - if (en[0]) + if (PCRTCDisplays.PCRTCDisplays[0].enabled) tex[0] = GetOutput(0, y_offset[0]); - if (en[1]) + if (PCRTCDisplays.PCRTCDisplays[1].enabled) tex[1] = GetOutput(1, y_offset[1]); if (feedback_merge) tex[2] = GetFeedbackOutput(); @@ -162,11 +117,6 @@ bool GSRenderer::Merge(int field) GSVector4 src_gs_read[2]; GSVector4 dst[3]; - const bool slbg = m_regs->PMODE.SLBG; - - GSVector2i resolution(GetResolution()); - bool scanmask_frame = m_scanmask_used && !display_offset; - const bool ignore_offset = !GSConfig.PCRTCOffsets; const bool is_bob = GSConfig.InterlaceMode == GSInterlaceMode::BobTFF || GSConfig.InterlaceMode == GSInterlaceMode::BobBFF; // Use offset for bob deinterlacing always, extra offset added later for FFMD mode. @@ -175,6 +125,7 @@ bool GSRenderer::Merge(int field) int field2 = 0; int mode = 3; + bool scanmask_frame = m_scanmask_used && abs(PCRTCDisplays.PCRTCDisplays[0].displayRect.y - PCRTCDisplays.PCRTCDisplays[1].displayRect.y) != 1; // FFMD (half frames) requires blend deinterlacing, so automatically use that. Same when SCANMSK is used but not blended in the merge circuit (Alpine Racer 3) if (GSConfig.InterlaceMode != GSInterlaceMode::Automatic || (!m_regs->SMODE2.FFMD && !scanmask_frame)) { @@ -184,157 +135,38 @@ bool GSRenderer::Merge(int field) for (int i = 0; i < 2; i++) { - if (!en[i] || !tex[i]) + GSPCRTCRegs::PCRTCDisplay& curCircuit = PCRTCDisplays.PCRTCDisplays[i]; + + if (!curCircuit.enabled || !tex[i]) continue; - GSVector4i r = GetFrameMagnifiedRect(i); GSVector4 scale = GSVector4(tex[i]->GetScale()).xyxy(); - - GSVector2i off(ignore_offset ? 0 : display_offsets[i]); - 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); - - if (!GSConfig.UseHardwareRenderer()) - { - // Clear any frame offsets, offset is already done in the software GetOutput. - fr[i].right -= fr[i].left; - fr[i].left = 0; - fr[i].bottom -= fr[i].top; - fr[i].top = 0; - - // Put any frame offset difference back if we aren't anti-blurring on a single fetch (not offset). - if (!GSConfig.PCRTCAntiBlur && single_fetch) - { - fr[i].right += frame_diff.x; - fr[i].left += frame_diff.x; - fr[i].bottom += frame_diff.y; - fr[i].top += frame_diff.y; - } - - } - - // 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; - - if ((!GSConfig.PCRTCAntiBlur || m_scanmask_used) && display_offset) - { - interlace_offset = static_cast(display_diff.y & 1); - - // When the displays are offset by 1 we need to adjust for upscale to handle it (reduces bounce in MGS2 when upscaling) - interlace_offset += (tex[i]->GetScale().y - 1.0f) / 2; - - if (interlace_offset >= 1.0f) - { - if (!ignore_offset) - off.y -= 1; - - display_diff.y -= 1; - } - } - - // Start of Anti-Blur code. - if (!ignore_offset) - { - if (GSConfig.PCRTCAntiBlur) - { - if (samesrc) - { - // Offset by DISPLAY setting - if (display_diff.x < 4) - off.x -= display_diff.x; - if (display_diff.y < 4) - off.y -= display_diff.y; - - // Only functional in HW mode, software clips/positions the framebuffer on read. - if (GSConfig.UseHardwareRenderer()) - { - // Offset by DISPFB setting - if (abs(frame_diff.x) < 4) - off.x += frame_diff.x; - if (abs(frame_diff.y) < 4) - off.y += frame_diff.y; - } - } - } - } - else - { - if (!slbg || !feedback_merge) - { - // If the offsets between the two displays are quite large, it's probably intended for an effect. - if (display_diff.x >= 4 || !GSConfig.PCRTCAntiBlur) - off.x = display_diff.x; - - if (display_diff.y >= 4 || !GSConfig.PCRTCAntiBlur) - off.y = display_diff.y; - - // Need to check if only circuit 2 is enabled. Stuntman toggles circuit 1 on and off every other frame. - if (samesrc || m_regs->PMODE.EN == 2) - { - // Adjusting the screen offset when using a negative offset. - const int videomode = static_cast(GetVideoMode()) - 1; - const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; - GSVector2i base_resolution(offsets.x, offsets.y); - - if (isinterlaced() && !m_regs->SMODE2.FFMD) - base_resolution.y *= 2; - - // Offset by DISPLAY setting - if (display_diff.x < 0) - { - off.x = 0; - if (base_resolution.x > resolution.x) - resolution.x -= display_diff.x; - } - if (display_diff.y < 0) - { - off.y = 0; - if (base_resolution.y > resolution.y) - resolution.y -= display_diff.y; - } - - // Don't do X, we only care about height, this would need to be tailored for games using X (Black does -5). - // Mainly for Hokuto no Ken which does -14 Y offset. - if (display_baseline.y < -4) - off.y += display_baseline.y; - - // Anti-Blur stuff - // Only functional in HW mode, software clips/positions the framebuffer on read. - if (GSConfig.PCRTCAntiBlur && GSConfig.UseHardwareRenderer()) - { - // Offset by DISPFB setting - if (abs(frame_diff.x) < 4) - off.x += frame_diff.x; - if (abs(frame_diff.y) < 4) - off.y += frame_diff.y; - } - } - } - } - // End of Anti-Blur code. - - if (isinterlaced() && m_regs->SMODE2.FFMD) - off.y >>= 1; - // dst is the final destination rect with offset on the screen. - dst[i] = scale * (GSVector4(off).xyxy() + GSVector4(r.rsize())); + dst[i] = scale * GSVector4(curCircuit.displayRect); // src_gs_read is the size which we're really reading from GS memory. - src_gs_read[i] = ((GSVector4(fr[i]) + GSVector4(0, y_offset[i], 0, y_offset[i])) * scale) / GSVector4(tex[i]->GetSize()).xyxy(); - - // src_out_rect is the resized rect for output. (Not really used) - src_out_rect[i] = (GSVector4(r) * scale) / GSVector4(tex[i]->GetSize()).xyxy(); - - if (m_regs->SMODE2.FFMD && !is_bob && !GSConfig.DisableInterlaceOffset && GSConfig.InterlaceMode != GSInterlaceMode::Off) + src_gs_read[i] = ((GSVector4(curCircuit.framebufferRect) + GSVector4(0, y_offset[i], 0, y_offset[i])) * scale) / GSVector4(tex[i]->GetSize()).xyxy(); + + float interlace_offset = 0.0f; + if (isReallyInterlaced() && m_regs->SMODE2.FFMD && !is_bob && !GSConfig.DisableInterlaceOffset && GSConfig.InterlaceMode != GSInterlaceMode::Off) { - // We do half because FFMD is a half sized framebuffer, then we offset by 1 in the shader for the actual interlace - if(GetUpscaleMultiplier() > 1.0f) - interlace_offset += ((((tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y) + 0.5f) * 0.5f) - 1.0f) * static_cast(field ^ field2); - offset = 1.0f; + interlace_offset = (scale.y) * static_cast(field ^ field2); } - // Restore manually offset "interlace" lines + // Scanmask frame offsets. It's gross, I'm sorry but it sucks. + if (m_scanmask_used) + { + int displayIntOffset = PCRTCDisplays.PCRTCDisplays[i].displayRect.y - PCRTCDisplays.PCRTCDisplays[1 - i].displayRect.y; + + if (displayIntOffset > 0) + { + displayIntOffset &= 1; + dst[i].y -= displayIntOffset * scale.y; + dst[i].w -= displayIntOffset * scale.y; + interlace_offset += displayIntOffset; + } + } + dst[i] += GSVector4(0.0f, interlace_offset, 0.0f, interlace_offset); } @@ -351,41 +183,21 @@ bool GSRenderer::Merge(int field) dst[2] = GSVector4(scale * GSVector4(feedback_rect.rsize())); } - // Set the resolution to the height of the displays (kind of a saturate height) - if (ignore_offset && !feedback_merge) - { - GSVector2i max_resolution = GetResolution(); - resolution.x = display_combined.x - display_baseline.x; - resolution.y = display_combined.y - display_baseline.y; - - if (isinterlaced() && m_regs->SMODE2.FFMD) - { - resolution.y >>= 1; - } - - resolution.x = std::min(max_resolution.x, resolution.x); - resolution.y = std::min(max_resolution.y, resolution.y); - } - + GSVector2i resolution = PCRTCDisplays.GetResolution(); fs = GSVector2i(static_cast(static_cast(resolution.x) * GetUpscaleMultiplier()), static_cast(static_cast(resolution.y) * GetUpscaleMultiplier())); - ds = fs; - // When interlace(FRAME) mode, the rect is half height, so it needs to be stretched. - const bool is_interlaced_resolution = m_regs->SMODE2.INT || (isReallyInterlaced() && IsAnalogue() && GSConfig.InterlaceMode != GSInterlaceMode::Off); - - if (is_interlaced_resolution && m_regs->SMODE2.FFMD) - ds.y *= 2; - - m_real_size = GSVector2i(fs.x, is_interlaced_resolution ? ds.y : fs.y); + m_real_size = GSVector2i(fs.x, fs.y); if (!tex[0] && !tex[1]) return false; - if ((tex[0] == tex[1]) && (src_out_rect[0] == src_out_rect[1]).alltrue() && (dst[0] == dst[1]).alltrue() && !feedback_merge && !slbg) + if ((tex[0] == tex[1]) && (src_out_rect[0] == src_out_rect[1]).alltrue() && + (PCRTCDisplays.PCRTCDisplays[0].displayRect == PCRTCDisplays.PCRTCDisplays[1].displayRect).alltrue() && + (PCRTCDisplays.PCRTCDisplays[0].framebufferRect == PCRTCDisplays.PCRTCDisplays[1].framebufferRect).alltrue() && + !feedback_merge && !m_regs->PMODE.SLBG) { // the two outputs are identical, skip drawing one of them (the one that is alpha blended) - tex[0] = NULL; } @@ -394,7 +206,7 @@ bool GSRenderer::Merge(int field) g_gs_device->Merge(tex, src_gs_read, dst, fs, m_regs->PMODE, m_regs->EXTBUF, c); if (isReallyInterlaced() && GSConfig.InterlaceMode != GSInterlaceMode::Off) - g_gs_device->Interlace(ds, field ^ field2, mode, offset); + g_gs_device->Interlace(fs, field ^ field2, mode, offset); if (GSConfig.ShadeBoost) g_gs_device->ShadeBoost(); diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index b65a5d9fe3..ef17374b8b 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -821,14 +821,14 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, { // 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output // Note: value outside of dRect must contains the background color (c) - StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY); + StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY, false); } // Save 2nd output if (feedback_write_2) { StretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast(ShaderConvert::YUV)].get(), - m_merge.cb.get(), nullptr, true); + m_merge.cb.get(), nullptr, false); } // Restore background color to process the normal merge @@ -838,13 +838,13 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, if (sTex[0]) { // 1st output is enabled. It must be blended - StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), true); + StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), false); } if (feedback_write_1) { StretchRect(sTex[0], full_r, sTex[2], dRect[2], m_convert.ps[static_cast(ShaderConvert::YUV)].get(), - m_merge.cb.get(), nullptr, true); + m_merge.cb.get(), nullptr, false); } } diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index e67fd9bde5..4ca28335ef 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -605,7 +605,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, { static_cast(sTex[1])->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); OMSetRenderTargets(dTex, nullptr, darea); - SetUtilityTexture(sTex[1], m_linear_sampler_cpu); + SetUtilityTexture(sTex[1], m_point_sampler_cpu); BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, c); @@ -625,7 +625,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, EndRenderPass(); OMSetRenderTargets(sTex[2], nullptr, fbarea); if (dcleared) - SetUtilityTexture(dTex, m_linear_sampler_cpu); + SetUtilityTexture(dTex, m_point_sampler_cpu); // sTex[2] can be sTex[0], in which case it might be cleared (e.g. Xenosaga). BeginRenderPassForStretchRect(static_cast(sTex[2]), fbarea, GSVector4i(dRect[2])); @@ -663,7 +663,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, { // 1st output is enabled. It must be blended SetUtilityRootSignature(); - SetUtilityTexture(sTex[0], m_linear_sampler_cpu); + SetUtilityTexture(sTex[0], m_point_sampler_cpu); SetPipeline(m_merge[PMODE.MMOD].get()); DrawStretchRect(sRect[0], dRect[0], dTex->GetSize()); } @@ -673,7 +673,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, EndRenderPass(); SetUtilityRootSignature(); SetPipeline(m_convert[static_cast(ShaderConvert::YUV)].get()); - SetUtilityTexture(dTex, m_linear_sampler_cpu); + SetUtilityTexture(dTex, m_point_sampler_cpu); OMSetRenderTargets(sTex[2], nullptr, fbarea); BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE); DrawStretchRect(full_r, dRect[2], dsize); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index f67c82cc60..75627ca6fb 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -50,43 +50,7 @@ GSRendererHW::GSRendererHW() GSVector2i GSRendererHW::GetOutputSize(int real_h) { - GSVector2i crtc_size(GetResolution()); - - // Correct framebuffer size to get output size when offsets not considered (uses framebuffer height) - if (!GSConfig.PCRTCOffsets) - { - const int videomode = static_cast(GetVideoMode()) - 1; - const int display_width = (VideoModeDividers[videomode].z + 1) / GetDisplayHMagnification(); - const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode]; - int display_height = offsets.y; - - if (isinterlaced() && !m_regs->SMODE2.FFMD) - display_height *= 2; - - if (crtc_size.x < display_width || crtc_size.y < display_height) - { - GSVector2i display_baseline = { 4096, 4096 }; - - for (int i = 0; i < 2; i++) - { - if (IsEnabled(i)) - { - const GSVector4i dr = GetDisplayRect(i); - - const GSVector2i display_diff(dr.left - display_baseline.x, dr.top - display_baseline.y); - - if (display_diff.x != 0 && abs(display_diff.x) < 4 && crtc_size.x < display_width) - crtc_size.x -= display_diff.x; - - if (display_diff.y != 0 && abs(display_diff.y) < 4 && crtc_size.y < display_height) - crtc_size.y -= display_diff.y; - - display_baseline.x = std::min(dr.left, display_baseline.x); - display_baseline.y = std::min(dr.top, display_baseline.y); - } - } - } - } + GSVector2i crtc_size(PCRTCDisplays.GetResolution()); // Include negative display offsets in the height here. crtc_size.y = std::max(crtc_size.y, real_h); @@ -241,40 +205,33 @@ void GSRendererHW::VSync(u32 field, bool registers_written) GSTexture* GSRendererHW::GetOutput(int i, int& y_offset) { - const GSRegDISPFB& DISPFB = m_regs->DISP[i].DISPFB; + int index = i >= 0 ? i : 1; - GIFRegTEX0 TEX0 = {}; + GSPCRTCRegs::PCRTCDisplay& curFramebuffer = PCRTCDisplays.PCRTCDisplays[index]; + GSVector2i framebufferSize = PCRTCDisplays.GetFramebufferSize(i); + const int fb_width = framebufferSize.x; + const int fb_height = framebufferSize.y; - TEX0.TBP0 = DISPFB.Block(); - TEX0.TBW = DISPFB.FBW; - TEX0.PSM = DISPFB.PSM; - - const int videomode = static_cast(GetVideoMode()) - 1; - const GSVector4i offsets = VideoModeOffsets[videomode]; - - const int fb_width = std::min(std::min(GetFramebufferWidth(), DISPFB.FBW * 64) + static_cast(DISPFB.DBX), 2048); - 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) + static_cast(DISPFB.DBY)) % 2048; - // If there is a negative vertical offset on the picture, we need to read more. - if (display_offset < 0) - { - fb_height += -display_offset; - } - // TRACE(_T("[%d] GetOutput %d %05x (%d)\n"), static_cast(g_perfmon.GetFrame()), i, static_cast(TEX0.TBP0), static_cast(TEX0.PSM)); + PCRTCDisplays.RemoveFramebufferOffset(i); + // TRACE(_T("[%d] GetOutput %d %05x (%d)\n"), (int)m_perfmon.GetFrame(), i, (int)TEX0.TBP0, (int)TEX0.PSM); GSTexture* t = nullptr; + GIFRegTEX0 TEX0 = {}; + TEX0.TBP0 = curFramebuffer.Block(); + TEX0.TBW = curFramebuffer.FBW; + TEX0.PSM = curFramebuffer.PSM; + if (GSTextureCache::Target* rt = m_tc->LookupDisplayTarget(TEX0, GetOutputSize(fb_height) * GSConfig.UpscaleMultiplier, fb_width, fb_height)) { t = rt->m_texture; const int delta = TEX0.TBP0 - rt->m_TEX0.TBP0; - if (delta > 0 && DISPFB.FBW != 0) + if (delta > 0 && curFramebuffer.FBW != 0) { const int pages = delta >> 5u; - int y_pages = pages / DISPFB.FBW; - y_offset = y_pages * GSLocalMemory::m_psm[DISPFB.PSM].pgs.y; + int y_pages = pages / curFramebuffer.FBW; + y_offset = y_pages * GSLocalMemory::m_psm[curFramebuffer.PSM].pgs.y; GL_CACHE("Frame y offset %d pixels, unit %d", y_offset, i); } @@ -797,8 +754,8 @@ GSVector2i GSRendererHW::GetTargetSize(GSVector2i* unscaled_size) const int page_y = GSLocalMemory::m_psm[m_context->FRAME.PSM].pgs.y - 1; // Round up the page as channel shuffles are generally done in pages at a time - width = (std::max(static_cast(GetResolution().x), width) + page_x) & ~page_x; - min_height = (std::max(static_cast(GetResolution().y), min_height) + page_y) & ~page_y; + width = (std::max(static_cast(PCRTCDisplays.GetResolution().x), width) + page_x) & ~page_x; + min_height = (std::max(static_cast(PCRTCDisplays.GetResolution().y), min_height) + page_y) & ~page_y; } // Align to even lines, reduces the chance of tiny resizes. @@ -1443,13 +1400,14 @@ void GSRendererHW::Draw() const bool clear_height_valid = (m_r.w >= 1024); if (clear_height_valid && context->FRAME.FBW == 1) { - u32 width = ceil(static_cast(m_r.w) / GetFramebufferHeight()) * 64; + GSVector2i fb_size = PCRTCDisplays.GetFramebufferSize(-1); + u32 width = ceil(static_cast(m_r.w) / fb_size.y) * 64; // Framebuffer is likely to be read as 16bit later, so we will need to double the width if the write is 32bit. - const bool double_width = GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 32 && GetFramebufferBitDepth() == 16; + const bool double_width = GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 32 && PCRTCDisplays.GetFramebufferBitDepth() == 16; m_r.x = 0; m_r.y = 0; - m_r.w = GetFramebufferHeight(); - m_r.z = std::max((width * (double_width ? 2 : 1)), static_cast(GetFramebufferWidth())); + m_r.w = fb_size.y; + m_r.z = std::max((width * (double_width ? 2 : 1)), static_cast(fb_size.x)); context->FRAME.FBW = (m_r.z + 63) / 64; m_context->scissor.in.z = context->FRAME.FBW * 64; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm index a2987d1b72..995c153dcc 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm @@ -563,12 +563,12 @@ void GSDeviceMTL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, { // 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output // Note: value outside of dRect must contains the background color (c) - StretchRect(sTex[1], sRect[1], dTex, dRect[1], ShaderConvert::COPY); + StretchRect(sTex[1], sRect[1], dTex, dRect[1], ShaderConvert::COPY, false); } // Save 2nd output if (feedback_write_2) // FIXME I'm not sure dRect[1] is always correct - DoStretchRect(dTex, full_r, sTex[2], dRect[1], m_convert_pipeline[static_cast(ShaderConvert::YUV)], true, LoadAction::DontCareIfFull, &cb_yuv, sizeof(cb_yuv)); + DoStretchRect(dTex, full_r, sTex[2], dRect[1], m_convert_pipeline[static_cast(ShaderConvert::YUV)], false, LoadAction::DontCareIfFull, &cb_yuv, sizeof(cb_yuv)); if (feedback_write_2_but_blend_bg) ClearRenderTarget(dTex, c); @@ -582,17 +582,17 @@ void GSDeviceMTL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, if (PMODE.MMOD == 1) { // Blend with a constant alpha - DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, true, LoadAction::Load, &cb_c, sizeof(cb_c)); + DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, false, LoadAction::Load, &cb_c, sizeof(cb_c)); } else { // Blend with 2 * input alpha - DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, true, LoadAction::Load, nullptr, 0); + DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, false, LoadAction::Load, nullptr, 0); } } if (feedback_write_1) // FIXME I'm not sure dRect[0] is always correct - StretchRect(dTex, full_r, sTex[2], dRect[0], ShaderConvert::YUV); + StretchRect(dTex, full_r, sTex[2], dRect[0], ShaderConvert::YUV, false); }} void GSDeviceMTL::DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset, int bufIdx) diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index 7523c27170..fb8f33d925 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -1327,7 +1327,7 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, { // 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output // Note: value outside of dRect must contains the background color (c) - StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY); + StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY, false); } // Upload constant to select YUV algo @@ -1340,7 +1340,7 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, // Save 2nd output if (feedback_write_2) - StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV); + StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV, false); // Restore background color to process the normal merge if (feedback_write_2_but_blend_bg) @@ -1357,17 +1357,17 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, // Blend with a constant alpha m_merge_obj.ps[1].Bind(); m_merge_obj.ps[1].Uniform4fv(0, c.v); - StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[1], true, OMColorMaskSelector()); + StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[1], true, OMColorMaskSelector(), false); } else { // Blend with 2 * input alpha - StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[0], true, OMColorMaskSelector()); + StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[0], true, OMColorMaskSelector(), false); } } if (feedback_write_1) - StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV); + StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV, false); } void GSDeviceOGL::DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset, int bufIdx) diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp index f2d6b0fd6f..33faa15ae4 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp @@ -82,22 +82,6 @@ void GSRendererSW::VSync(u32 field, bool registers_written) { Sync(0); // IncAge might delete a cached texture in use - if (0) if (LOG) - { - fprintf(s_fp, "%llu\n", g_perfmon.GetFrame()); - - GSVector4i dr = GetDisplayRect(); - GSVector4i fr = GetFrameRect(); - - fprintf(s_fp, "dr %d %d %d %d, fr %d %d %d %d\n", - dr.x, dr.y, dr.z, dr.w, - fr.x, fr.y, fr.z, fr.w); - - m_regs->Dump(s_fp); - - fflush(s_fp); - } - /* int draw[8], sum = 0; @@ -127,27 +111,20 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset) { Sync(1); - const GSRegDISPFB& DISPFB = m_regs->DISP[i].DISPFB; + int index = i >= 0 ? i : 1; + GSPCRTCRegs::PCRTCDisplay& curFramebuffer = PCRTCDisplays.PCRTCDisplays[index]; + GSVector2i framebufferSize = PCRTCDisplays.GetFramebufferSize(i); + GSVector4i framebufferRect = PCRTCDisplays.GetFramebufferRect(i); + int w = curFramebuffer.FBW * 64; + int h = framebufferSize.y; - int w = DISPFB.FBW * 64; - - const int videomode = static_cast(GetVideoMode()) - 1; - 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); - - // If there is a negative vertical offset on the picture, we need to read more. - if (display_offset < 0) - { - h += -display_offset; - } - - if (g_gs_device->ResizeTarget(&m_texture[i], w, h)) + if (g_gs_device->ResizeTarget(&m_texture[index], w, h)) { + const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[curFramebuffer.PSM]; constexpr int pitch = 1024 * 4; - const int off_x = DISPFB.DBX & 0x7ff; - const int off_y = DISPFB.DBY & 0x7ff; + // Should really be framebufferOffsets rather than framebufferRect but this might be compensated with anti-blur in some games. + const int off_x = (framebufferRect.x & 0x7ff) & ~(psm.bs.x-1); + const int off_y = (framebufferRect.y & 0x7ff) & ~(psm.bs.y-1); const GSVector4i out_r(0, 0, w, h); 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); @@ -155,6 +132,7 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset) bool h_wrap = false; bool w_wrap = false; + PCRTCDisplays.RemoveFramebufferOffset(i); // Need to read it in 2 parts, since you can't do a split rect. if (r.bottom >= 2048) { @@ -172,50 +150,51 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset) w_wrap = true; } - const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[DISPFB.PSM]; - // Display doesn't use texa, and instead uses the equivalent of this GIFRegTEXA texa = {}; texa.AEM = 0; - texa.TA0 = (DISPFB.PSM == PSM_PSMCT24 || DISPFB.PSM == PSM_PSGPU24) ? 0x80 : 0; + texa.TA0 = (curFramebuffer.PSM == PSM_PSMCT24 || curFramebuffer.PSM == PSM_PSGPU24) ? 0x80 : 0; texa.TA1 = 0x80; // Top left rect - psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), r.ralign(psm.bs), m_output, pitch, texa); + psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), r.ralign(psm.bs), m_output, pitch, texa); + + // Top left rect + psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), r.ralign(psm.bs), m_output, pitch, texa); int top = (h_wrap) ? ((r.bottom - r.top) * pitch) : 0; - int left = (w_wrap) ? (r.right - r.left) * (GSLocalMemory::m_psm[DISPFB.PSM].bpp / 8) : 0; + int left = (w_wrap) ? (r.right - r.left) * (GSLocalMemory::m_psm[curFramebuffer.PSM].bpp / 8) : 0; // The following only happen if the DBX/DBY wrap around at 2048. // Top right rect if (w_wrap) - psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rw.ralign(psm.bs), &m_output[left], pitch, texa); + psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rw.ralign(psm.bs), &m_output[left], pitch, texa); // Bottom left rect if (h_wrap) - psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rh.ralign(psm.bs), &m_output[top], pitch, texa); + psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rh.ralign(psm.bs), &m_output[top], pitch, texa); // Bottom right rect if (h_wrap && w_wrap) { // 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); - psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rwh.ralign(psm.bs), &m_output[top + left], pitch, texa); + psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rwh.ralign(psm.bs), &m_output[top + left], pitch, texa); } - m_texture[i]->Update(out_r, m_output, pitch); + m_texture[index]->Update(out_r, m_output, pitch); if (GSConfig.DumpGSData) { if (GSConfig.SaveFrame && s_n >= GSConfig.SaveN) { - m_texture[i]->Save(GetDrawDumpPath("%05d_f%lld_fr%d_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), i, (int)DISPFB.Block(), psm_str(DISPFB.PSM))); + m_texture[i]->Save(GetDrawDumpPath("%05d_f%lld_fr%d_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), i, (int)curFramebuffer.Block(), psm_str(curFramebuffer.PSM))); } } } - return m_texture[i]; + return m_texture[index]; } GSTexture* GSRendererSW::GetFeedbackOutput() @@ -490,18 +469,18 @@ void GSRendererSW::Draw() { // Dump the RT in 32 bits format. It helps to debug texture shuffle effect s = GetDrawDumpPath("%05d_f%lld_rt0_%05x_32bits.bmp", s_n, frame, m_context->FRAME.Block()); - m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, GetFrameRect().width(), 512); + m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, PCRTCDisplays.GetFramebufferRect(-1).width(), 512); } s = GetDrawDumpPath("%05d_f%lld_rt0_%05x_%s.bmp", s_n, frame, m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM)); - m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512); + m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512); } if (GSConfig.SaveDepth && s_n >= GSConfig.SaveN) { s = GetDrawDumpPath("%05d_f%lld_rz0_%05x_%s.bmp", s_n, frame, m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM)); - m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512); + m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512); } Queue(data); @@ -514,18 +493,18 @@ void GSRendererSW::Draw() { // Dump the RT in 32 bits format. It helps to debug texture shuffle effect s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_32bits.bmp", s_n, frame, m_context->FRAME.Block()); - m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, GetFrameRect().width(), 512); + m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, PCRTCDisplays.GetFramebufferRect(-1).width(), 512); } s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_%s.bmp", s_n, frame, m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM)); - m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512); + m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512); } if (GSConfig.SaveDepth && s_n >= GSConfig.SaveN) { s = GetDrawDumpPath("%05d_f%lld_rz1_%05x_%s.bmp", s_n, frame, m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM)); - m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512); + m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512); } if (GSConfig.SaveL > 0 && (s_n - GSConfig.SaveN) > GSConfig.SaveL) @@ -613,14 +592,14 @@ void GSRendererSW::Sync(int reason) { s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM)); - m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512); + m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512); } if (GSConfig.SaveDepth) { s = GetDrawDumpPath("%05d_f%lld_zb1_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM)); - m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512); + m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512); } } diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 4b3c620cb7..e1e4d7b7d1 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -736,7 +736,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, { static_cast(sTex[1])->TransitionToLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); OMSetRenderTargets(dTex, nullptr, darea, false); - SetUtilityTexture(sTex[1], m_linear_sampler); + SetUtilityTexture(sTex[1], m_point_sampler); BeginClearRenderPass(m_utility_color_render_pass_clear, darea, c); SetPipeline(m_convert[static_cast(ShaderConvert::COPY)]); DrawStretchRect(sRect[1], PMODE.SLBG ? dRect[2] : dRect[1], dsize); @@ -753,7 +753,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, EndRenderPass(); OMSetRenderTargets(sTex[2], nullptr, fbarea, false); if (dcleared) - SetUtilityTexture(dTex, m_linear_sampler); + SetUtilityTexture(dTex, m_point_sampler); // sTex[2] can be sTex[0], in which case it might be cleared (e.g. Xenosaga). BeginRenderPassForStretchRect(static_cast(sTex[2]), fbarea, GSVector4i(dRect[2])); if (dcleared) @@ -787,7 +787,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, if (sTex[0] && sTex[0]->GetState() == GSTexture::State::Dirty) { // 1st output is enabled. It must be blended - SetUtilityTexture(sTex[0], m_linear_sampler); + SetUtilityTexture(sTex[0], m_point_sampler); SetPipeline(m_merge[PMODE.MMOD]); SetUtilityPushConstants(&c, sizeof(c)); DrawStretchRect(sRect[0], dRect[0], dTex->GetSize()); @@ -797,7 +797,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, { EndRenderPass(); SetPipeline(m_convert[static_cast(ShaderConvert::YUV)]); - SetUtilityTexture(dTex, m_linear_sampler); + SetUtilityTexture(dTex, m_point_sampler); SetUtilityPushConstants(yuv_constants, sizeof(yuv_constants)); OMSetRenderTargets(sTex[2], nullptr, fbarea, false); BeginRenderPass(m_utility_color_render_pass_load, fbarea);