Merge pull request #8376 from unknownbrackets/gpu-depth

Correct universally for clamped depth range
This commit is contained in:
Henrik Rydgård 2016-01-04 09:55:02 +01:00
commit f0812297a6
14 changed files with 94 additions and 150 deletions

View File

@ -42,5 +42,4 @@ void Compatibility::Clear() {
void Compatibility::LoadIniSection(IniFile &iniFile, std::string section) {
iniFile.Get(section.c_str(), "NoDepthRounding", &flags_.NoDepthRounding, flags_.NoDepthRounding);
iniFile.Get(section.c_str(), "PixelDepthRounding", &flags_.PixelDepthRounding, flags_.PixelDepthRounding);
iniFile.Get(section.c_str(), "DepthRangeHack", &flags_.DepthRangeHack, flags_.DepthRangeHack);
}

View File

@ -47,7 +47,6 @@
struct CompatFlags {
bool NoDepthRounding;
bool PixelDepthRounding;
bool DepthRangeHack;
};
class IniFile;

View File

@ -597,6 +597,8 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo
float xOffset = 0.0f;
float hScale = 1.0f;
float yOffset = 0.0f;
float zScale = 1.0f;
float zOffset = 0.0f;
// If we're within the bounds, we want clipping the viewport way. So leave it be.
if (left < 0.0f || right > renderWidth) {
@ -625,27 +627,53 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo
yOffset = drift / (bottom - top);
}
bool scaleChanged = gstate_c.vpWidthScale != wScale || gstate_c.vpHeightScale != hScale;
bool offsetChanged = gstate_c.vpXOffset != xOffset || gstate_c.vpYOffset != yOffset;
if (scaleChanged || offsetChanged) {
gstate_c.vpWidthScale = wScale;
gstate_c.vpHeightScale = hScale;
gstate_c.vpXOffset = xOffset;
gstate_c.vpYOffset = yOffset;
out.dirtyProj = true;
}
out.viewportX = left + displayOffsetX;
out.viewportY = top + displayOffsetY;
out.viewportW = right - left;
out.viewportH = bottom - top;
float zScale = gstate.getViewportZScale();
float zCenter = gstate.getViewportZCenter();
float depthRangeMin = zCenter - zScale;
float depthRangeMax = zCenter + zScale;
out.depthRangeMin = depthRangeMin * (1.0f / 65535.0f);
out.depthRangeMax = depthRangeMax * (1.0f / 65535.0f);
float vpZScale = gstate.getViewportZScale();
float vpZCenter = gstate.getViewportZCenter();
float depthRangeMin = vpZCenter - vpZScale;
float depthRangeMax = vpZCenter + vpZScale;
// Near/far can be inverted. Let's reverse while dealing with clamping, though.
bool inverted = vpZScale < 0.0f;
float near = (inverted ? depthRangeMax : depthRangeMin) * (1.0f / 65535.0f);
float far = (inverted ? depthRangeMin : depthRangeMax) * (1.0f / 65535.0f);
if (near < 0.0f || far > 1.0f) {
float overageNear = std::max(-near, 0.0f);
float overageFar = std::max(far - 1.0f, 0.0f);
float drift = overageFar - overageNear;
near += overageNear;
far -= overageFar;
zScale = fabsf(vpZScale * (2.0f / 65535.0f)) / (far - near);
zOffset = drift / (far - near);
}
if (inverted) {
zScale = -zScale;
inverted = false;
}
out.depthRangeMin = inverted ? far : near;
out.depthRangeMax = inverted ? near : far;
bool scaleChanged = gstate_c.vpWidthScale != wScale || gstate_c.vpHeightScale != hScale;
bool offsetChanged = gstate_c.vpXOffset != xOffset || gstate_c.vpYOffset != yOffset;
bool depthChanged = gstate_c.vpDepthScale != zScale || gstate_c.vpZOffset != zOffset;
if (scaleChanged || offsetChanged || depthChanged) {
gstate_c.vpWidthScale = wScale;
gstate_c.vpHeightScale = hScale;
gstate_c.vpDepthScale = zScale;
gstate_c.vpXOffset = xOffset;
gstate_c.vpYOffset = yOffset;
gstate_c.vpZOffset = zOffset;
out.dirtyProj = true;
out.dirtyDepth = depthChanged;
}
#ifndef MOBILE_DEVICE
float minz = gstate.getDepthRangeMin();

View File

@ -63,6 +63,7 @@ struct ViewportAndScissor {
float depthRangeMin;
float depthRangeMax;
bool dirtyProj;
bool dirtyDepth;
};
void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, float renderHeight, int bufferWidth, int bufferHeight, ViewportAndScissor &out);

View File

@ -481,11 +481,6 @@ void DIRECTX9_GPU::CheckGPUFeatures() {
features |= GPU_ROUND_DEPTH_TO_16BIT;
}
// The Phantasy Star hack :(
if (PSP_CoreParameter().compat.flags().DepthRangeHack) {
features |= GPU_USE_DEPTH_RANGE_HACK;
}
gstate_c.featureFlags = features;
}

View File

@ -248,7 +248,7 @@ void ShaderManagerDX9::VSSetMatrix(int creg, const float* pMatrix) {
}
// Depth in ogl is between -1;1 we need between 0;1 and optionally reverse it
static void ConvertProjMatrixToD3D(Matrix4x4 &in, bool invertedX, bool invertedY, bool invertedZ) {
static void ConvertProjMatrixToD3D(Matrix4x4 &in, bool invertedX, bool invertedY) {
// Half pixel offset hack
float xoff = 0.5f / gstate_c.curRTRenderWidth;
xoff = gstate_c.vpXOffset + (invertedX ? xoff : -xoff);
@ -260,7 +260,9 @@ static void ConvertProjMatrixToD3D(Matrix4x4 &in, bool invertedX, bool invertedY
if (invertedY)
yoff = -yoff;
in.translateAndScale(Vec3(xoff, yoff, 0.5f), Vec3(gstate_c.vpWidthScale, gstate_c.vpHeightScale, invertedZ ? -0.5 : 0.5f));
const Vec3 trans(xoff, yoff, gstate_c.vpZOffset + 0.5f);
const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f);
in.translateAndScale(trans, scale);
}
static void ConvertProjMatrixToD3DThrough(Matrix4x4 &in) {
@ -342,40 +344,7 @@ void ShaderManagerDX9::VSUpdateUniforms(int dirtyUniforms) {
flippedMatrix[12] = -flippedMatrix[12];
}
// In Phantasy Star Portable 2, depth range sometimes goes negative and is clamped by glDepthRange to 0,
// causing graphics clipping glitch (issue #1788). This hack modifies the projection matrix to work around it.
if (gstate_c.Supports(GPU_USE_DEPTH_RANGE_HACK)) {
float zScale = gstate.getViewportZScale() / 65535.0f;
float zCenter = gstate.getViewportZCenter() / 65535.0f;
// if far depth range < 0
if (zCenter + zScale < 0.0f) {
// if perspective projection
if (flippedMatrix[11] < 0.0f) {
float depthMax = gstate.getDepthRangeMax() / 65535.0f;
float depthMin = gstate.getDepthRangeMin() / 65535.0f;
float a = flippedMatrix[10];
float b = flippedMatrix[14];
float n = b / (a - 1.0f);
float f = b / (a + 1.0f);
f = (n * f) / (n + ((zCenter + zScale) * (n - f) / (depthMax - depthMin)));
a = (n + f) / (n - f);
b = (2.0f * n * f) / (n - f);
if (!my_isnan(a) && !my_isnan(b)) {
flippedMatrix[10] = a;
flippedMatrix[14] = b;
}
}
}
}
const bool invertedZ = gstate_c.vpDepth < 0;
ConvertProjMatrixToD3D(flippedMatrix, invertedX, invertedY, invertedZ);
ConvertProjMatrixToD3D(flippedMatrix, invertedX, invertedY);
VSSetMatrix(CONST_VS_PROJ, flippedMatrix.getReadPtr());
}
@ -513,18 +482,22 @@ void ShaderManagerDX9::VSUpdateUniforms(int dirtyUniforms) {
if (dirtyUniforms & DIRTY_DEPTHRANGE) {
float viewZScale = gstate.getViewportZScale();
float viewZCenter = gstate.getViewportZCenter();
// Given the way we do the rounding, the integer part of the offset is probably mostly irrelevant as we cancel
// it afterwards anyway.
// It seems that we should adjust for D3D projection matrix. We got squashed up to only 0-1, so we divide
// the scale factor by 2, and add an offset. But, this doesn't work! I get near-perfect results not doing it.
// viewZScale *= 2.0f;
// Need to take the possibly inverted proj matrix into account.
if (gstate_c.vpDepth < 0.0)
viewZScale *= -1.0f;
viewZCenter -= 32767.5f;
float viewZInvScale;
// We had to scale and translate Z to account for our clamped Z range.
// Therefore, we also need to reverse this to round properly.
//
// Example: scale = 65535.0, center = 0.0
// Resulting range = -65535 to 65535, clamped to [0, 65535]
// gstate_c.vpDepthScale = 2.0f
// gstate_c.vpZOffset = -1.0f
//
// The projection already accounts for those, so we need to reverse them.
//
// Additionally, D3D9 uses a range from [0, 1]. We double and move the center.
viewZScale *= (1.0f / gstate_c.vpDepthScale) * 2.0f;
viewZCenter -= 65535.0f * gstate_c.vpZOffset + 32768.5f;
if (viewZScale != 0.0) {
viewZInvScale = 1.0f / viewZScale;
} else {

View File

@ -33,8 +33,6 @@ namespace DX9 {
class PSShader;
class VSShader;
void ConvertProjMatrixToD3D(Matrix4x4 & in);
// Pretty much full. Will need more bits for more fine grained dirty tracking for lights.
enum {
DIRTY_PROJMATRIX = (1 << 0),

View File

@ -293,23 +293,13 @@ void TransformDrawEngineDX9::ApplyDrawState(int prim) {
float depthMin = vpAndScissor.depthRangeMin;
float depthMax = vpAndScissor.depthRangeMax;
if (!gstate.isModeThrough()) {
// Direct3D can't handle negative depth ranges, so we fix it in the projection matrix.
if (gstate_c.vpDepth != depthMax - depthMin) {
gstate_c.vpDepth = depthMax - depthMin;
vpAndScissor.dirtyProj = true;
}
if (depthMin > depthMax) {
std::swap(depthMin, depthMax);
}
if (depthMin < 0.0f) depthMin = 0.0f;
if (depthMax > 1.0f) depthMax = 1.0f;
}
dxstate.viewport.set(vpAndScissor.viewportX, vpAndScissor.viewportY, vpAndScissor.viewportW, vpAndScissor.viewportH, depthMin, depthMax);
if (vpAndScissor.dirtyProj) {
shaderManager_->DirtyUniform(DIRTY_PROJMATRIX);
}
if (vpAndScissor.dirtyDepth) {
shaderManager_->DirtyUniform(DIRTY_DEPTHRANGE);
}
}
void TransformDrawEngineDX9::ApplyDrawStateLate() {

View File

@ -562,11 +562,6 @@ void GLES_GPU::CheckGPUFeatures() {
}
}
// The Phantasy Star hack :(
if (PSP_CoreParameter().compat.flags().DepthRangeHack) {
features |= GPU_USE_DEPTH_RANGE_HACK;
}
#ifdef MOBILE_DEVICE
// Arguably, we should turn off GPU_IS_MOBILE on like modern Tegras, etc.
features |= GPU_IS_MOBILE;

View File

@ -378,8 +378,8 @@ static inline void ScaleProjMatrix(Matrix4x4 &in) {
// GL upside down is a pain as usual.
yOffset = -yOffset;
}
const Vec3 trans(gstate_c.vpXOffset, yOffset, 0.0f);
const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, 1.0);
const Vec3 trans(gstate_c.vpXOffset, yOffset, gstate_c.vpZOffset * 2.0f);
const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale);
in.translateAndScale(trans, scale);
}
@ -437,38 +437,6 @@ void LinkedShader::UpdateUniforms(u32 vertType) {
flippedMatrix[12] = -flippedMatrix[12];
}
// In Phantasy Star Portable 2, depth range sometimes goes negative and is clamped by glDepthRange to 0,
// causing graphics clipping glitch (issue #1788). This hack modifies the projection matrix to work around it.
if (gstate_c.Supports(GPU_USE_DEPTH_RANGE_HACK)) {
float zScale = gstate.getViewportZScale() / 65535.0f;
float zCenter = gstate.getViewportZCenter() / 65535.0f;
// if far depth range < 0
if (zCenter + zScale < 0.0f) {
// if perspective projection
if (flippedMatrix[11] < 0.0f) {
float depthMax = gstate.getDepthRangeMax() / 65535.0f;
float depthMin = gstate.getDepthRangeMin() / 65535.0f;
float a = flippedMatrix[10];
float b = flippedMatrix[14];
float n = b / (a - 1.0f);
float f = b / (a + 1.0f);
f = (n * f) / (n + ((zCenter + zScale) * (n - f) / (depthMax - depthMin)));
a = (n + f) / (n - f);
b = (2.0f * n * f) / (n - f);
if (!my_isnan(a) && !my_isnan(b)) {
flippedMatrix[10] = a;
flippedMatrix[14] = b;
}
}
}
}
ScaleProjMatrix(flippedMatrix);
glUniformMatrix4fv(u_proj, 1, GL_FALSE, flippedMatrix.m);
@ -624,11 +592,27 @@ void LinkedShader::UpdateUniforms(u32 vertType) {
float viewZScale = gstate.getViewportZScale();
float viewZCenter = gstate.getViewportZCenter();
float viewZInvScale;
// We had to scale and translate Z to account for our clamped Z range.
// Therefore, we also need to reverse this to round properly.
//
// Example: scale = 65535.0, center = 0.0
// Resulting range = -65535 to 65535, clamped to [0, 65535]
// gstate_c.vpDepthScale = 2.0f
// gstate_c.vpZOffset = -1.0f
//
// The projection already accounts for those, so we need to reverse them.
//
// Additionally, OpenGL uses a range from [-1, 1]. So we multiply by scale and add the center.
viewZScale *= (1.0f / gstate_c.vpDepthScale);
viewZCenter -= 65535.0f * (gstate_c.vpZOffset);
if (viewZScale != 0.0) {
viewZInvScale = 1.0f / viewZScale;
} else {
viewZInvScale = 0.0;
}
float data[4] = { viewZScale, viewZCenter, viewZCenter, viewZInvScale };
SetFloatUniform4(u_depthRange, data);
}

View File

@ -371,6 +371,9 @@ void TransformDrawEngine::ApplyDrawState(int prim) {
if (vpAndScissor.dirtyProj) {
shaderManager_->DirtyUniform(DIRTY_PROJMATRIX);
}
if (vpAndScissor.dirtyDepth) {
shaderManager_->DirtyUniform(DIRTY_DEPTHRANGE);
}
}
void TransformDrawEngine::ApplyDrawStateLate() {

View File

@ -254,10 +254,9 @@ void GPUStateCache::DoState(PointerWrap &p) {
p.Do(vpWidth);
p.Do(vpHeight);
if (s >= 4) {
p.Do(vpDepth);
} else {
vpDepth = 1.0f; // any positive value should be fine
if (s == 4) {
float oldDepth = 1.0f;
p.Do(oldDepth);
}
p.Do(curRTWidth);

View File

@ -453,7 +453,6 @@ enum {
GPU_SUPPORTS_UNPACK_SUBIMAGE = FLAG_BIT(3),
GPU_SUPPORTS_BLEND_MINMAX = FLAG_BIT(4),
GPU_SUPPORTS_LOGIC_OP = FLAG_BIT(5),
GPU_USE_DEPTH_RANGE_HACK = FLAG_BIT(6),
GPU_SUPPORTS_VAO = FLAG_BIT(18),
GPU_SUPPORTS_ANY_COPY_IMAGE = FLAG_BIT(19),
GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH = FLAG_BIT(20),
@ -511,12 +510,13 @@ struct GPUStateCache {
float vpWidth;
float vpHeight;
float vpDepth;
float vpXOffset;
float vpYOffset;
float vpZOffset;
float vpWidthScale;
float vpHeightScale;
float vpDepthScale;
KnownVertexBounds vertBounds;

View File

@ -111,23 +111,3 @@ PixelDepthRounding = true
# Heroes Phantasia Limited Edition Disc requires pixel depth rounding.
[ULJS00455]
PixelDepthRounding = true
# Phantasy Star Portable 2 has the same issue.
[ULJM05493]
DepthRangeHack = true
[NPJH50043]
DepthRangeHack = true
[ULJM08030]
DepthRangeHack = true
[ULES01439]
DepthRangeHack = true
[ULUS10529]
DepthRangeHack = true
[ULJM91018] # Infinity demo disc?
DepthRangeHack = true
[NPJH90157] # Infinity demo
DepthRangeHack = true
[ULJM05732]
DepthRangeHack = true
[NPJH50332]
DepthRangeHack = true