GS: Implement PCRTC Offsets

This is off by default because people will complain about black borders, but I've tried to keep it to a minimum.

Enabling this option (Screen Offsets in the graphics settings) will allow you to position the screen in games which allow you to do so, maintain correct aspect ratios, and screen shake effects which are done on PCRTC (WipEout Fusion for example) will work.
This commit is contained in:
refractionpcsx2 2022-04-10 15:30:55 +01:00
parent 7ad59a7af1
commit 48fd68ca87
16 changed files with 323 additions and 129 deletions

View File

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

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>747</width>
<width>566</width>
<height>890</height>
</rect>
</property>
@ -261,34 +261,41 @@
</item>
<item row="7" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="2">
<widget class="QCheckBox" name="integerScaling">
<property name="text">
<string>Integer Upscaling</string>
</property>
</widget>
</item>
<item row="0" column="1">
<item row="0" column="0">
<widget class="QCheckBox" name="bilinearFiltering">
<property name="text">
<string>Bilinear Filtering</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="1" column="0">
<widget class="QCheckBox" name="vsync">
<property name="text">
<string>Sync To Host Refresh (VSync)</string>
</property>
</widget>
</item>
<item row="1" column="2">
<item row="1" column="1">
<widget class="QCheckBox" name="internalResolutionScreenshots">
<property name="text">
<string>Internal Resolution Screenshots</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="integerScaling">
<property name="text">
<string>Integer Upscaling</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="PCRTCOffsets">
<property name="text">
<string>Screen Offsets</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@ -422,6 +422,7 @@ struct Pcsx2Config
struct
{
bool
PCRTCOffsets : 1,
IntegerScaling : 1,
LinearPresent : 1,
UseDebugDevice : 1,

View File

@ -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";

View File

@ -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;
}

View File

@ -258,6 +258,29 @@ public:
PRIM_OVERLAP m_prim_overlap;
std::vector<size_t> 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();

View File

@ -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<int>(GSConfig.InterlaceMode) - 1) & 1);
const int field2 = scanmask ? 0 : 0 - ((static_cast<int>(GSConfig.InterlaceMode) - 1) & 1);
const int offset = tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y;
int mode = scanmask ? 2 : (static_cast<int>(GSConfig.InterlaceMode) - 1) >> 1;
int mode = scanmask ? 2 : ((static_cast<int>(GSConfig.InterlaceMode) - 1) >> 1);
g_gs_device->Interlace(ds, field ^ field2, mode, offset);
}

View File

@ -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<int>(ShaderConvert::YUV)].get(),
StretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(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<int>(ShaderConvert::YUV)].get(),
StretchRect(sTex[0], full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
m_merge.cb.get(), nullptr, true);
}
}

View File

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

View File

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

View File

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

View File

@ -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<int>(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<GSTextureVK*>(sTex[2]), fbarea, GSVector4i(dRect[1]));
BeginRenderPassForStretchRect(static_cast<GSTextureVK*>(sTex[2]), fbarea, GSVector4i(dRect[2]));
if (dcleared)
{
SetPipeline(m_convert[static_cast<int>(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<int>(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();

View File

@ -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"

View File

@ -42,6 +42,7 @@ enum
{
// Renderer
IDC_FILTER,
IDC_PCRTC_OFFSETS,
// Hardware Renderer
IDC_PRELOAD_TEXTURES,
IDC_ACCURATE_DATE,

View File

@ -276,6 +276,7 @@ RendererTab::RendererTab(wxWindow* parent)
auto upscale_prereq = [this]{ return !m_is_native_res; };
PaddedBoxSizer<wxBoxSizer> tab_box(wxVERTICAL);
PaddedBoxSizer<wxStaticBoxSizer> general_box(wxVERTICAL, this, "General GS Settings");
PaddedBoxSizer<wxStaticBoxSizer> hardware_box(wxVERTICAL, this, "Hardware Mode");
PaddedBoxSizer<wxStaticBoxSizer> 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);
}

View File

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