mirror of https://github.com/PCSX2/pcsx2.git
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:
parent
7ad59a7af1
commit
48fd68ca87
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -422,6 +422,7 @@ struct Pcsx2Config
|
|||
struct
|
||||
{
|
||||
bool
|
||||
PCRTCOffsets : 1,
|
||||
IntegerScaling : 1,
|
||||
LinearPresent : 1,
|
||||
UseDebugDevice : 1,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -42,6 +42,7 @@ enum
|
|||
{
|
||||
// Renderer
|
||||
IDC_FILTER,
|
||||
IDC_PCRTC_OFFSETS,
|
||||
// Hardware Renderer
|
||||
IDC_PRELOAD_TEXTURES,
|
||||
IDC_ACCURATE_DATE,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue