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);