diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 05fe1ef3cc..c31dad2d89 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -95,6 +95,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget* SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.interlacing, "EmuCore/GS", "deinterlace", DEFAULT_INTERLACE_MODE); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.bilinearFiltering, "EmuCore/GS", "linear_present", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.integerScaling, "EmuCore/GS", "IntegerScaling", false); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.PCRTCOffsets, "EmuCore/GS", "pcrtc_offsets", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.internalResolutionScreenshots, "EmuCore/GS", "InternalResolutionScreenshots", false); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.zoom, "EmuCore/GS", "Zoom", 100.0f); SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.stretchY, "EmuCore/GS", "StretchY", 100.0f); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index 3c05868e2e..172fa3e983 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -6,7 +6,7 @@ 0 0 - 747 + 566 890 @@ -261,34 +261,41 @@ - - - - Integer Upscaling - - - - + Bilinear Filtering - + Sync To Host Refresh (VSync) - + Internal Resolution Screenshots + + + + Integer Upscaling + + + + + + + Screen Offsets + + + diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 37c289509e..fb27ceb360 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -422,6 +422,7 @@ struct Pcsx2Config struct { bool + PCRTCOffsets : 1, IntegerScaling : 1, LinearPresent : 1, UseDebugDevice : 1, diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index c6e55a681c..89567aec87 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -1359,6 +1359,7 @@ void GSApp::Init() m_default_configuration["FMVSoftwareRendererSwitch"] = "0"; m_default_configuration["fxaa"] = "0"; m_default_configuration["HWDisableReadbacks"] = "0"; + m_default_configuration["pcrtc_offsets"] = "0"; m_default_configuration["IntegerScaling"] = "0"; m_default_configuration["deinterlace"] = "7"; m_default_configuration["conservative_framebuffer"] = "1"; diff --git a/pcsx2/GS/GSState.cpp b/pcsx2/GS/GSState.cpp index c2e16b78fd..fc1977ec83 100644 --- a/pcsx2/GS/GSState.cpp +++ b/pcsx2/GS/GSState.cpp @@ -368,63 +368,51 @@ GSVideoMode GSState::GetVideoMode() __assume(0); // unreachable } -// There are some cases where the PS2 seems to saturate the output circuit size when the developer requests for a higher -// unsupported value with respect to the current video mode via the DISP registers, the following function handles such cases. -// NOTE: This function is totally hacky as there are no documents related to saturation of output dimensions, function is -// generally just based on technical and intellectual guesses. -void GSState::SaturateOutputSize(GSVector4i& r) +bool GSState::IsAnalogue() { + return GetVideoMode() == GSVideoMode::NTSC || GetVideoMode() == GSVideoMode::PAL || GetVideoMode() == GSVideoMode::HDTV_1080I; +} + +GSVector4i GSState::GetDisplayRectSize(int i) +{ + GSVector4i rectangle = { 0, 0, 0, 0 }; + + if (!IsEnabled(0) && !IsEnabled(1)) + return rectangle; + const GSVideoMode videomode = GetVideoMode(); + const auto& DISP = m_regs->DISP[i].DISPLAY; - const bool is_ntsc = videomode == GSVideoMode::NTSC; - const bool is_pal = videomode == GSVideoMode::PAL; + 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)). + const int width = (DW / (VideoModeDividers[(int)videomode - 1].x + 1)); + const int height = (DH / (VideoModeDividers[(int)videomode - 1].y + 1)); - const auto& SMODE2 = m_regs->SMODE2; - const auto& PMODE = m_regs->PMODE; - const int res_multi = (SMODE2.INT + 1); - // Pixels only get drawn so quick so oversizing of the DISPLAY is ignored when rasterized. - // Non-Interlaced pictures can only do half the number of lines (double strike) - if (is_ntsc) - r.bottom = r.top + std::min(r.height(), 224 * res_multi); - else if (is_pal) - r.bottom = r.top + std::min(r.height(), 256 * res_multi); + // Set up the display rectangle based on the values obtained from DISPLAY registers + + rectangle.right = width; + rectangle.bottom = height; + //DevCon.Warning("Display Rect Size Returning w %d h %d", width, height); + return rectangle; } GSVector4i GSState::GetDisplayRect(int i) { - if (!IsEnabled(0) && !IsEnabled(1)) - return {}; + GSVector4i rectangle = { 0, 0, 0, 0 }; - // If no specific context is requested then pass the merged rectangle as return value if (i == -1) { - if (IsEnabled(0) && IsEnabled(1)) - { - const GSVector4i disp1_rect = GetDisplayRect(0); - const GSVector4i disp2_rect = GetDisplayRect(1); - - const GSVector4i intersect = disp1_rect.rintersect(disp2_rect); - const GSVector4i combined = disp1_rect.runion_ordered(disp2_rect); - - // If the conditions for passing the merged rectangle is unsatisfied, then - // pass the rectangle with the bigger size. - const bool can_be_merged = - intersect.width() == 0 || - intersect.height() == 0 || - intersect.xyxy().eq(combined.xyxy()); - - if (can_be_merged) - return combined; - - if (disp1_rect.rarea() > disp2_rect.rarea()) - return disp1_rect; - - return disp2_rect; - } - - i = m_regs->PMODE.EN2; + 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; @@ -441,18 +429,46 @@ GSVector4i GSState::GetDisplayRect(int i) const int height = DH / magnification.y; // Set up the display rectangle based on the values obtained from DISPLAY registers - GSVector4i rectangle; - - rectangle.left = DX / magnification.x; - rectangle.top = DY / magnification.y; + rectangle.left = DX; + rectangle.top = DY; rectangle.right = rectangle.left + width; rectangle.bottom = rectangle.top + height; - SaturateOutputSize(rectangle); - + //DevCon.Warning("left %d offset %d total %d top %d offset %d total %d", rectangle.left, VideoModeOffsets[(int)videomode - 1].z, (rectangle.left - VideoModeOffsets[(int)videomode - 1].z) / magnification.x, rectangle.top, (VideoModeOffsets[(int)videomode - 1].w / magnification.y) * (IsAnalogue() ? res_multi : 1), v_offset); + //DevCon.Warning("Get Display Rect Left %d Right %d Top %d Bottom %d", rectangle.left, rectangle.right, rectangle.top, rectangle.bottom); return rectangle; } +GSVector2i GSState::GetResolutionOffset(int i) +{ + const GSVideoMode videomode = GetVideoMode(); + const auto& DISP = m_regs->DISP[i].DISPLAY; + + const auto& SMODE2 = m_regs->SMODE2; + const int res_multi = (SMODE2.INT + 1); + GSVector2i offset; + + offset.x = (((int)DISP.DX - VideoModeOffsets[(int)videomode - 1].z) / (VideoModeDividers[(int)videomode - 1].x + 1)); + offset.y = (((int)DISP.DY - (VideoModeOffsets[(int)videomode - 1].w * (IsAnalogue() ? res_multi : 1))) / (VideoModeDividers[(int)videomode - 1].y + 1)); + //DevCon.Warning("Res Offset X %d Video mode %d Y %d Video mode %d", DISP.DX, VideoModeOffsets[(int)videomode - 1].z, DISP.DY, (VideoModeOffsets[(int)videomode - 1].w * (IsAnalogue() ? res_multi : 1))); + + return offset; +} + +GSVector2i GSState::GetResolution() +{ + const GSVideoMode videomode = GetVideoMode(); + const auto& SMODE2 = m_regs->SMODE2; + const int res_multi = (SMODE2.INT + 1); + + GSVector2i resolution; + + resolution.x = VideoModeOffsets[(int)videomode - 1].x; + resolution.y = VideoModeOffsets[(int)videomode - 1].y * ((IsAnalogue() && res_multi) ? res_multi : 1); + //DevCon.Warning("Getting Resolution X %d Y %d", resolution.x, resolution.y); + return resolution; +} + GSVector4i GSState::GetFrameRect(int i) { // If no specific context is requested then pass the merged rectangle as return value @@ -461,19 +477,31 @@ GSVector4i GSState::GetFrameRect(int i) GSVector4i rectangle = GetDisplayRect(i); - int w = rectangle.width(); - int h = rectangle.height(); + const auto& DISP = m_regs->DISP[i].DISPLAY; - if (isinterlaced() && m_regs->SMODE2.FFMD && h > 1) - h >>= 1; + 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; const u32 DBY = m_regs->DISP[i].DISPFB.DBY; + int w = DW / magnification.x; + int h = DH / magnification.y; + rectangle.left = DBX; rectangle.top = DBY; - rectangle.right = rectangle.left + w; - rectangle.bottom = rectangle.top + h; + + if (isinterlaced() && m_regs->SMODE2.FFMD && h > 1) + { + h >>= 1; + } + + // Round down as this is the reading rect, so we want 0-639 and 0-479 for an example + rectangle.right = rectangle.left + w - 1; + rectangle.bottom = rectangle.top + h - 1; + + //DevCon.Warning("Frame Rect left %d right %d top %d bottom %d DBX %d DBY %d", rectangle.left, rectangle.right, rectangle.top, rectangle.bottom, DBX, DBY); return rectangle; } diff --git a/pcsx2/GS/GSState.h b/pcsx2/GS/GSState.h index ce147db65b..a08b23c1c0 100644 --- a/pcsx2/GS/GSState.h +++ b/pcsx2/GS/GSState.h @@ -258,6 +258,29 @@ 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) + }; + + 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) + }; + public: GSState(); virtual ~GSState(); @@ -265,13 +288,16 @@ public: void ResetHandlers(); int GetFramebufferHeight(); - void SaturateOutputSize(GSVector4i& r); GSVector4i GetDisplayRect(int i = -1); + GSVector4i GetDisplayRectSize(int i = -1); + GSVector2i GetResolutionOffset(int i = -1); + GSVector2i GetResolution(); GSVector4i GetFrameRect(int i = -1); GSVideoMode GetVideoMode(); bool IsEnabled(int i); bool isinterlaced(); + bool IsAnalogue(); float GetTvRefreshRate(); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index d12ed4a756..25ea20512b 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -77,25 +77,32 @@ bool GSRenderer::Merge(int field) 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; for (int i = 0; i < 2; i++) { - en[i] = IsEnabled(i); + en[i] = IsEnabled(i) || (m_regs->EXTBUF.FBIN == i && feedback_merge); if (en[i]) { fr[i] = GetFrameRect(i); dr[i] = GetDisplayRect(i); + display_combined.x = std::max(GetDisplayRectSize(i).right + abs(GetResolutionOffset(i).x), display_combined.x); + display_combined.y = std::max(GetDisplayRectSize(i).bottom + abs(GetResolutionOffset(i).y), display_combined.y); display_baseline.x = std::min(dr[i].left, display_baseline.x); display_baseline.y = std::min(dr[i].top, 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); + //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); //printf("[%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); } } + //display_combined.y = display_combined.y * (IsAnalogue() ? (isinterlaced() + 1) : 1); + if (!en[0] && !en[1]) { return false; @@ -162,8 +169,6 @@ bool GSRenderer::Merge(int field) s_n++; - bool feedback_merge = m_regs->EXTWRITE.WRITE == 1; - if (samesrc && fr[0].bottom == fr[1].bottom && !feedback_merge) { tex[0] = GetOutput(0, y_offset[0]); @@ -182,78 +187,187 @@ bool GSRenderer::Merge(int field) GSVector4 src[2]; GSVector4 src_hw[2]; - GSVector4 dst[2]; + GSVector4 dst[3]; const bool slbg = m_regs->PMODE.SLBG; - bool scanmsk_frame = true; for (int i = 0; i < 2; i++) { if (!en[i] || !tex[i]) continue; - GSVector4i r = fr[i]; + const GSVector2i resolution(GetResolution()); + GSVector4i r = GetDisplayRectSize(i); GSVector4 scale = GSVector4(tex[i]->GetScale()).xyxy(); - src[i] = GSVector4(r) * scale / GSVector4(tex[i]->GetSize()).xyxy(); - src_hw[i] = (GSVector4(r) + GSVector4(0, y_offset[i], 0, y_offset[i])) * scale / GSVector4(tex[i]->GetSize()).xyxy(); + bool ignore_offset = !GSConfig.PCRTCOffsets; - GSVector2 off(0); + GSVector2i off(ignore_offset ? 0 : GetResolutionOffset(i)); GSVector2i display_diff(dr[i].left - display_baseline.x, dr[i].top - display_baseline.y); GSVector2i frame_diff(fr[i].left - frame_baseline.x, fr[i].top - frame_baseline.y); - // Don't apply offset when blending with background color - // The Suffering - video monitors (feedback write) - if (!slbg) + const GSVideoMode videomode = GetVideoMode(); + + const int interlace_offset = display_diff.y & 1; + + if (m_scanmask_used && interlace_offset) { - // Time Crisis 2/3 uses two side by side images when in split screen mode. - // Though ignore cases where baseline and display rectangle offsets only differ by 1 pixel, causes blurring and wrong resolution output on FFXII - if (display_diff.x > 2) - { - off.x = tex[i]->GetScale().x * display_diff.x; - } - // If the DX offset is too small then consider the status of frame memory offsets, prevents blurring on Tenchu: Fatal Shadows, Worms 3D - else if (display_diff.x != frame_diff.x) - { - off.x = tex[i]->GetScale().x * frame_diff.x; - } + display_diff.y &= ~1; - if (m_scanmask_used && display_diff.y == 1) // Scanmask effect wouldn't look correct if we scale the offset - { - off.y = display_diff.y; - scanmsk_frame = false; // Scanmsk is used between the 2 merge circuits. - } - else if (display_diff.y >= 4) // Shouldn't this be >= 2? - { - off.y = tex[i]->GetScale().y * display_diff.y; + if (!ignore_offset) + off.y &= ~1; + } - if (m_regs->SMODE2.INT && m_regs->SMODE2.FFMD) + display_diff.x /= (VideoModeDividers[(int)videomode - 1].x + 1); + display_diff.y /= (VideoModeDividers[(int)videomode - 1].y + 1); + + if (!ignore_offset && display_combined.y < resolution.y && display_combined.x < resolution.x) + { + float difference[2]; + difference[0] = resolution.x / (float)display_combined.x; + difference[1] = resolution.y / (float)display_combined.y; + //DevCon.Warning("Difference x %f y %f old size x %d y %d res x %d y %d off x %d y %d", difference[0], difference[1], r.right, r.bottom, resolution.x, resolution.y, off.x, off.y); + if (difference[0] > 1.0f) + { + float difference_to_use = (difference[0] < difference[1]) ? difference[0] : difference[1]; + int width_change = (r.right * difference_to_use) - r.right; + + r.right += width_change; + off.x -= width_change >> 1; + + int height_change = (r.bottom * difference_to_use) - r.bottom; + if (height_change > 4) { - off.y /= 2; + r.bottom += height_change; + off.y -= height_change >> 1; } } - else if (display_diff.y != frame_diff.y) + // Anti blur hax + if (display_diff.x < 4) { - off.y = tex[i]->GetScale().y * frame_diff.y; + off.x -= display_diff.x; + } + + if (display_diff.y < 4) + { + off.y -= display_diff.y; } } - dst[i] = GSVector4(off).xyxy() + scale * GSVector4(r.rsize()); + else if(ignore_offset)// Stretch to fit the window + { + float difference[2]; - fs.x = std::max(fs.x, (int)(dst[i].z + 0.5f)); - fs.y = std::max(fs.y, (int)(dst[i].w + 0.5f)); + //If the picture is offset we want to make sure we don't make it bigger, so this is the only place we need to now about the offset! + difference[0] = resolution.x / (float)((display_combined.x - GetResolutionOffset(i).x) + display_diff.x); + difference[1] = resolution.y / (float)((display_combined.y - GetResolutionOffset(i).y) + display_diff.y); + + //DevCon.Warning("Difference x %f y %f old size x %d y %d res x %d y %d off x %d y %d disp diff x %d y %d comb x %d y %d res off x %d y %d", difference[0], difference[1], r.right, r.bottom, resolution.x, resolution.y, off.x, off.y, display_diff.x, display_diff.y, display_combined.x, display_combined.y, GetResolutionOffset(i).x, GetResolutionOffset(i).y); + if (difference[0] > 1.0f) + { + const float difference_to_use = difference[0]; + const int width_change = (r.right * difference_to_use) - r.right; + + r.right += width_change; + } + + const float difference_to_use = difference[1]; + const int height_change = (r.bottom * difference_to_use) - r.bottom; + + if (difference[1] > 1.0f && (!m_scanmask_used || height_change > 4)) + { + r.bottom += height_change; + } + // Anti blur hax + if (!slbg || !feedback_merge) + { + if (display_diff.x > 4) + off.x = display_diff.x; + + if (display_diff.y > 4) + off.y = display_diff.y; + } + + if (!slbg || !feedback_merge) + { + if (samesrc) + { + if (display_diff.x < 4 && off.x) + off.x = 0; + if (display_diff.y < 4) + off.y = 0; + + if (display_diff.x > 4) + off.x = display_diff.x; + + if (display_diff.y > 4) + off.y = display_diff.y; + + if (frame_diff.x == 1) + off.x += 1; + if (frame_diff.y == 1) + off.y += 1; + } + else + { + if (display_diff.x > 4) + off.x = display_diff.x; + + if (display_diff.y > 4) + off.y = display_diff.y; + } + } + } + // Anti blur hax + else if (samesrc) + { + if (display_diff.x < 4) + off.x -= display_diff.x; + if (display_diff.y < 4) + off.y -= display_diff.y; + + if (frame_diff.x == 1) + off.x += 1; + if (frame_diff.y == 1) + off.y += 1; + } + + // Src is the size for output + src[i] = GSVector4(r) * scale / GSVector4(tex[i]->GetSize()).xyxy(); + // Src_hw is the size which we're really reading + src_hw[i] = (GSVector4(fr[i]) + GSVector4(0, y_offset[i], 0, y_offset[i])) * scale / GSVector4(tex[i]->GetSize()).xyxy(); + + dst[i] = scale * (GSVector4(off).xyxy() + GSVector4(r.rsize())); + + if (m_scanmask_used && interlace_offset) + dst[i] += GSVector4(0.0f, 1.0f, 0.0f, 1.0f); + //DevCon.Warning("Offset final x %d y %d Display x %d y %d Frame x %d y %d rect x %d y %d z %d w %d", off.x, off.y, display_diff.x, display_diff.x, frame_diff.y, frame_diff.y, r.x, r.y, r.z, r.w); } + if (feedback_merge && tex[2]) + { + GSVector4 scale = GSVector4(tex[2]->GetScale()).xyxy(); + GSVector4i feedback_rect; + + feedback_rect.left = m_regs->EXTBUF.WDX; + feedback_rect.right = feedback_rect.left + ((m_regs->EXTDATA.WW + 1) / ((m_regs->EXTDATA.SMPH - m_regs->DISP[m_regs->EXTBUF.FBIN].DISPLAY.MAGH) + 1)); + feedback_rect.top = m_regs->EXTBUF.WDY; + feedback_rect.bottom = ((m_regs->EXTDATA.WH + 1) * (2 - m_regs->EXTBUF.WFFMD)) / ((m_regs->EXTDATA.SMPV - m_regs->DISP[m_regs->EXTBUF.FBIN].DISPLAY.MAGV) + 1); + + dst[2] = GSVector4(scale * GSVector4(feedback_rect.rsize())); + } + + fs = GetResolution() * GSVector2i(GetUpscaleMultiplier()); + //DevCon.Warning("Res x %d y %d", fs.x, fs.y); ds = fs; + m_real_size = ds; if (m_regs->SMODE2.INT && m_regs->SMODE2.FFMD) { ds.y *= 2; } - m_real_size = ds; - if (tex[0] || tex[1]) { - if (tex[0] == tex[1] && !slbg && (src[0] == src[1] & dst[0] == dst[1]).alltrue()) + if (tex[0] == tex[1] && !slbg && (src[0] == src[1] & dst[0] == dst[1]).alltrue() && !feedback_merge) { // the two outputs are identical, skip drawing one of them (the one that is alpha blended) @@ -266,7 +380,7 @@ bool GSRenderer::Merge(int field) if (m_regs->SMODE2.INT && GSConfig.InterlaceMode != GSInterlaceMode::Off) { - const bool scanmask = (m_scanmask_used && scanmsk_frame) && GSConfig.InterlaceMode == GSInterlaceMode::Automatic; + const bool scanmask = m_scanmask_used && GSConfig.InterlaceMode == GSInterlaceMode::Automatic; if (GSConfig.InterlaceMode == GSInterlaceMode::Automatic && (m_regs->SMODE2.FFMD)) // Auto interlace enabled / Odd frame interlace setting { @@ -276,9 +390,9 @@ bool GSRenderer::Merge(int field) } else { - const int field2 = scanmask ? 0 : 1 - ((static_cast(GSConfig.InterlaceMode) - 1) & 1); + const int field2 = scanmask ? 0 : 0 - ((static_cast(GSConfig.InterlaceMode) - 1) & 1); const int offset = tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y; - int mode = scanmask ? 2 : (static_cast(GSConfig.InterlaceMode) - 1) >> 1; + int mode = scanmask ? 2 : ((static_cast(GSConfig.InterlaceMode) - 1) >> 1); g_gs_device->Interlace(ds, field ^ field2, mode, offset); } diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index b3d500af3c..79b7c39345 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -715,13 +715,13 @@ 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, dRect[1], ShaderConvert::COPY); + StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY); } // Save 2nd output if (feedback_write_2) { - StretchRect(dTex, full_r, sTex[2], dRect[1], m_convert.ps[static_cast(ShaderConvert::YUV)].get(), + StretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast(ShaderConvert::YUV)].get(), m_merge.cb.get(), nullptr, true); } @@ -737,7 +737,7 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, if (feedback_write_1) { - StretchRect(dTex, full_r, sTex[2], dRect[0], m_convert.ps[static_cast(ShaderConvert::YUV)].get(), + StretchRect(sTex[0], full_r, sTex[2], dRect[2], m_convert.ps[static_cast(ShaderConvert::YUV)].get(), m_merge.cb.get(), nullptr, true); } } diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index b67bcc2620..23a6eb067c 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -256,7 +256,7 @@ GSTexture* GSRendererHW::GetFeedbackOutput() TEX0.TBW = m_regs->EXTBUF.EXBW; TEX0.PSM = m_regs->DISP[m_regs->EXTBUF.FBIN & 1].DISPFB.PSM; - GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, GetTargetSize(), /*GetFrameRect(i).bottom*/ 0); + GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, GetTargetSize(), /*GetFrameRect(i).bottom*/ m_regs->DISP[m_regs->EXTBUF.FBIN & 1].DISPLAY.DH); GSTexture* t = rt->m_texture; diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index 6ffb4c87c3..dfb4e08472 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -1325,7 +1325,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, dRect[1], ShaderConvert::COPY); + StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY); } // Upload constant to select YUV algo @@ -1337,8 +1337,8 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, } // Save 2nd output - if (feedback_write_2) // FIXME I'm not sure dRect[1] is always correct - StretchRect(dTex, full_r, sTex[2], dRect[1], ShaderConvert::YUV); + if (feedback_write_2) + StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV); // Restore background color to process the normal merge if (feedback_write_2_but_blend_bg) @@ -1364,8 +1364,8 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, } } - if (feedback_write_1) // FIXME I'm not sure dRect[0] is always correct - StretchRect(dTex, full_r, sTex[2], dRect[0], ShaderConvert::YUV); + if (feedback_write_1) + StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV); } void GSDeviceOGL::DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset) diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.h b/pcsx2/GS/Renderers/SW/GSRendererSW.h index 7b60a9fe8e..1923af569f 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.h +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.h @@ -71,7 +71,7 @@ protected: IRasterizer* m_rl; GSRingHeap m_vertex_heap; GSTextureCacheSW* m_tc; - GSTexture* m_texture[2]; + GSTexture* m_texture[3]; u8* m_output; GSPixelOffset4* m_fzb; GSVector4i m_fzb_bbox; diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 1bf6002155..9b84d939ef 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -763,7 +763,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, SetUtilityTexture(sTex[1], m_linear_sampler); BeginClearRenderPass(m_utility_color_render_pass_clear, darea, c); SetPipeline(m_convert[static_cast(ShaderConvert::COPY)]); - DrawStretchRect(sRect[1], dRect[1], dsize); + DrawStretchRect(sRect[1], PMODE.SLBG ? dRect[2] : dRect[1], dsize); dTex->SetState(GSTexture::State::Dirty); dcleared = true; } @@ -772,20 +772,19 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, // Upload constant to select YUV algo const GSVector2i fbsize(sTex[2] ? sTex[2]->GetSize() : GSVector2i(0, 0)); const GSVector4i fbarea(0, 0, fbsize.x, fbsize.y); - if (feedback_write_2)// FIXME I'm not sure dRect[1] is always correct + if (feedback_write_2) { EndRenderPass(); OMSetRenderTargets(sTex[2], nullptr, fbarea, false); if (dcleared) SetUtilityTexture(dTex, m_linear_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[1])); + BeginRenderPassForStretchRect(static_cast(sTex[2]), fbarea, GSVector4i(dRect[2])); if (dcleared) { SetPipeline(m_convert[static_cast(ShaderConvert::YUV)]); SetUtilityPushConstants(yuv_constants, sizeof(yuv_constants)); - DrawStretchRect(full_r, dRect[1], fbsize); + DrawStretchRect(full_r, dRect[2], fbsize); } EndRenderPass(); @@ -818,7 +817,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, DrawStretchRect(sRect[0], dRect[0], dTex->GetSize()); } - if (feedback_write_1) // FIXME I'm not sure dRect[0] is always correct + if (feedback_write_1) { EndRenderPass(); SetPipeline(m_convert[static_cast(ShaderConvert::YUV)]); @@ -826,7 +825,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, SetUtilityPushConstants(yuv_constants, sizeof(yuv_constants)); OMSetRenderTargets(sTex[2], nullptr, fbarea, false); BeginRenderPass(m_utility_color_render_pass_load, fbarea); - DrawStretchRect(full_r, dRect[0], dsize); + DrawStretchRect(full_r, dRect[2], dsize); } EndRenderPass(); diff --git a/pcsx2/GS/Window/GSSetting.cpp b/pcsx2/GS/Window/GSSetting.cpp index b4213a3f65..6f2151631f 100644 --- a/pcsx2/GS/Window/GSSetting.cpp +++ b/pcsx2/GS/Window/GSSetting.cpp @@ -89,6 +89,10 @@ const char* dialog_message(int ID, bool* updateText) return cvtString("Enabled: GPU converts colormap-textures.\n" "Disabled: CPU converts colormap-textures.\n\n" "It is a trade-off between GPU and CPU."); + case IDC_PCRTC_OFFSETS: + return cvtString("Enable: Takes in to account offsets in the analogue circuits.\n" + "This will use the intended aspect ratios and screen offsets, may cause odd black borders.\n" + "Used for screen positioning and screen shake in Wipeout Fusion."); case IDC_ACCURATE_DATE: return cvtString("Implement a more accurate algorithm to compute GS destination alpha testing.\n" "It improves shadow and transparency rendering.\n\n" diff --git a/pcsx2/GS/Window/GSSetting.h b/pcsx2/GS/Window/GSSetting.h index 96c54133e0..0c2de522a7 100644 --- a/pcsx2/GS/Window/GSSetting.h +++ b/pcsx2/GS/Window/GSSetting.h @@ -42,6 +42,7 @@ enum { // Renderer IDC_FILTER, + IDC_PCRTC_OFFSETS, // Hardware Renderer IDC_PRELOAD_TEXTURES, IDC_ACCURATE_DATE, diff --git a/pcsx2/GS/Window/GSwxDialog.cpp b/pcsx2/GS/Window/GSwxDialog.cpp index 9c93288016..7534982121 100644 --- a/pcsx2/GS/Window/GSwxDialog.cpp +++ b/pcsx2/GS/Window/GSwxDialog.cpp @@ -276,6 +276,7 @@ RendererTab::RendererTab(wxWindow* parent) auto upscale_prereq = [this]{ return !m_is_native_res; }; PaddedBoxSizer tab_box(wxVERTICAL); + PaddedBoxSizer general_box(wxVERTICAL, this, "General GS Settings"); PaddedBoxSizer hardware_box(wxVERTICAL, this, "Hardware Mode"); PaddedBoxSizer software_box(wxVERTICAL, this, "Software Mode"); @@ -315,8 +316,16 @@ RendererTab::RendererTab(wxWindow* parent) m_ui.addSpinAndLabel(thread_box, "Extra Rendering threads:", "extrathreads", 0, 32, 2, IDC_SWTHREADS, sw_prereq); software_box->Add(thread_box, wxSizerFlags().Centre()); + // General GS Settings box + auto* pcrtc_checks_box = new wxWrapSizer(wxHORIZONTAL); + + m_ui.addCheckBox(pcrtc_checks_box, "Screen Offsets", "pcrtc_offsets", IDC_PCRTC_OFFSETS); + + general_box->Add(pcrtc_checks_box, wxSizerFlags().Center()); + tab_box->Add(hardware_box.outer, wxSizerFlags().Expand()); tab_box->Add(software_box.outer, wxSizerFlags().Expand()); + tab_box->Add(general_box.outer, wxSizerFlags().Expand()); SetSizerAndFit(tab_box.outer); } diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index a5c1fac5ea..f9c9185a2a 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -292,6 +292,7 @@ Pcsx2Config::GSOptions::GSOptions() { bitset = 0; + PCRTCOffsets = false; IntegerScaling = false; LinearPresent = true; UseDebugDevice = false; @@ -501,6 +502,8 @@ void Pcsx2Config::GSOptions::ReloadIniSettings() // Unfortunately, because code in the GS still reads the setting by key instead of // using these variables, we need to use the old names. Maybe post 2.0 we can change this. + + GSSettingBoolEx(PCRTCOffsets, "pcrtc_offsets"); GSSettingBool(IntegerScaling); GSSettingBoolEx(LinearPresent, "linear_present"); GSSettingBool(UseDebugDevice);