71 changes: 60 additions & 11 deletions Source/Core/VideoCommon/Src/VertexShaderManager.cpp
Expand Up @@ -39,13 +39,6 @@ static float s_fViewRotation[2];
VertexShaderConstants VertexShaderManager::constants;
bool VertexShaderManager::dirty;

void UpdateViewport(Matrix44& vpCorrection);

void UpdateViewportWithCorrection()
{
UpdateViewport(s_viewportCorrection);
}

struct ProjectionHack
{
float sign;
Expand Down Expand Up @@ -130,6 +123,58 @@ void UpdateProjectionHack(int iPhackvalue[], std::string sPhackvalue[])
g_ProjHack3 = bProjHack3;
}


// Viewport correction:
// In D3D, the viewport rectangle must fit within the render target.
// Say you want a viewport at (ix, iy) with size (iw, ih),
// but your viewport must be clamped at (ax, ay) with size (aw, ah).
// Just multiply the projection matrix with the following to get the same
// effect:
// [ (iw/aw) 0 0 ((iw - 2*(ax-ix)) / aw - 1) ]
// [ 0 (ih/ah) 0 ((-ih + 2*(ay-iy)) / ah + 1) ]
// [ 0 0 1 0 ]
// [ 0 0 0 1 ]
static void ViewportCorrectionMatrix(Matrix44& result)
{
int scissorXOff = bpmem.scissorOffset.x * 2;
int scissorYOff = bpmem.scissorOffset.y * 2;

// TODO: ceil, floor or just cast to int?
// TODO: Directly use the floats instead of rounding them?
float intendedX = xfregs.viewport.xOrig - xfregs.viewport.wd - scissorXOff;
float intendedY = xfregs.viewport.yOrig + xfregs.viewport.ht - scissorYOff;
float intendedWd = 2.0f * xfregs.viewport.wd;
float intendedHt = -2.0f * xfregs.viewport.ht;

if (intendedWd < 0.f)
{
intendedX += intendedWd;
intendedWd = -intendedWd;
}
if (intendedHt < 0.f)
{
intendedY += intendedHt;
intendedHt = -intendedHt;
}

// fit to EFB size
float X = (intendedX >= 0.f) ? intendedX : 0.f;
float Y = (intendedY >= 0.f) ? intendedY : 0.f;
float Wd = (X + intendedWd <= EFB_WIDTH) ? intendedWd : (EFB_WIDTH - X);
float Ht = (Y + intendedHt <= EFB_HEIGHT) ? intendedHt : (EFB_HEIGHT - Y);

Matrix44::LoadIdentity(result);
if (Wd == 0 || Ht == 0)
return;

result.data[4*0+0] = intendedWd / Wd;
result.data[4*0+3] = (intendedWd - 2.f * (X - intendedX)) / Wd - 1.f;
result.data[4*1+1] = intendedHt / Ht;
result.data[4*1+3] = (-intendedHt + 2.f * (Y - intendedY)) / Ht + 1.f;
}

void UpdateViewport();

void VertexShaderManager::Init()
{
Dirty();
Expand Down Expand Up @@ -326,12 +371,16 @@ void VertexShaderManager::SetConstants()
bViewportChanged = false;
constants.depthparams[0] = xfregs.viewport.farZ / 16777216.0f;
constants.depthparams[1] = xfregs.viewport.zRange / 16777216.0f;
constants.depthparams[2] = -1.f / g_renderer->EFBToScaledX(ceilf(2.0f * xfregs.viewport.wd));
constants.depthparams[3] = 1.f / g_renderer->EFBToScaledY(ceilf(-2.0f * xfregs.viewport.ht));
dirty = true;
// This is so implementation-dependent that we can't have it here.
UpdateViewport(s_viewportCorrection);
bProjectionChanged = true;
UpdateViewport();

// Update projection if the viewport isn't 1:1 useable
if(!g_ActiveConfig.backend_info.bSupportsOversizedViewports)
{
ViewportCorrectionMatrix(s_viewportCorrection);
bProjectionChanged = true;
}
}

if (bProjectionChanged)
Expand Down
2 changes: 0 additions & 2 deletions Source/Core/VideoCommon/Src/VertexShaderManager.h
Expand Up @@ -12,8 +12,6 @@ class PointerWrap;

void UpdateProjectionHack(int iParams[], std::string sParams[]);

void UpdateViewportWithCorrection();

// The non-API dependent parts.
class VertexShaderManager
{
Expand Down
1 change: 1 addition & 0 deletions Source/Core/VideoCommon/Src/VideoConfig.h
Expand Up @@ -153,6 +153,7 @@ struct VideoConfig
bool bSupportsPixelLighting;
bool bSupportsPrimitiveRestart;
bool bSupportsSeparateAlphaFunction;
bool bSupportsOversizedViewports;
bool bSupportsGLSLUBO; // needed by PixelShaderGen, so must stay in VideoCommon
bool bSupportsEarlyZ; // needed by PixelShaderGen, so must stay in VideoCommon
} backend_info;
Expand Down