GS: Rework of PCRTC code.

This commit is contained in:
refractionpcsx2 2022-12-17 11:05:32 +00:00
parent 26e691ba93
commit 71d0bbbc25
11 changed files with 628 additions and 678 deletions

View File

@ -232,6 +232,7 @@ static bool DoGSOpen(GSRendererType renderer, u8* basemem)
}
g_gs_renderer->SetRegsMem(basemem);
g_gs_renderer->ResetPCRTC();
}
else
{

View File

@ -285,6 +285,14 @@ void GSState::ResetHandlers()
m_fpGIFRegHandlers[GIF_A_D_REG_LABEL] = &GSState::GIFRegHandlerNull;
}
void GSState::ResetPCRTC()
{
PCRTCDisplays.SetVideoMode(GetVideoMode());
PCRTCDisplays.EnableDisplays(m_regs->PMODE, m_regs->SMODE2, isReallyInterlaced());
PCRTCDisplays.SetRects(0, m_regs->DISP[0].DISPLAY, m_regs->DISP[0].DISPFB);
PCRTCDisplays.SetRects(1, m_regs->DISP[1].DISPLAY, m_regs->DISP[1].DISPFB);
}
void GSState::UpdateSettings(const Pcsx2Config::GSOptions& old_config)
{
m_mipmap = GSConfig.Mipmap;
@ -340,264 +348,6 @@ GSVideoMode GSState::GetVideoMode()
__assume(0); // unreachable
}
bool GSState::IsAnalogue()
{
return GetVideoMode() == GSVideoMode::NTSC || GetVideoMode() == GSVideoMode::PAL || GetVideoMode() == GSVideoMode::HDTV_1080I;
}
GSVector4i GSState::GetFrameMagnifiedRect(int i)
{
GSVector4i rectangle = { 0, 0, 0, 0 };
if (!IsEnabled(i))
return rectangle;
const int videomode = static_cast<int>(GetVideoMode()) - 1;
const auto& DISP = m_regs->DISP[i].DISPLAY;
const bool ignore_offset = !GSConfig.PCRTCOffsets;
const u32 DW = DISP.DW + 1;
const u32 DH = DISP.DH + 1;
;
// The number of sub pixels to draw are given in DH and DW, the MAGH/V relates to the size of the original square in the FB
// but the size it's drawn to uses the default size of the display mode (for PAL/NTSC this is a MAGH of 3)
// so for example a game will have a DW of 2559 and a MAGH of 4 to make it 512 (from the FB), but because it's drawing 2560 subpixels
// it will cover the entire 640 wide of the screen (2560 / (3+1)).
int width;
int height;
if (ignore_offset)
{
width = (DW / (DISP.MAGH + 1));
height = (DH / (DISP.MAGV + 1));
}
else
{
width = (DW / (VideoModeDividers[videomode].x + 1));
height = (DH / (VideoModeDividers[videomode].y + 1));
}
int res_multi = 1;
if (isinterlaced() && m_regs->SMODE2.FFMD && height > 1)
res_multi = 2;
// Set up the display rectangle based on the values obtained from DISPLAY registers
rectangle.right = width;
rectangle.bottom = height / res_multi;
return rectangle;
}
int GSState::GetDisplayHMagnification()
{
// Pick one of the DISPLAY's and hope that they are both the same. Favour DISPLAY[1]
for (int i = 1; i >= 0; i--)
{
if (IsEnabled(i))
return m_regs->DISP[i].DISPLAY.MAGH + 1;
}
// If neither DISPLAY is enabled, fallback to resolution offset (should never happen)
const int videomode = static_cast<int>(GetVideoMode()) - 1;
return VideoModeDividers[videomode].x + 1;
}
GSVector4i GSState::GetDisplayRect(int i)
{
GSVector4i rectangle = { 0, 0, 0, 0 };
if (i == -1)
{
return GetDisplayRect(0).runion(GetDisplayRect(1));
}
if (!IsEnabled(i))
return rectangle;
const auto& DISP = m_regs->DISP[i].DISPLAY;
const u32 DW = DISP.DW + 1;
const u32 DH = DISP.DH + 1;
const u32 MAGH = DISP.MAGH + 1;
const u32 MAGV = DISP.MAGV + 1;
const GSVector2i magnification(MAGH, MAGV);
const int width = DW / magnification.x;
const int height = DH / magnification.y;
const GSVector2i offsets = GetResolutionOffset(i);
// Set up the display rectangle based on the values obtained from DISPLAY registers
rectangle.left = offsets.x;
rectangle.top = offsets.y;
rectangle.right = rectangle.left + width;
rectangle.bottom = rectangle.top + height;
return rectangle;
}
GSVector2i GSState::GetResolutionOffset(int i)
{
GSVector2i offset = { 0, 0 };
if (!IsEnabled(i))
return offset;
const int videomode = static_cast<int>(GetVideoMode()) - 1;
const auto& DISP = m_regs->DISP[i].DISPLAY;
const auto& SMODE2 = m_regs->SMODE2;
const int res_multi = (SMODE2.INT + 1);
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
offset.x = (static_cast<int>(DISP.DX) - offsets.z) / (VideoModeDividers[videomode].x + 1);
offset.y = (static_cast<int>(DISP.DY) - (offsets.w * ((IsAnalogue() && res_multi) ? res_multi : 1))) / (VideoModeDividers[videomode].y + 1);
return offset;
}
GSVector2i GSState::GetResolution()
{
const int videomode = static_cast<int>(GetVideoMode()) - 1;
const bool ignore_offset = !GSConfig.PCRTCOffsets;
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
GSVector2i resolution(offsets.x, offsets.y);
// The resolution of the framebuffer is double when in FRAME mode and interlaced.
// Also we need a special check because no-interlace patches like to render in the original height, but in non-interlaced mode
// which means it would normally go off the bottom of the screen. Advantages of emulation, i guess... Limited to Ignore Offsets + Deinterlacing = Off.
if ((isinterlaced() && !m_regs->SMODE2.FFMD) || (GSConfig.InterlaceMode == GSInterlaceMode::Off && !GSConfig.PCRTCOffsets && !isinterlaced()))
resolution.y *= 2;
if (ignore_offset)
{
// Ideally we'd just cut the width at the resolution, but of course we have to hack the hack...
// Some games (Mortal Kombat Armageddon) render the image at 834 pixels then shrink it to 624 pixels
// which does fit, but when we ignore offsets we go on framebuffer size and some other games
// such as Johnny Mosleys Mad Trix and Transformers render too much but design it to go off the screen.
int magnified_width = (VideoModeDividers[videomode].z + 1) / GetDisplayHMagnification();
// When viewing overscan allow up to the overscan size
if (GSConfig.PCRTCOverscan)
magnified_width = std::max(magnified_width, offsets.x);
GSVector4i total_rect = GetDisplayRect(0).runion(GetDisplayRect(1));
total_rect.z = total_rect.z - total_rect.x;
total_rect.w = total_rect.w - total_rect.y;
total_rect.z = std::min(total_rect.z, magnified_width);
total_rect.w = std::min(total_rect.w, resolution.y);
resolution.x = total_rect.z;
resolution.y = total_rect.w;
}
return resolution;
}
GSVector4i GSState::GetFrameRect(int i, bool ignore_off)
{
// If no specific context is requested then pass the merged rectangle as return value
if (i == -1)
return GetFrameRect(0, ignore_off).runion(GetFrameRect(1, ignore_off));
GSVector4i rectangle = { 0, 0, 0, 0 };
if (!IsEnabled(i))
return rectangle;
const auto& DISP = m_regs->DISP[i].DISPLAY;
const u32 DW = DISP.DW + 1;
const u32 DH = DISP.DH + 1;
const GSVector2i magnification(DISP.MAGH+1, DISP.MAGV + 1);
const u32 DBX = m_regs->DISP[i].DISPFB.DBX;
int DBY = m_regs->DISP[i].DISPFB.DBY;
const int w = DW / magnification.x;
const int h = DH / magnification.y;
// If the combined height overflows 2048, it's likely adding a bit of extra data before the picture for offsetting the interlace
// only game known to do this is NASCAR '08
if (!ignore_off && (DBY + h) >= 2048)
DBY = DBY - 2048;
rectangle.left = (ignore_off) ? 0 : DBX;
rectangle.top = (ignore_off) ? 0 : DBY;
rectangle.right = rectangle.left + w;
rectangle.bottom = rectangle.top + h;
if (isinterlaced() && m_regs->SMODE2.FFMD && h > 1)
{
rectangle.bottom += 1;
rectangle.bottom >>= 1;
}
return rectangle;
}
int GSState::GetFramebufferHeight()
{
// Framebuffer height is 11 bits max
constexpr int height_limit = (1 << 11);
const GSVector4i disp1_rect = GetFrameRect(0, true);
const GSVector4i disp2_rect = GetFrameRect(1, true);
const GSVector4i combined = disp1_rect.runion(disp2_rect);
// DBY isn't an offset to the frame memory but rather an offset to read output circuit inside
// the frame memory, hence the top offset should also be calculated for the total height of the
// frame memory. Also we need to wrap the value only when we're dealing with values with range of the
// frame memory (offset + read output circuit height, IOW bottom of merged_output)
const int max_height = std::max(disp1_rect.height(), disp2_rect.height());
const int frame_memory_height = std::max(max_height, combined.bottom % height_limit);
if (frame_memory_height > 1024)
GL_PERF("Massive framebuffer height detected! (height:%d)", frame_memory_height);
return frame_memory_height;
}
int GSState::GetFramebufferBitDepth()
{
if (IsEnabled(0))
return GSLocalMemory::m_psm[m_regs->DISP[0].DISPFB.PSM].bpp;
else if (IsEnabled(1))
return GSLocalMemory::m_psm[m_regs->DISP[1].DISPFB.PSM].bpp;
return 32;
}
int GSState::GetFramebufferWidth()
{
const GSVector4i disp1_rect = GetFrameRect(0, true);
const GSVector4i disp2_rect = GetFrameRect(1, true);
const int max_width = std::max(disp1_rect.width(), disp2_rect.width());
return max_width;
}
bool GSState::IsEnabled(int i)
{
ASSERT(i >= 0 && i < 2);
const auto& DISP = m_regs->DISP[i].DISPLAY;
const bool disp1_enabled = m_regs->PMODE.EN1;
const bool disp2_enabled = m_regs->PMODE.EN2;
if ((i == 0 && disp1_enabled) || (i == 1 && disp2_enabled))
return DISP.DW && DISP.DH;
return false;
}
float GSState::GetTvRefreshRate()
{
const GSVideoMode videomode = GetVideoMode();
@ -2775,6 +2525,8 @@ int GSState::Defrost(const freezeData* fd)
g_perfmon.SetFrame(5000);
ResetPCRTC();
return 0;
}

View File

@ -290,6 +290,8 @@ public:
PRIM_OVERLAP m_prim_overlap;
std::vector<size_t> m_drawlist;
struct GSPCRTCRegs
{
// The horizontal offset values (under z) for PAL and NTSC have been tweaked
// they should be apparently 632 and 652 respectively, but that causes a thick black line on the left
// these values leave a small black line on the right in a bunch of games, but it's not so bad.
@ -322,27 +324,473 @@ public:
GSVector4i(0, 0, 1919, 1079)
};
struct PCRTCDisplay
{
bool enabled;
int FBP;
int FBW;
int PSM;
GSVector2i prevDisplayOffset;
GSVector2i displayOffset;
GSVector4i displayRect;
GSVector2i magnification;
GSVector2i prevFramebufferOffsets;
GSVector2i framebufferOffsets;
GSVector4i framebufferRect;
int Block()
{
return FBP << 5;
}
};
int videomode;
int interlaced;
int FFMD;
bool PCRTCSameSrc;
bool toggling_field;
PCRTCDisplay PCRTCDisplays[2];
bool IsAnalogue()
{
GSVideoMode video = static_cast<GSVideoMode>(videomode + 1);
return video == GSVideoMode::NTSC || video == GSVideoMode::PAL || video == GSVideoMode::HDTV_1080I;
}
// Calculates which display is closest to matching zero offsets in either direction.
GSVector2i NearestToZeroOffset()
{
GSVector2i returnValue = { 1, 1 };
if (!PCRTCDisplays[0].enabled && !PCRTCDisplays[1].enabled)
return returnValue;
for (int i = 0; i < 2; i++)
{
if (!PCRTCDisplays[i].enabled)
{
returnValue.x = 1 - i;
returnValue.y = 1 - i;
return returnValue;
}
}
if (abs(PCRTCDisplays[0].displayOffset.x - VideoModeOffsets[videomode].z) <
abs(PCRTCDisplays[1].displayOffset.x - VideoModeOffsets[videomode].z))
returnValue.x = 0;
// When interlaced, the vertical base offset is doubled
int verticalOffset = VideoModeOffsets[videomode].w * (1 << interlaced);
if (abs(PCRTCDisplays[0].displayOffset.y - verticalOffset) <
abs(PCRTCDisplays[1].displayOffset.y - verticalOffset))
returnValue.y = 0;
return returnValue;
}
void SetVideoMode(GSVideoMode videoModeIn)
{
videomode = static_cast<int>(videoModeIn) - 1;
}
// Enable each of the displays.
void EnableDisplays(GSRegPMODE pmode, GSRegSMODE2 smode2, bool smodetoggle)
{
PCRTCDisplays[0].enabled = pmode.EN1;
PCRTCDisplays[1].enabled = pmode.EN2;
interlaced = smode2.INT && IsAnalogue();
FFMD = smode2.FFMD;
toggling_field = smodetoggle && IsAnalogue();
}
void CheckSameSource()
{
if (PCRTCDisplays[0].enabled != PCRTCDisplays[1].enabled || (PCRTCDisplays[0].enabled | PCRTCDisplays[1].enabled) == false)
{
PCRTCSameSrc = false;
return;
}
PCRTCSameSrc = PCRTCDisplays[0].FBP == PCRTCDisplays[1].FBP &&
PCRTCDisplays[0].FBW == PCRTCDisplays[1].FBW &&
GSUtil::HasCompatibleBits(PCRTCDisplays[0].PSM, PCRTCDisplays[1].PSM);
}
bool FrameWrap()
{
GSVector4i combined_rect = GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
return combined_rect.w >= 2048 || combined_rect.z >= 2048;
}
// If the start point of both frames match, we can do a single read
bool FrameRectMatch()
{
return PCRTCSameSrc;
}
GSVector2i GetResolution()
{
GSVector2i resolution;
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
const bool is_full_height = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off) || GSConfig.InterlaceMode == GSInterlaceMode::Off;
if (!GSConfig.PCRTCOffsets)
{
if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled)
{
GSVector4i combined_size = PCRTCDisplays[0].displayRect.runion(PCRTCDisplays[1].displayRect);
resolution = { combined_size.width(), combined_size.height() };
}
else if (PCRTCDisplays[0].enabled)
{
resolution = { PCRTCDisplays[0].displayRect.width(), PCRTCDisplays[0].displayRect.height() };
}
else
{
resolution = { PCRTCDisplays[1].displayRect.width(), PCRTCDisplays[1].displayRect.height() };
}
}
else
{
const int shift = is_full_height ? 1 : 0;
resolution = { offsets.x, offsets.y << shift };
}
resolution.x = std::min(resolution.x, offsets.x);
resolution.y = std::min(resolution.y, is_full_height ? offsets.y << 1 : offsets.y);
return resolution;
}
GSVector4i GetFramebufferRect(int display)
{
if (display == -1)
{
return GSVector4i(PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect));
}
else
{
return PCRTCDisplays[display].framebufferRect;
}
}
int GetFramebufferBitDepth()
{
if (PCRTCDisplays[0].enabled)
return GSLocalMemory::m_psm[PCRTCDisplays[0].PSM].bpp;
else if (PCRTCDisplays[1].enabled)
return GSLocalMemory::m_psm[PCRTCDisplays[1].PSM].bpp;
return 32;
}
GSVector2i GetFramebufferSize(int display)
{
if (display == -1)
{
GSVector4i combined_rect = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
if (combined_rect.z >= 2048)
{
int high_x = (PCRTCDisplays[0].framebufferRect.x > PCRTCDisplays[1].framebufferRect.x) ? PCRTCDisplays[0].framebufferRect.x : PCRTCDisplays[1].framebufferRect.x;
combined_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : high_x;
combined_rect.x = 0;
}
if (combined_rect.w >= 2048)
{
int high_y = (PCRTCDisplays[0].framebufferRect.y > PCRTCDisplays[1].framebufferRect.y) ? PCRTCDisplays[0].framebufferRect.y : PCRTCDisplays[1].framebufferRect.y;
combined_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : high_y;
combined_rect.y = 0;
}
return GSVector2i(combined_rect.z, combined_rect.w);
}
else
{
GSVector4i out_rect = PCRTCDisplays[display].framebufferRect;
if (out_rect.z >= 2048)
{
out_rect.z -= GSConfig.UseHardwareRenderer() ? 2048 : out_rect.x;
out_rect.x = 0;
}
if (out_rect.w >= 2048)
{
out_rect.w -= GSConfig.UseHardwareRenderer() ? 2048 : out_rect.y;
out_rect.y = 0;
}
return GSVector2i(out_rect.z, out_rect.w);
}
}
// Sets up the rectangles for both the framebuffer read and the displays for the merge circuit.
void SetRects(int display, GSRegDISPLAY displayReg, GSRegDISPFB framebufferReg)
{
// Save framebuffer information first, while we're here.
PCRTCDisplays[display].FBP = framebufferReg.FBP;
PCRTCDisplays[display].FBW = framebufferReg.FBW;
PCRTCDisplays[display].PSM = framebufferReg.PSM;
// Probably not really enabled but will cause a mess.
// Q-Ball Billiards enables both circuits but doesn't set one of them up.
if (PCRTCDisplays[display].FBW == 0 && displayReg.DW == 0 && displayReg.DH == 0 && displayReg.MAGH == 0)
{
PCRTCDisplays[display].enabled = false;
return;
}
PCRTCDisplays[display].magnification = GSVector2i(displayReg.MAGH + 1, displayReg.MAGV + 1);
const u32 DW = displayReg.DW + 1;
const u32 DH = displayReg.DH + 1;
const int renderWidth = DW / PCRTCDisplays[display].magnification.x;
const int renderHeight = DH / PCRTCDisplays[display].magnification.y;
u32 finalDisplayWidth = renderWidth;
u32 finalDisplayHeight = renderHeight;
// When using screen offsets the screen gets squashed/resized in to the actual screen size.
if (GSConfig.PCRTCOffsets)
{
finalDisplayWidth = DW / (VideoModeDividers[videomode].x + 1);
finalDisplayHeight = DH / (VideoModeDividers[videomode].y + 1);
}
else
{
finalDisplayWidth = std::min(finalDisplayWidth ,DW / (VideoModeDividers[videomode].x + 1));
finalDisplayHeight = std::min(finalDisplayHeight, DH / (VideoModeDividers[videomode].y + 1));
}
// Framebuffer size and offsets.
PCRTCDisplays[display].prevFramebufferOffsets = PCRTCDisplays[display].framebufferOffsets;
PCRTCDisplays[display].framebufferRect.x = 0;
PCRTCDisplays[display].framebufferRect.y = 0;
PCRTCDisplays[display].framebufferRect.z = renderWidth;
if(FFMD && interlaced) // Round up the height as if it's an odd value, this will cause havok with the merge circuit.
PCRTCDisplays[display].framebufferRect.w = (renderHeight + 1) >> (FFMD * interlaced); // Half height read if FFMD + INT enabled.
else
PCRTCDisplays[display].framebufferRect.w = renderHeight;
PCRTCDisplays[display].framebufferOffsets.x = framebufferReg.DBX;
PCRTCDisplays[display].framebufferOffsets.y = framebufferReg.DBY;
const bool is_interlaced_resolution = interlaced || (toggling_field && GSConfig.InterlaceMode != GSInterlaceMode::Off);
// If the interlace flag isn't set, but it's still interlacing, the height is likely reported wrong.
// Q-Ball Billiards.
if (is_interlaced_resolution && !interlaced)
finalDisplayHeight *= 2;
// Display size and offsets.
PCRTCDisplays[display].displayRect.x = 0;
PCRTCDisplays[display].displayRect.y = 0;
PCRTCDisplays[display].displayRect.z = finalDisplayWidth;
PCRTCDisplays[display].displayRect.w = finalDisplayHeight;
PCRTCDisplays[display].prevDisplayOffset = PCRTCDisplays[display].displayOffset;
PCRTCDisplays[display].displayOffset.x = displayReg.DX;
PCRTCDisplays[display].displayOffset.y = displayReg.DY;
}
// Calculate framebuffer read offsets, should be considered if only one circuit is enabled, or difference is more than 1 line.
// Only considered if "Anti-blur" is enabled.
void CalculateFramebufferOffset()
{
if (GSConfig.PCRTCAntiBlur && PCRTCDisplays[0].enabled && PCRTCSameSrc)
{
if (abs(PCRTCDisplays[1].framebufferOffsets.y - PCRTCDisplays[0].framebufferOffsets.y) == 1
&& PCRTCDisplays[0].displayOffset.y == PCRTCDisplays[1].displayOffset.y)
{
if (PCRTCDisplays[1].framebufferOffsets.y < PCRTCDisplays[0].framebufferOffsets.y)
PCRTCDisplays[0].framebufferOffsets.y = PCRTCDisplays[1].framebufferOffsets.y;
else
PCRTCDisplays[1].framebufferOffsets.y = PCRTCDisplays[0].framebufferOffsets.y;
}
}
PCRTCDisplays[0].framebufferRect.x += PCRTCDisplays[0].framebufferOffsets.x;
PCRTCDisplays[0].framebufferRect.z += PCRTCDisplays[0].framebufferOffsets.x;
PCRTCDisplays[0].framebufferRect.y += PCRTCDisplays[0].framebufferOffsets.y;
PCRTCDisplays[0].framebufferRect.w += PCRTCDisplays[0].framebufferOffsets.y;
PCRTCDisplays[1].framebufferRect.x += PCRTCDisplays[1].framebufferOffsets.x;
PCRTCDisplays[1].framebufferRect.z += PCRTCDisplays[1].framebufferOffsets.x;
PCRTCDisplays[1].framebufferRect.y += PCRTCDisplays[1].framebufferOffsets.y;
PCRTCDisplays[1].framebufferRect.w += PCRTCDisplays[1].framebufferOffsets.y;
}
// Used in software mode to align the buffer when reading. Offset is accounted for (block aligned) by GetOutput.
void RemoveFramebufferOffset(int display)
{
if (display >= 0)
{
// Hardware needs nothing but handling for wrapped framebuffers.
if (GSConfig.UseHardwareRenderer())
{
if (PCRTCDisplays[display].framebufferRect.z >= 2048)
{
PCRTCDisplays[display].framebufferRect.x = 0;
PCRTCDisplays[display].framebufferRect.z -= 2048;
}
if (PCRTCDisplays[display].framebufferRect.w >= 2048)
{
PCRTCDisplays[display].framebufferRect.y = 0;
PCRTCDisplays[display].framebufferRect.w -= 2048;
}
}
else
{
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[display].PSM];
GSVector4i r = PCRTCDisplays[display].framebufferRect;
r = r.ralign<Align_Outside>(psm.bs);
PCRTCDisplays[display].framebufferRect.z -= r.x;
PCRTCDisplays[display].framebufferRect.w -= r.y;
PCRTCDisplays[display].framebufferRect.x -= r.x;
PCRTCDisplays[display].framebufferRect.y -= r.y;
}
}
else
{
// This code is to read the framebuffer nicely block aligned in software, then leave the remaining offset in to the block.
// In hardware mode this doesn't happen, it reads the whole framebuffer, so we need to keep the offset.
if (!GSConfig.UseHardwareRenderer())
{
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[PCRTCDisplays[1].PSM];
GSVector4i r = PCRTCDisplays[0].framebufferRect.runion(PCRTCDisplays[1].framebufferRect);
r = r.ralign<Align_Outside>(psm.bs);
PCRTCDisplays[0].framebufferRect.x -= r.x;
PCRTCDisplays[0].framebufferRect.y -= r.y;
PCRTCDisplays[0].framebufferRect.z -= r.x;
PCRTCDisplays[0].framebufferRect.w -= r.y;
PCRTCDisplays[1].framebufferRect.x -= r.x;
PCRTCDisplays[1].framebufferRect.y -= r.y;
PCRTCDisplays[1].framebufferRect.z -= r.x;
PCRTCDisplays[1].framebufferRect.w -= r.y;
}
}
}
// If the two displays are offset from each other, move them to the correct offsets.
// If using screen offsets, calculate the positions here.
void CalculateDisplayOffset(bool scanmask)
{
// Offsets are generally ignored, the "hacky" way of doing the displays, but direct to framebuffers.
if (!GSConfig.PCRTCOffsets)
{
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
int int_off[2] = { 0, 0 };
GSVector2i zeroDisplay = NearestToZeroOffset();
GSVector2i baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
{
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
PCRTCDisplays[1].displayOffset.y -= blurOffset;
else
PCRTCDisplays[0].displayOffset.y -= blurOffset;
}
// If there's a single pixel offset, account for it else it can throw interlacing out.
for (int i = 0; i < 2; i++)
{
if (!PCRTCDisplays[i].enabled)
continue;
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
const int offset = (PCRTCDisplays[i].displayOffset.y - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
if (offset > 4)
continue;
int_off[i] = offset & 1;
if (offset < 0)
int_off[i] = -int_off[i];
PCRTCDisplays[i].displayRect.y += int_off[i];
PCRTCDisplays[i].displayRect.w += int_off[i];
}
// Handle difference in offset between the two displays, used in games like DmC and Time Crisis 2 (for split screen).
// Offset is not screen based, but relative to each other.
if (PCRTCDisplays[0].enabled && PCRTCDisplays[1].enabled)
{
GSVector2i offset;
offset.x = (PCRTCDisplays[1 - zeroDisplay.x].displayOffset.x - PCRTCDisplays[zeroDisplay.x].displayOffset.x) / (VideoModeDividers[videomode].x + 1);
offset.y = (PCRTCDisplays[1 - zeroDisplay.y].displayOffset.y - PCRTCDisplays[zeroDisplay.y].displayOffset.y) / (VideoModeDividers[videomode].y + 1);
if (offset.x >= 4 || !GSConfig.PCRTCAntiBlur)
{
PCRTCDisplays[1 - zeroDisplay.x].displayRect.x += offset.x;
PCRTCDisplays[1 - zeroDisplay.x].displayRect.z += offset.x;
}
if (offset.y >= 4 || !GSConfig.PCRTCAntiBlur)
{
PCRTCDisplays[1 - zeroDisplay.y].displayRect.y += offset.y - int_off[1 - zeroDisplay.y];
PCRTCDisplays[1 - zeroDisplay.y].displayRect.w += offset.y - int_off[1 - zeroDisplay.y];
}
baseOffset = PCRTCDisplays[zeroDisplay.y].displayOffset;
}
// Handle any large vertical offset from the zero position on the screen.
// Example: Hokuto no Ken, does a rougly -14 offset to bring the screen up.
// Ignore the lowest bit, we've already accounted for this
int vOffset = ((static_cast<int>(baseOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1));
if(vOffset <= 4 && vOffset != 0)
{
PCRTCDisplays[0].displayRect.y += vOffset - int_off[0];
PCRTCDisplays[0].displayRect.w += vOffset - int_off[0];
PCRTCDisplays[1].displayRect.y += vOffset - int_off[1];
PCRTCDisplays[1].displayRect.w += vOffset - int_off[1];
}
}
else // We're using screen offsets, so just calculate the entire offset.
{
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
GSVector2i offset = { 0, 0 };
int blurOffset = abs(PCRTCDisplays[1].displayOffset.y - PCRTCDisplays[0].displayOffset.y);
if (GSConfig.PCRTCAntiBlur && !scanmask && blurOffset < 4)
{
if (PCRTCDisplays[1].displayOffset.y > PCRTCDisplays[0].displayOffset.y)
PCRTCDisplays[1].displayOffset.y -= blurOffset;
else
PCRTCDisplays[0].displayOffset.y -= blurOffset;
}
for (int i = 0; i < 2; i++)
{
// Should this be MAGV/H in the DISPLAY register rather than the "default" magnification?
offset.x = (static_cast<int>(PCRTCDisplays[i].displayOffset.x) - offsets.z) / (VideoModeDividers[videomode].x + 1);
offset.y = (static_cast<int>(PCRTCDisplays[i].displayOffset.y) - (offsets.w * (interlaced + 1))) / (VideoModeDividers[videomode].y + 1);
PCRTCDisplays[i].displayRect.x += offset.x;
PCRTCDisplays[i].displayRect.z += offset.x;
PCRTCDisplays[i].displayRect.y += offset.y;
PCRTCDisplays[i].displayRect.w += offset.y;
}
}
}
} PCRTCDisplays;
public:
/// Returns the appropriate directory for draw dumping.
static std::string GetDrawDumpPath(const char* format, ...);
void ResetHandlers();
void ResetPCRTC();
int GetFramebufferHeight();
int GetFramebufferWidth();
int GetFramebufferBitDepth();
int GetDisplayHMagnification();
GSVector4i GetDisplayRect(int i = -1);
GSVector4i GetFrameMagnifiedRect(int i = -1);
GSVector2i GetResolutionOffset(int i = -1);
GSVector2i GetResolution();
GSVector4i GetFrameRect(int i = -1, bool ignore_off = false);
GSVideoMode GetVideoMode();
bool IsEnabled(int i);
bool isinterlaced();
bool isReallyInterlaced();
bool IsAnalogue();
float GetTvRefreshRate();

View File

@ -77,82 +77,37 @@ void GSRenderer::Destroy()
bool GSRenderer::Merge(int field)
{
bool en[2];
GSVector4i fr[2];
GSVector4i dr[2];
GSVector2i display_offsets[2];
GSVector2i display_baseline = {INT_MAX, INT_MAX};
GSVector2i frame_baseline = {INT_MAX, INT_MAX};
GSVector2i display_combined = {0, 0};
bool feedback_merge = m_regs->EXTWRITE.WRITE == 1;
bool display_offset = false;
for (int i = 0; i < 2; i++)
{
en[i] = IsEnabled(i) || (m_regs->EXTBUF.FBIN == i && feedback_merge);
if (en[i])
{
fr[i] = GetFrameRect(i);
dr[i] = GetDisplayRect(i);
display_offsets[i] = GetResolutionOffset(i);
display_combined.x = std::max(((dr[i].right) - dr[i].left) + display_offsets[i].x, display_combined.x);
display_combined.y = std::max((dr[i].bottom - dr[i].top) + display_offsets[i].y, display_combined.y);
display_baseline.x = std::min(display_offsets[i].x, display_baseline.x);
display_baseline.y = std::min(display_offsets[i].y, display_baseline.y);
frame_baseline.x = std::min(std::max(fr[i].left, 0), frame_baseline.x);
frame_baseline.y = std::min(std::max(fr[i].top, 0), frame_baseline.y);
display_offset |= std::abs(display_baseline.y - display_offsets[i].y) == 1;
/*DevCon.Warning("Read offset was X %d(left %d) Y %d(top %d)", display_baseline.x, dr[i].left, display_baseline.y, dr[i].top);
DevCon.Warning("[%d]: %d %d %d %d, %d %d %d %d\n", i, fr[i].x,fr[i].y,fr[i].z,fr[i].w , dr[i].x,dr[i].y,dr[i].z,dr[i].w);
DevCon.Warning("Offset X %d Offset Y %d", display_offsets[i].x, display_offsets[i].y);*/
}
}
if (!en[0] && !en[1])
{
return false;
}
GL_PUSH("Renderer Merge %d (0: enabled %d 0x%x, 1: enabled %d 0x%x)", s_n, en[0], m_regs->DISP[0].DISPFB.Block(), en[1], m_regs->DISP[1].DISPFB.Block());
// try to avoid fullscreen blur, could be nice on tv but on a monitor it's like double vision, hurts my eyes (persona 4, guitar hero)
//
// NOTE: probably the technique explained in graphtip.pdf (Antialiasing by Supersampling / 4. Reading Odd/Even Scan Lines Separately with the PCRTC then Blending)
const bool samesrc =
en[0] && en[1] &&
m_regs->DISP[0].DISPFB.FBP == m_regs->DISP[1].DISPFB.FBP &&
m_regs->DISP[0].DISPFB.FBW == m_regs->DISP[1].DISPFB.FBW &&
GSUtil::HasCompatibleBits(m_regs->DISP[0].DISPFB.PSM, m_regs->DISP[1].DISPFB.PSM);
bool single_fetch = false;
GSVector2i fs(0, 0);
GSVector2i ds(0, 0);
GSTexture* tex[3] = {NULL, NULL, NULL};
int y_offset[3] = {0, 0, 0};
s_n++;
GSVector2i fs(0, 0);
GSTexture* tex[3] = { NULL, NULL, NULL };
int y_offset[3] = { 0, 0, 0 };
bool feedback_merge = m_regs->EXTWRITE.WRITE == 1;
PCRTCDisplays.SetVideoMode(GetVideoMode());
PCRTCDisplays.EnableDisplays(m_regs->PMODE, m_regs->SMODE2, isReallyInterlaced());
if (!PCRTCDisplays.PCRTCDisplays[0].enabled && !PCRTCDisplays.PCRTCDisplays[1].enabled)
return false;
PCRTCDisplays.SetRects(0, m_regs->DISP[0].DISPLAY, m_regs->DISP[0].DISPFB);
PCRTCDisplays.SetRects(1, m_regs->DISP[1].DISPLAY, m_regs->DISP[1].DISPFB);
PCRTCDisplays.CalculateDisplayOffset(m_scanmask_used);
PCRTCDisplays.CalculateFramebufferOffset();
PCRTCDisplays.CheckSameSource();
// Only need to check the right/bottom on software renderer, hardware always gets the full texture then cuts a bit out later.
if (samesrc && !feedback_merge && (GSConfig.UseHardwareRenderer() || (fr[0].right == fr[1].right && fr[0].bottom == fr[1].bottom)))
if (PCRTCDisplays.FrameRectMatch() && !PCRTCDisplays.FrameWrap() && !feedback_merge)
{
tex[0] = GetOutput(0, y_offset[0]);
tex[0] = GetOutput(-1, y_offset[0]);
tex[1] = tex[0]; // saves one texture fetch
y_offset[1] = y_offset[0];
single_fetch = true;
}
else
{
if (en[0])
if (PCRTCDisplays.PCRTCDisplays[0].enabled)
tex[0] = GetOutput(0, y_offset[0]);
if (en[1])
if (PCRTCDisplays.PCRTCDisplays[1].enabled)
tex[1] = GetOutput(1, y_offset[1]);
if (feedback_merge)
tex[2] = GetFeedbackOutput();
@ -162,11 +117,6 @@ bool GSRenderer::Merge(int field)
GSVector4 src_gs_read[2];
GSVector4 dst[3];
const bool slbg = m_regs->PMODE.SLBG;
GSVector2i resolution(GetResolution());
bool scanmask_frame = m_scanmask_used && !display_offset;
const bool ignore_offset = !GSConfig.PCRTCOffsets;
const bool is_bob = GSConfig.InterlaceMode == GSInterlaceMode::BobTFF || GSConfig.InterlaceMode == GSInterlaceMode::BobBFF;
// Use offset for bob deinterlacing always, extra offset added later for FFMD mode.
@ -175,6 +125,7 @@ bool GSRenderer::Merge(int field)
int field2 = 0;
int mode = 3;
bool scanmask_frame = m_scanmask_used && abs(PCRTCDisplays.PCRTCDisplays[0].displayRect.y - PCRTCDisplays.PCRTCDisplays[1].displayRect.y) != 1;
// FFMD (half frames) requires blend deinterlacing, so automatically use that. Same when SCANMSK is used but not blended in the merge circuit (Alpine Racer 3)
if (GSConfig.InterlaceMode != GSInterlaceMode::Automatic || (!m_regs->SMODE2.FFMD && !scanmask_frame))
{
@ -184,157 +135,38 @@ bool GSRenderer::Merge(int field)
for (int i = 0; i < 2; i++)
{
if (!en[i] || !tex[i])
GSPCRTCRegs::PCRTCDisplay& curCircuit = PCRTCDisplays.PCRTCDisplays[i];
if (!curCircuit.enabled || !tex[i])
continue;
GSVector4i r = GetFrameMagnifiedRect(i);
GSVector4 scale = GSVector4(tex[i]->GetScale()).xyxy();
GSVector2i off(ignore_offset ? 0 : display_offsets[i]);
GSVector2i display_diff(display_offsets[i].x - display_baseline.x, display_offsets[i].y - display_baseline.y);
GSVector2i frame_diff(fr[i].left - frame_baseline.x, fr[i].top - frame_baseline.y);
if (!GSConfig.UseHardwareRenderer())
{
// Clear any frame offsets, offset is already done in the software GetOutput.
fr[i].right -= fr[i].left;
fr[i].left = 0;
fr[i].bottom -= fr[i].top;
fr[i].top = 0;
// Put any frame offset difference back if we aren't anti-blurring on a single fetch (not offset).
if (!GSConfig.PCRTCAntiBlur && single_fetch)
{
fr[i].right += frame_diff.x;
fr[i].left += frame_diff.x;
fr[i].bottom += frame_diff.y;
fr[i].top += frame_diff.y;
}
}
// If using scanmsk we have to keep the single line offset, regardless of upscale
// so we handle this separately after the rect calculations.
float interlace_offset = 0.0f;
if ((!GSConfig.PCRTCAntiBlur || m_scanmask_used) && display_offset)
{
interlace_offset = static_cast<float>(display_diff.y & 1);
// When the displays are offset by 1 we need to adjust for upscale to handle it (reduces bounce in MGS2 when upscaling)
interlace_offset += (tex[i]->GetScale().y - 1.0f) / 2;
if (interlace_offset >= 1.0f)
{
if (!ignore_offset)
off.y -= 1;
display_diff.y -= 1;
}
}
// Start of Anti-Blur code.
if (!ignore_offset)
{
if (GSConfig.PCRTCAntiBlur)
{
if (samesrc)
{
// Offset by DISPLAY setting
if (display_diff.x < 4)
off.x -= display_diff.x;
if (display_diff.y < 4)
off.y -= display_diff.y;
// Only functional in HW mode, software clips/positions the framebuffer on read.
if (GSConfig.UseHardwareRenderer())
{
// Offset by DISPFB setting
if (abs(frame_diff.x) < 4)
off.x += frame_diff.x;
if (abs(frame_diff.y) < 4)
off.y += frame_diff.y;
}
}
}
}
else
{
if (!slbg || !feedback_merge)
{
// If the offsets between the two displays are quite large, it's probably intended for an effect.
if (display_diff.x >= 4 || !GSConfig.PCRTCAntiBlur)
off.x = display_diff.x;
if (display_diff.y >= 4 || !GSConfig.PCRTCAntiBlur)
off.y = display_diff.y;
// Need to check if only circuit 2 is enabled. Stuntman toggles circuit 1 on and off every other frame.
if (samesrc || m_regs->PMODE.EN == 2)
{
// Adjusting the screen offset when using a negative offset.
const int videomode = static_cast<int>(GetVideoMode()) - 1;
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
GSVector2i base_resolution(offsets.x, offsets.y);
if (isinterlaced() && !m_regs->SMODE2.FFMD)
base_resolution.y *= 2;
// Offset by DISPLAY setting
if (display_diff.x < 0)
{
off.x = 0;
if (base_resolution.x > resolution.x)
resolution.x -= display_diff.x;
}
if (display_diff.y < 0)
{
off.y = 0;
if (base_resolution.y > resolution.y)
resolution.y -= display_diff.y;
}
// Don't do X, we only care about height, this would need to be tailored for games using X (Black does -5).
// Mainly for Hokuto no Ken which does -14 Y offset.
if (display_baseline.y < -4)
off.y += display_baseline.y;
// Anti-Blur stuff
// Only functional in HW mode, software clips/positions the framebuffer on read.
if (GSConfig.PCRTCAntiBlur && GSConfig.UseHardwareRenderer())
{
// Offset by DISPFB setting
if (abs(frame_diff.x) < 4)
off.x += frame_diff.x;
if (abs(frame_diff.y) < 4)
off.y += frame_diff.y;
}
}
}
}
// End of Anti-Blur code.
if (isinterlaced() && m_regs->SMODE2.FFMD)
off.y >>= 1;
// dst is the final destination rect with offset on the screen.
dst[i] = scale * (GSVector4(off).xyxy() + GSVector4(r.rsize()));
dst[i] = scale * GSVector4(curCircuit.displayRect);
// src_gs_read is the size which we're really reading from GS memory.
src_gs_read[i] = ((GSVector4(fr[i]) + GSVector4(0, y_offset[i], 0, y_offset[i])) * scale) / GSVector4(tex[i]->GetSize()).xyxy();
src_gs_read[i] = ((GSVector4(curCircuit.framebufferRect) + GSVector4(0, y_offset[i], 0, y_offset[i])) * scale) / GSVector4(tex[i]->GetSize()).xyxy();
// src_out_rect is the resized rect for output. (Not really used)
src_out_rect[i] = (GSVector4(r) * scale) / GSVector4(tex[i]->GetSize()).xyxy();
if (m_regs->SMODE2.FFMD && !is_bob && !GSConfig.DisableInterlaceOffset && GSConfig.InterlaceMode != GSInterlaceMode::Off)
float interlace_offset = 0.0f;
if (isReallyInterlaced() && m_regs->SMODE2.FFMD && !is_bob && !GSConfig.DisableInterlaceOffset && GSConfig.InterlaceMode != GSInterlaceMode::Off)
{
// We do half because FFMD is a half sized framebuffer, then we offset by 1 in the shader for the actual interlace
if(GetUpscaleMultiplier() > 1.0f)
interlace_offset += ((((tex[1] ? tex[1]->GetScale().y : tex[0]->GetScale().y) + 0.5f) * 0.5f) - 1.0f) * static_cast<float>(field ^ field2);
offset = 1.0f;
interlace_offset = (scale.y) * static_cast<float>(field ^ field2);
}
// Restore manually offset "interlace" lines
// Scanmask frame offsets. It's gross, I'm sorry but it sucks.
if (m_scanmask_used)
{
int displayIntOffset = PCRTCDisplays.PCRTCDisplays[i].displayRect.y - PCRTCDisplays.PCRTCDisplays[1 - i].displayRect.y;
if (displayIntOffset > 0)
{
displayIntOffset &= 1;
dst[i].y -= displayIntOffset * scale.y;
dst[i].w -= displayIntOffset * scale.y;
interlace_offset += displayIntOffset;
}
}
dst[i] += GSVector4(0.0f, interlace_offset, 0.0f, interlace_offset);
}
@ -351,41 +183,21 @@ bool GSRenderer::Merge(int field)
dst[2] = GSVector4(scale * GSVector4(feedback_rect.rsize()));
}
// Set the resolution to the height of the displays (kind of a saturate height)
if (ignore_offset && !feedback_merge)
{
GSVector2i max_resolution = GetResolution();
resolution.x = display_combined.x - display_baseline.x;
resolution.y = display_combined.y - display_baseline.y;
if (isinterlaced() && m_regs->SMODE2.FFMD)
{
resolution.y >>= 1;
}
resolution.x = std::min(max_resolution.x, resolution.x);
resolution.y = std::min(max_resolution.y, resolution.y);
}
GSVector2i resolution = PCRTCDisplays.GetResolution();
fs = GSVector2i(static_cast<int>(static_cast<float>(resolution.x) * GetUpscaleMultiplier()),
static_cast<int>(static_cast<float>(resolution.y) * GetUpscaleMultiplier()));
ds = fs;
// When interlace(FRAME) mode, the rect is half height, so it needs to be stretched.
const bool is_interlaced_resolution = m_regs->SMODE2.INT || (isReallyInterlaced() && IsAnalogue() && GSConfig.InterlaceMode != GSInterlaceMode::Off);
if (is_interlaced_resolution && m_regs->SMODE2.FFMD)
ds.y *= 2;
m_real_size = GSVector2i(fs.x, is_interlaced_resolution ? ds.y : fs.y);
m_real_size = GSVector2i(fs.x, fs.y);
if (!tex[0] && !tex[1])
return false;
if ((tex[0] == tex[1]) && (src_out_rect[0] == src_out_rect[1]).alltrue() && (dst[0] == dst[1]).alltrue() && !feedback_merge && !slbg)
if ((tex[0] == tex[1]) && (src_out_rect[0] == src_out_rect[1]).alltrue() &&
(PCRTCDisplays.PCRTCDisplays[0].displayRect == PCRTCDisplays.PCRTCDisplays[1].displayRect).alltrue() &&
(PCRTCDisplays.PCRTCDisplays[0].framebufferRect == PCRTCDisplays.PCRTCDisplays[1].framebufferRect).alltrue() &&
!feedback_merge && !m_regs->PMODE.SLBG)
{
// the two outputs are identical, skip drawing one of them (the one that is alpha blended)
tex[0] = NULL;
}
@ -394,7 +206,7 @@ bool GSRenderer::Merge(int field)
g_gs_device->Merge(tex, src_gs_read, dst, fs, m_regs->PMODE, m_regs->EXTBUF, c);
if (isReallyInterlaced() && GSConfig.InterlaceMode != GSInterlaceMode::Off)
g_gs_device->Interlace(ds, field ^ field2, mode, offset);
g_gs_device->Interlace(fs, field ^ field2, mode, offset);
if (GSConfig.ShadeBoost)
g_gs_device->ShadeBoost();

View File

@ -821,14 +821,14 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
{
// 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output
// Note: value outside of dRect must contains the background color (c)
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY);
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY, false);
}
// Save 2nd output
if (feedback_write_2)
{
StretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
m_merge.cb.get(), nullptr, true);
m_merge.cb.get(), nullptr, false);
}
// Restore background color to process the normal merge
@ -838,13 +838,13 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
if (sTex[0])
{
// 1st output is enabled. It must be blended
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), true);
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), false);
}
if (feedback_write_1)
{
StretchRect(sTex[0], full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
m_merge.cb.get(), nullptr, true);
m_merge.cb.get(), nullptr, false);
}
}

View File

@ -605,7 +605,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
{
static_cast<GSTexture12*>(sTex[1])->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
OMSetRenderTargets(dTex, nullptr, darea);
SetUtilityTexture(sTex[1], m_linear_sampler_cpu);
SetUtilityTexture(sTex[1], m_point_sampler_cpu);
BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE,
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS, c);
@ -625,7 +625,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
EndRenderPass();
OMSetRenderTargets(sTex[2], nullptr, fbarea);
if (dcleared)
SetUtilityTexture(dTex, m_linear_sampler_cpu);
SetUtilityTexture(dTex, m_point_sampler_cpu);
// sTex[2] can be sTex[0], in which case it might be cleared (e.g. Xenosaga).
BeginRenderPassForStretchRect(static_cast<GSTexture12*>(sTex[2]), fbarea, GSVector4i(dRect[2]));
@ -663,7 +663,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
{
// 1st output is enabled. It must be blended
SetUtilityRootSignature();
SetUtilityTexture(sTex[0], m_linear_sampler_cpu);
SetUtilityTexture(sTex[0], m_point_sampler_cpu);
SetPipeline(m_merge[PMODE.MMOD].get());
DrawStretchRect(sRect[0], dRect[0], dTex->GetSize());
}
@ -673,7 +673,7 @@ void GSDevice12::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
EndRenderPass();
SetUtilityRootSignature();
SetPipeline(m_convert[static_cast<int>(ShaderConvert::YUV)].get());
SetUtilityTexture(dTex, m_linear_sampler_cpu);
SetUtilityTexture(dTex, m_point_sampler_cpu);
OMSetRenderTargets(sTex[2], nullptr, fbarea);
BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE, D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE);
DrawStretchRect(full_r, dRect[2], dsize);

View File

@ -50,43 +50,7 @@ GSRendererHW::GSRendererHW()
GSVector2i GSRendererHW::GetOutputSize(int real_h)
{
GSVector2i crtc_size(GetResolution());
// Correct framebuffer size to get output size when offsets not considered (uses framebuffer height)
if (!GSConfig.PCRTCOffsets)
{
const int videomode = static_cast<int>(GetVideoMode()) - 1;
const int display_width = (VideoModeDividers[videomode].z + 1) / GetDisplayHMagnification();
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
int display_height = offsets.y;
if (isinterlaced() && !m_regs->SMODE2.FFMD)
display_height *= 2;
if (crtc_size.x < display_width || crtc_size.y < display_height)
{
GSVector2i display_baseline = { 4096, 4096 };
for (int i = 0; i < 2; i++)
{
if (IsEnabled(i))
{
const GSVector4i dr = GetDisplayRect(i);
const GSVector2i display_diff(dr.left - display_baseline.x, dr.top - display_baseline.y);
if (display_diff.x != 0 && abs(display_diff.x) < 4 && crtc_size.x < display_width)
crtc_size.x -= display_diff.x;
if (display_diff.y != 0 && abs(display_diff.y) < 4 && crtc_size.y < display_height)
crtc_size.y -= display_diff.y;
display_baseline.x = std::min(dr.left, display_baseline.x);
display_baseline.y = std::min(dr.top, display_baseline.y);
}
}
}
}
GSVector2i crtc_size(PCRTCDisplays.GetResolution());
// Include negative display offsets in the height here.
crtc_size.y = std::max(crtc_size.y, real_h);
@ -241,40 +205,33 @@ void GSRendererHW::VSync(u32 field, bool registers_written)
GSTexture* GSRendererHW::GetOutput(int i, int& y_offset)
{
const GSRegDISPFB& DISPFB = m_regs->DISP[i].DISPFB;
int index = i >= 0 ? i : 1;
GIFRegTEX0 TEX0 = {};
GSPCRTCRegs::PCRTCDisplay& curFramebuffer = PCRTCDisplays.PCRTCDisplays[index];
GSVector2i framebufferSize = PCRTCDisplays.GetFramebufferSize(i);
const int fb_width = framebufferSize.x;
const int fb_height = framebufferSize.y;
TEX0.TBP0 = DISPFB.Block();
TEX0.TBW = DISPFB.FBW;
TEX0.PSM = DISPFB.PSM;
const int videomode = static_cast<int>(GetVideoMode()) - 1;
const GSVector4i offsets = VideoModeOffsets[videomode];
const int fb_width = std::min<int>(std::min<int>(GetFramebufferWidth(), DISPFB.FBW * 64) + static_cast<int>(DISPFB.DBX), 2048);
const int display_height = offsets.y * ((isinterlaced() && !m_regs->SMODE2.FFMD) ? 2 : 1);
const int display_offset = GetResolutionOffset(i).y;
int fb_height = (std::min<int>(GetFramebufferHeight(), display_height) + static_cast<int>(DISPFB.DBY)) % 2048;
// If there is a negative vertical offset on the picture, we need to read more.
if (display_offset < 0)
{
fb_height += -display_offset;
}
// TRACE(_T("[%d] GetOutput %d %05x (%d)\n"), static_cast<int>(g_perfmon.GetFrame()), i, static_cast<int>(TEX0.TBP0), static_cast<int>(TEX0.PSM));
PCRTCDisplays.RemoveFramebufferOffset(i);
// TRACE(_T("[%d] GetOutput %d %05x (%d)\n"), (int)m_perfmon.GetFrame(), i, (int)TEX0.TBP0, (int)TEX0.PSM);
GSTexture* t = nullptr;
GIFRegTEX0 TEX0 = {};
TEX0.TBP0 = curFramebuffer.Block();
TEX0.TBW = curFramebuffer.FBW;
TEX0.PSM = curFramebuffer.PSM;
if (GSTextureCache::Target* rt = m_tc->LookupDisplayTarget(TEX0, GetOutputSize(fb_height) * GSConfig.UpscaleMultiplier, fb_width, fb_height))
{
t = rt->m_texture;
const int delta = TEX0.TBP0 - rt->m_TEX0.TBP0;
if (delta > 0 && DISPFB.FBW != 0)
if (delta > 0 && curFramebuffer.FBW != 0)
{
const int pages = delta >> 5u;
int y_pages = pages / DISPFB.FBW;
y_offset = y_pages * GSLocalMemory::m_psm[DISPFB.PSM].pgs.y;
int y_pages = pages / curFramebuffer.FBW;
y_offset = y_pages * GSLocalMemory::m_psm[curFramebuffer.PSM].pgs.y;
GL_CACHE("Frame y offset %d pixels, unit %d", y_offset, i);
}
@ -797,8 +754,8 @@ GSVector2i GSRendererHW::GetTargetSize(GSVector2i* unscaled_size)
const int page_y = GSLocalMemory::m_psm[m_context->FRAME.PSM].pgs.y - 1;
// Round up the page as channel shuffles are generally done in pages at a time
width = (std::max(static_cast<u32>(GetResolution().x), width) + page_x) & ~page_x;
min_height = (std::max(static_cast<u32>(GetResolution().y), min_height) + page_y) & ~page_y;
width = (std::max(static_cast<u32>(PCRTCDisplays.GetResolution().x), width) + page_x) & ~page_x;
min_height = (std::max(static_cast<u32>(PCRTCDisplays.GetResolution().y), min_height) + page_y) & ~page_y;
}
// Align to even lines, reduces the chance of tiny resizes.
@ -1443,13 +1400,14 @@ void GSRendererHW::Draw()
const bool clear_height_valid = (m_r.w >= 1024);
if (clear_height_valid && context->FRAME.FBW == 1)
{
u32 width = ceil(static_cast<float>(m_r.w) / GetFramebufferHeight()) * 64;
GSVector2i fb_size = PCRTCDisplays.GetFramebufferSize(-1);
u32 width = ceil(static_cast<float>(m_r.w) / fb_size.y) * 64;
// Framebuffer is likely to be read as 16bit later, so we will need to double the width if the write is 32bit.
const bool double_width = GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 32 && GetFramebufferBitDepth() == 16;
const bool double_width = GSLocalMemory::m_psm[context->FRAME.PSM].bpp == 32 && PCRTCDisplays.GetFramebufferBitDepth() == 16;
m_r.x = 0;
m_r.y = 0;
m_r.w = GetFramebufferHeight();
m_r.z = std::max((width * (double_width ? 2 : 1)), static_cast<u32>(GetFramebufferWidth()));
m_r.w = fb_size.y;
m_r.z = std::max((width * (double_width ? 2 : 1)), static_cast<u32>(fb_size.x));
context->FRAME.FBW = (m_r.z + 63) / 64;
m_context->scissor.in.z = context->FRAME.FBW * 64;

View File

@ -563,12 +563,12 @@ void GSDeviceMTL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
{
// 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output
// Note: value outside of dRect must contains the background color (c)
StretchRect(sTex[1], sRect[1], dTex, dRect[1], ShaderConvert::COPY);
StretchRect(sTex[1], sRect[1], dTex, dRect[1], ShaderConvert::COPY, false);
}
// Save 2nd output
if (feedback_write_2) // FIXME I'm not sure dRect[1] is always correct
DoStretchRect(dTex, full_r, sTex[2], dRect[1], m_convert_pipeline[static_cast<int>(ShaderConvert::YUV)], true, LoadAction::DontCareIfFull, &cb_yuv, sizeof(cb_yuv));
DoStretchRect(dTex, full_r, sTex[2], dRect[1], m_convert_pipeline[static_cast<int>(ShaderConvert::YUV)], false, LoadAction::DontCareIfFull, &cb_yuv, sizeof(cb_yuv));
if (feedback_write_2_but_blend_bg)
ClearRenderTarget(dTex, c);
@ -582,17 +582,17 @@ void GSDeviceMTL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
if (PMODE.MMOD == 1)
{
// Blend with a constant alpha
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, true, LoadAction::Load, &cb_c, sizeof(cb_c));
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, false, LoadAction::Load, &cb_c, sizeof(cb_c));
}
else
{
// Blend with 2 * input alpha
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, true, LoadAction::Load, nullptr, 0);
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], pipeline, false, LoadAction::Load, nullptr, 0);
}
}
if (feedback_write_1) // FIXME I'm not sure dRect[0] is always correct
StretchRect(dTex, full_r, sTex[2], dRect[0], ShaderConvert::YUV);
StretchRect(dTex, full_r, sTex[2], dRect[0], ShaderConvert::YUV, false);
}}
void GSDeviceMTL::DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset, int bufIdx)

View File

@ -1327,7 +1327,7 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
{
// 2nd output is enabled and selected. Copy it to destination so we can blend it with 1st output
// Note: value outside of dRect must contains the background color (c)
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY);
StretchRect(sTex[1], sRect[1], dTex, PMODE.SLBG ? dRect[2] : dRect[1], ShaderConvert::COPY, false);
}
// Upload constant to select YUV algo
@ -1340,7 +1340,7 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
// Save 2nd output
if (feedback_write_2)
StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV);
StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV, false);
// Restore background color to process the normal merge
if (feedback_write_2_but_blend_bg)
@ -1357,17 +1357,17 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
// Blend with a constant alpha
m_merge_obj.ps[1].Bind();
m_merge_obj.ps[1].Uniform4fv(0, c.v);
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[1], true, OMColorMaskSelector());
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[1], true, OMColorMaskSelector(), false);
}
else
{
// Blend with 2 * input alpha
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[0], true, OMColorMaskSelector());
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[0], true, OMColorMaskSelector(), false);
}
}
if (feedback_write_1)
StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV);
StretchRect(dTex, full_r, sTex[2], dRect[2], ShaderConvert::YUV, false);
}
void GSDeviceOGL::DoInterlace(GSTexture* sTex, GSTexture* dTex, int shader, bool linear, float yoffset, int bufIdx)

View File

@ -82,22 +82,6 @@ void GSRendererSW::VSync(u32 field, bool registers_written)
{
Sync(0); // IncAge might delete a cached texture in use
if (0) if (LOG)
{
fprintf(s_fp, "%llu\n", g_perfmon.GetFrame());
GSVector4i dr = GetDisplayRect();
GSVector4i fr = GetFrameRect();
fprintf(s_fp, "dr %d %d %d %d, fr %d %d %d %d\n",
dr.x, dr.y, dr.z, dr.w,
fr.x, fr.y, fr.z, fr.w);
m_regs->Dump(s_fp);
fflush(s_fp);
}
/*
int draw[8], sum = 0;
@ -127,27 +111,20 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset)
{
Sync(1);
const GSRegDISPFB& DISPFB = m_regs->DISP[i].DISPFB;
int index = i >= 0 ? i : 1;
GSPCRTCRegs::PCRTCDisplay& curFramebuffer = PCRTCDisplays.PCRTCDisplays[index];
GSVector2i framebufferSize = PCRTCDisplays.GetFramebufferSize(i);
GSVector4i framebufferRect = PCRTCDisplays.GetFramebufferRect(i);
int w = curFramebuffer.FBW * 64;
int h = framebufferSize.y;
int w = DISPFB.FBW * 64;
const int videomode = static_cast<int>(GetVideoMode()) - 1;
const int display_offset = GetResolutionOffset(i).y;
const GSVector4i offsets = !GSConfig.PCRTCOverscan ? VideoModeOffsets[videomode] : VideoModeOffsetsOverscan[videomode];
const int display_height = offsets.y * ((isinterlaced() && !m_regs->SMODE2.FFMD) ? 2 : 1);
int h = std::min(GetFramebufferHeight(), display_height);
// If there is a negative vertical offset on the picture, we need to read more.
if (display_offset < 0)
{
h += -display_offset;
}
if (g_gs_device->ResizeTarget(&m_texture[i], w, h))
if (g_gs_device->ResizeTarget(&m_texture[index], w, h))
{
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[curFramebuffer.PSM];
constexpr int pitch = 1024 * 4;
const int off_x = DISPFB.DBX & 0x7ff;
const int off_y = DISPFB.DBY & 0x7ff;
// Should really be framebufferOffsets rather than framebufferRect but this might be compensated with anti-blur in some games.
const int off_x = (framebufferRect.x & 0x7ff) & ~(psm.bs.x-1);
const int off_y = (framebufferRect.y & 0x7ff) & ~(psm.bs.y-1);
const GSVector4i out_r(0, 0, w, h);
GSVector4i r(off_x, off_y, w + off_x, h + off_y);
GSVector4i rh(off_x, off_y, w + off_x, (h + off_y) & 0x7FF);
@ -155,6 +132,7 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset)
bool h_wrap = false;
bool w_wrap = false;
PCRTCDisplays.RemoveFramebufferOffset(i);
// Need to read it in 2 parts, since you can't do a split rect.
if (r.bottom >= 2048)
{
@ -172,50 +150,51 @@ GSTexture* GSRendererSW::GetOutput(int i, int& y_offset)
w_wrap = true;
}
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[DISPFB.PSM];
// Display doesn't use texa, and instead uses the equivalent of this
GIFRegTEXA texa = {};
texa.AEM = 0;
texa.TA0 = (DISPFB.PSM == PSM_PSMCT24 || DISPFB.PSM == PSM_PSGPU24) ? 0x80 : 0;
texa.TA0 = (curFramebuffer.PSM == PSM_PSMCT24 || curFramebuffer.PSM == PSM_PSGPU24) ? 0x80 : 0;
texa.TA1 = 0x80;
// Top left rect
psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), r.ralign<Align_Outside>(psm.bs), m_output, pitch, texa);
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), r.ralign<Align_Outside>(psm.bs), m_output, pitch, texa);
// Top left rect
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), r.ralign<Align_Outside>(psm.bs), m_output, pitch, texa);
int top = (h_wrap) ? ((r.bottom - r.top) * pitch) : 0;
int left = (w_wrap) ? (r.right - r.left) * (GSLocalMemory::m_psm[DISPFB.PSM].bpp / 8) : 0;
int left = (w_wrap) ? (r.right - r.left) * (GSLocalMemory::m_psm[curFramebuffer.PSM].bpp / 8) : 0;
// The following only happen if the DBX/DBY wrap around at 2048.
// Top right rect
if (w_wrap)
psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rw.ralign<Align_Outside>(psm.bs), &m_output[left], pitch, texa);
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rw.ralign<Align_Outside>(psm.bs), &m_output[left], pitch, texa);
// Bottom left rect
if (h_wrap)
psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rh.ralign<Align_Outside>(psm.bs), &m_output[top], pitch, texa);
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rh.ralign<Align_Outside>(psm.bs), &m_output[top], pitch, texa);
// Bottom right rect
if (h_wrap && w_wrap)
{
// Needs also rw with the start/end height of rh, fills in the bottom right rect which will be missing if both overflow.
const GSVector4i rwh(rw.left, rh.top, rw.right, rh.bottom);
psm.rtx(m_mem, m_mem.GetOffset(DISPFB.Block(), DISPFB.FBW, DISPFB.PSM), rwh.ralign<Align_Outside>(psm.bs), &m_output[top + left], pitch, texa);
psm.rtx(m_mem, m_mem.GetOffset(curFramebuffer.Block(), curFramebuffer.FBW, curFramebuffer.PSM), rwh.ralign<Align_Outside>(psm.bs), &m_output[top + left], pitch, texa);
}
m_texture[i]->Update(out_r, m_output, pitch);
m_texture[index]->Update(out_r, m_output, pitch);
if (GSConfig.DumpGSData)
{
if (GSConfig.SaveFrame && s_n >= GSConfig.SaveN)
{
m_texture[i]->Save(GetDrawDumpPath("%05d_f%lld_fr%d_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), i, (int)DISPFB.Block(), psm_str(DISPFB.PSM)));
m_texture[i]->Save(GetDrawDumpPath("%05d_f%lld_fr%d_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), i, (int)curFramebuffer.Block(), psm_str(curFramebuffer.PSM)));
}
}
}
return m_texture[i];
return m_texture[index];
}
GSTexture* GSRendererSW::GetFeedbackOutput()
@ -490,18 +469,18 @@ void GSRendererSW::Draw()
{
// Dump the RT in 32 bits format. It helps to debug texture shuffle effect
s = GetDrawDumpPath("%05d_f%lld_rt0_%05x_32bits.bmp", s_n, frame, m_context->FRAME.Block());
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, GetFrameRect().width(), 512);
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
}
s = GetDrawDumpPath("%05d_f%lld_rt0_%05x_%s.bmp", s_n, frame, m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM));
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512);
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
}
if (GSConfig.SaveDepth && s_n >= GSConfig.SaveN)
{
s = GetDrawDumpPath("%05d_f%lld_rz0_%05x_%s.bmp", s_n, frame, m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM));
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512);
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
}
Queue(data);
@ -514,18 +493,18 @@ void GSRendererSW::Draw()
{
// Dump the RT in 32 bits format. It helps to debug texture shuffle effect
s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_32bits.bmp", s_n, frame, m_context->FRAME.Block());
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, GetFrameRect().width(), 512);
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, 0, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
}
s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_%s.bmp", s_n, frame, m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM));
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512);
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
}
if (GSConfig.SaveDepth && s_n >= GSConfig.SaveN)
{
s = GetDrawDumpPath("%05d_f%lld_rz1_%05x_%s.bmp", s_n, frame, m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM));
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512);
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
}
if (GSConfig.SaveL > 0 && (s_n - GSConfig.SaveN) > GSConfig.SaveL)
@ -613,14 +592,14 @@ void GSRendererSW::Sync(int reason)
{
s = GetDrawDumpPath("%05d_f%lld_rt1_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), m_context->FRAME.Block(), psm_str(m_context->FRAME.PSM));
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, GetFrameRect().width(), 512);
m_mem.SaveBMP(s, m_context->FRAME.Block(), m_context->FRAME.FBW, m_context->FRAME.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
}
if (GSConfig.SaveDepth)
{
s = GetDrawDumpPath("%05d_f%lld_zb1_%05x_%s.bmp", s_n, g_perfmon.GetFrame(), m_context->ZBUF.Block(), psm_str(m_context->ZBUF.PSM));
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, GetFrameRect().width(), 512);
m_mem.SaveBMP(s, m_context->ZBUF.Block(), m_context->FRAME.FBW, m_context->ZBUF.PSM, PCRTCDisplays.GetFramebufferRect(-1).width(), 512);
}
}

View File

@ -736,7 +736,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
{
static_cast<GSTextureVK*>(sTex[1])->TransitionToLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
OMSetRenderTargets(dTex, nullptr, darea, false);
SetUtilityTexture(sTex[1], m_linear_sampler);
SetUtilityTexture(sTex[1], m_point_sampler);
BeginClearRenderPass(m_utility_color_render_pass_clear, darea, c);
SetPipeline(m_convert[static_cast<int>(ShaderConvert::COPY)]);
DrawStretchRect(sRect[1], PMODE.SLBG ? dRect[2] : dRect[1], dsize);
@ -753,7 +753,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
EndRenderPass();
OMSetRenderTargets(sTex[2], nullptr, fbarea, false);
if (dcleared)
SetUtilityTexture(dTex, m_linear_sampler);
SetUtilityTexture(dTex, m_point_sampler);
// sTex[2] can be sTex[0], in which case it might be cleared (e.g. Xenosaga).
BeginRenderPassForStretchRect(static_cast<GSTextureVK*>(sTex[2]), fbarea, GSVector4i(dRect[2]));
if (dcleared)
@ -787,7 +787,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
if (sTex[0] && sTex[0]->GetState() == GSTexture::State::Dirty)
{
// 1st output is enabled. It must be blended
SetUtilityTexture(sTex[0], m_linear_sampler);
SetUtilityTexture(sTex[0], m_point_sampler);
SetPipeline(m_merge[PMODE.MMOD]);
SetUtilityPushConstants(&c, sizeof(c));
DrawStretchRect(sRect[0], dRect[0], dTex->GetSize());
@ -797,7 +797,7 @@ void GSDeviceVK::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
{
EndRenderPass();
SetPipeline(m_convert[static_cast<int>(ShaderConvert::YUV)]);
SetUtilityTexture(dTex, m_linear_sampler);
SetUtilityTexture(dTex, m_point_sampler);
SetUtilityPushConstants(yuv_constants, sizeof(yuv_constants));
OMSetRenderTargets(sTex[2], nullptr, fbarea, false);
BeginRenderPass(m_utility_color_render_pass_load, fbarea);