Merge pull request #7920 from hrydgard/vshader-depth-round

Round depth values to 16-bit precision to match the PSP's 16-bit depth buffer
This commit is contained in:
Henrik Rydgård committed 2015-08-30 11:30:12 +02:00
commit 12884eceb9
13 files changed
+150 -21

No files matched your search

+4
View File
@@ -211,6 +211,7 @@ void SoftwareTransform(
// The w of uv is also never used (hardcoded to 1.0.)
}
} else {
// Okay, need to actually perform the full transform.
for (int index = 0; index < maxIndex; index++) {
reader.Goto(index);
@@ -401,6 +402,9 @@ void SoftwareTransform(
}
transformed[index].color0_32 = c0.ToRGBA();
transformed[index].color1_32 = c1.ToRGBA();
// The multiplication by the projection matrix is still performed in the vertex shader.
// So is vertex depth rounding, to simulate the 16-bit depth buffer.
}
}
+1 -1
View File
@@ -889,7 +889,7 @@ void DIRECTX9_GPU::Execute_ViewportType(u32 op, u32 diff) {
switch (op >> 24) {
case GE_CMD_VIEWPORTZ1:
case GE_CMD_VIEWPORTZ2:
shaderManager_->DirtyUniform(DIRTY_PROJMATRIX);
shaderManager_->DirtyUniform(DIRTY_PROJMATRIX | DIRTY_DEPTHRANGE);
break;
}
}
+24
View File
@@ -176,6 +176,10 @@ void ShaderManagerDX9::VSSetColorUniform3(int creg, u32 color) {
pD3Ddevice->SetVertexShaderConstantF(creg, col, 1);
}
void ShaderManagerDX9::VSSetFloatUniform4(int creg, float data[4]) {
pD3Ddevice->SetVertexShaderConstantF(creg, data, 1);
}
void ShaderManagerDX9::VSSetFloat24Uniform3(int creg, const u32 data[3]) {
const u32 col[4] = {
data[0] >> 8, data[1] >> 8, data[2] >> 8, 0
@@ -475,6 +479,26 @@ void ShaderManagerDX9::VSUpdateUniforms(int dirtyUniforms) {
VSSetFloatArray(CONST_VS_UVSCALEOFFSET, uvscaleoff, 4);
}
if (dirtyUniforms & DIRTY_DEPTHRANGE) {
float viewZScale = gstate.getViewportZScale();
float viewZCenter = gstate.getViewportZCenter();
// Adjust for D3D projection matrix. We got squashed up to only 0-1, so we multiply
// the scale factor by 2, and add an offset.
// Given the way we do the rounding, the integer part of the offset is probably mostly irrelevant as we cancel
// it afterwards anyway.
viewZScale *= 2.0f;
viewZCenter -= 32767.5f;
float viewZInvScale;
if (viewZScale != 0.0) {
viewZInvScale = 1.0f / viewZScale;
} else {
viewZInvScale = 0.0;
}
float data[4] = { viewZScale, viewZCenter, viewZCenter, viewZInvScale };
VSSetFloatUniform4(CONST_VS_DEPTHRANGE, data);
}
// Lighting
if (dirtyUniforms & DIRTY_AMBIENT) {
VSSetColorUniform3Alpha(CONST_VS_AMBIENT, gstate.ambientcolor, gstate.getAmbientA());
+3
View File
@@ -57,6 +57,8 @@ enum {
DIRTY_UVSCALEOFFSET = (1 << 18), // this will be dirtied ALL THE TIME... maybe we'll need to do "last value with this shader compares"
DIRTY_TEXCLAMP = (1 << 19),
DIRTY_DEPTHRANGE = (1 << 20),
DIRTY_WORLDMATRIX = (1 << 21),
DIRTY_VIEWMATRIX = (1 << 22), // Maybe we'll fold this into projmatrix eventually
DIRTY_TEXMATRIX = (1 << 23),
@@ -143,6 +145,7 @@ private:
void VSSetFloat(int creg, float value);
void VSSetFloatArray(int creg, const float *value, int count);
void VSSetFloat24Uniform3(int creg, const u32 data[3]);
void VSSetFloatUniform4(int creg, float data[4]);
void Clear();
+5 -3
View File
@@ -745,10 +745,12 @@ void TransformDrawEngineDX9::ApplyDrawState(int prim) {
float zScale = gstate.getViewportZScale() / 65535.0f;
float zCenter = gstate.getViewportZCenter() / 65535.0f;
// Note - we lose the sign of the zscale here. Although I suppose we still keep it in gstate_c.vpDepth...
float depthRangeMin = zCenter - fabsf(zScale);
float depthRangeMax = zCenter + fabsf(zScale);
// Note - We lose the sign of the zscale here. But we keep it in gstate_c.vpDepth.
// That variable is only check for sign later so the multiplication by 2 isn't really necessary.
// It's unclear why we need this Z offset of 1 to match OpenGL, but this checks out in multiple games.
float depthRangeMin = zCenter - fabsf(zScale) - 0.5f/65535.0f;
float depthRangeMax = zCenter + fabsf(zScale) - 0.5f/65535.0f;
gstate_c.vpDepth = zScale * 2;
// D3D doesn't like viewports partially outside the target, so we
+21 -2
View File
@@ -226,6 +226,11 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) {
}
}
if (!gstate.isModeThrough()) {
WRITE(p, "float4 u_depthRange : register(c%i);\n", CONST_VS_DEPTHRANGE);
}
// And the "varyings".
if (useHWTransform) {
WRITE(p, "struct VS_IN { \n");
if (vertTypeIsSkinningEnabled(vertType)) {
@@ -279,6 +284,20 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) {
}
WRITE(p, "};\n");
// Confirmed: Through mode gets through exactly the same in GL and D3D in Phantasy Star: Text is 38023.0 in the test scene.
if (!gstate.isModeThrough()) {
// Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection.
// Not completely sure this is 100% right under DX9 as the Z range is different...
WRITE(p, "\nfloat4 depthRoundZVP(float4 v) {\n");
WRITE(p, " float z = v.z / v.w;\n");
WRITE(p, " z = z * u_depthRange.x + u_depthRange.y;\n");
WRITE(p, " z = floor(z);\n");
WRITE(p, " z = (z - u_depthRange.z) * u_depthRange.w;\n");
WRITE(p, " return float4(v.x, v.y, z * v.w, v.w);\n");
WRITE(p, "}\n\n");
}
WRITE(p, "VS_OUT main(VS_IN In) {\n");
WRITE(p, " VS_OUT Out = (VS_OUT)0; \n");
if (!useHWTransform) {
@@ -309,7 +328,7 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) {
if (gstate.isModeThrough()) {
WRITE(p, " Out.gl_Position = mul(float4(In.position.xyz, 1.0), u_proj_through);\n");
} else {
WRITE(p, " Out.gl_Position = mul(float4(In.position.xyz, 1.0), u_proj);\n");
WRITE(p, " Out.gl_Position = depthRoundZVP(mul(float4(In.position.xyz, 1.0), u_proj));\n");
}
} else {
// Step 1: World Transform / Skinning
@@ -398,7 +417,7 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) {
WRITE(p, " float4 viewPos = float4(mul(float4(worldpos, 1.0), u_view), 1.0);\n");
// Final view and projection transforms.
WRITE(p, " Out.gl_Position = mul(viewPos, u_proj);\n");
WRITE(p, " Out.gl_Position = depthRoundZVP(mul(viewPos, u_proj));\n");
// TODO: Declare variables for dots for shade mapping if needed.
+1
View File
@@ -87,5 +87,6 @@ int TranslateNumBonesDX9(int bones);
#define CONST_VS_LIGHTDIFFUSE 71
#define CONST_VS_LIGHTSPECULAR 75
#define CONST_VS_LIGHTAMBIENT 79
#define CONST_VS_DEPTHRANGE 83
};
+10 -4
View File
@@ -77,8 +77,8 @@ static const CommandTableEntry commandTable[] = {
{GE_CMD_FOG2, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, DIRTY_FOGCOEF, &GLES_GPU::Execute_FogCoef},
// Should these maybe flush?
{GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE},
{GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE},
{GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE},
{GE_CMD_MAXZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_DEPTHRANGE},
// Changes that dirty texture scaling.
{GE_CMD_TEXMAPMODE, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, DIRTY_UVSCALEOFFSET, &GLES_GPU::Execute_TexMapMode},
@@ -206,8 +206,8 @@ static const CommandTableEntry commandTable[] = {
{GE_CMD_VIEWPORTY1, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GLES_GPU::Execute_ViewportType},
{GE_CMD_VIEWPORTX2, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GLES_GPU::Execute_ViewportType},
{GE_CMD_VIEWPORTY2, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GLES_GPU::Execute_ViewportType},
{GE_CMD_VIEWPORTZ1, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GLES_GPU::Execute_ViewportType},
{GE_CMD_VIEWPORTZ2, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GLES_GPU::Execute_ViewportType},
{GE_CMD_VIEWPORTZ1, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, DIRTY_DEPTHRANGE, &GLES_GPU::Execute_ViewportZType},
{GE_CMD_VIEWPORTZ2, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, DIRTY_DEPTHRANGE, &GLES_GPU::Execute_ViewportZType},
// Region
{GE_CMD_REGION1, FLAG_FLUSHBEFOREONCHANGE | FLAG_EXECUTEONCHANGE, 0, &GLES_GPU::Execute_Region},
@@ -1014,6 +1014,12 @@ void GLES_GPU::Execute_ViewportType(u32 op, u32 diff) {
gstate_c.textureChanged |= TEXCHANGE_PARAMSONLY;
}
void GLES_GPU::Execute_ViewportZType(u32 op, u32 diff) {
gstate_c.framebufChanged = true;
gstate_c.textureChanged |= TEXCHANGE_PARAMSONLY;
shaderManager_->DirtyUniform(DIRTY_DEPTHRANGE);
}
void GLES_GPU::Execute_TexScaleU(u32 op, u32 diff) {
gstate_c.uv.uScale = getFloat24(op);
shaderManager_->DirtyUniform(DIRTY_UVSCALEOFFSET);
+1
View File
@@ -101,6 +101,7 @@ public:
void Execute_Scissor(u32 op, u32 diff);
void Execute_FramebufType(u32 op, u32 diff);
void Execute_ViewportType(u32 op, u32 diff);
void Execute_ViewportZType(u32 op, u32 diff);
void Execute_TexScaleU(u32 op, u32 diff);
void Execute_TexScaleV(u32 op, u32 diff);
void Execute_TexOffsetU(u32 op, u32 diff);
+21 -2
View File
@@ -167,6 +167,7 @@ LinkedShader::LinkedShader(Shader *vs, Shader *fs, u32 vertType, bool useHWTrans
numBones = TranslateNumBones(vertTypeGetNumBoneWeights(vertType));
else
numBones = 0;
u_depthRange = glGetUniformLocation(program, "u_depthRange");
#ifdef USE_BONE_ARRAY
u_bone = glGetUniformLocation(program, "u_bone");
@@ -233,6 +234,8 @@ LinkedShader::LinkedShader(Shader *vs, Shader *fs, u32 vertType, bool useHWTrans
if (u_texmtx != -1) availableUniforms |= DIRTY_TEXMATRIX;
if (u_stencilReplaceValue != -1) availableUniforms |= DIRTY_STENCILREPLACEVALUE;
if (u_blendFixA != -1 || u_blendFixB != -1 || u_fbotexSize != -1) availableUniforms |= DIRTY_SHADERBLEND;
if (u_depthRange != -1)
availableUniforms |= DIRTY_DEPTHRANGE;
// Looping up to numBones lets us avoid checking u_bone[i]
#ifdef USE_BONE_ARRAY
@@ -343,6 +346,10 @@ static void SetFloat24Uniform3(int uniform, const u32 data[3]) {
glUniform3fv(uniform, 1, (const GLfloat *)&col[0]);
}
static void SetFloatUniform4(int uniform, float data[4]) {
glUniform4fv(uniform, 1, data);
}
static void SetMatrix4x3(int uniform, const float *m4x3) {
float m4x4[16];
ConvertMatrix4x3To4x4(m4x4, m4x3);
@@ -442,7 +449,7 @@ void LinkedShader::UpdateUniforms(u32 vertType) {
if (dirty & DIRTY_PROJTHROUGHMATRIX)
{
Matrix4x4 proj_through;
proj_through.setOrtho(0.0f, gstate_c.curRTWidth, gstate_c.curRTHeight, 0, 0, 1);
proj_through.setOrtho(0.0f, gstate_c.curRTWidth, gstate_c.curRTHeight, 0, 0.0f, 1.0f);
glUniformMatrix4fv(u_proj_through, 1, GL_FALSE, proj_through.getReadPtr());
}
if (dirty & DIRTY_TEXENV) {
@@ -547,7 +554,7 @@ void LinkedShader::UpdateUniforms(u32 vertType) {
glUniform4fv(u_uvscaleoffset, 1, uvscaleoff);
}
if (dirty & DIRTY_TEXCLAMP) {
if ((dirty & DIRTY_TEXCLAMP) && u_texclamp != -1) {
const float invW = 1.0f / (float)gstate_c.curTextureWidth;
const float invH = 1.0f / (float)gstate_c.curTextureHeight;
const int w = gstate.getTextureWidth(0);
@@ -582,6 +589,18 @@ void LinkedShader::UpdateUniforms(u32 vertType) {
if (dirty & DIRTY_TEXMATRIX) {
SetMatrix4x3(u_texmtx, gstate.tgenMatrix);
}
if ((dirty & DIRTY_DEPTHRANGE) && u_depthRange != -1) {
float viewZScale = gstate.getViewportZScale();
float viewZCenter = gstate.getViewportZCenter();
float viewZInvScale;
if (viewZScale != 0.0) {
viewZInvScale = 1.0f / viewZScale;
} else {
viewZInvScale = 0.0;
}
float data[4] = { viewZScale, viewZCenter, viewZCenter, viewZInvScale };
SetFloatUniform4(u_depthRange, data);
}
if (dirty & DIRTY_STENCILREPLACEVALUE) {
glUniform1f(u_stencilReplaceValue, (float)gstate.getStencilTestRef() * (1.0f / 255.0f));
+9 -4
View File
@@ -88,6 +88,8 @@ public:
int u_view;
int u_texmtx;
int u_world;
int u_depthRange; // x,y = viewport xscale/xcenter. z,w=clipping minz/maxz (?)
#ifdef USE_BONE_ARRAY
int u_bone; // array, size is numBones
#else
@@ -132,10 +134,10 @@ public:
enum {
DIRTY_PROJMATRIX = (1 << 0),
DIRTY_PROJTHROUGHMATRIX = (1 << 1),
DIRTY_FOGCOLOR = (1 << 2),
DIRTY_FOGCOEF = (1 << 3),
DIRTY_TEXENV = (1 << 4),
DIRTY_ALPHACOLORREF = (1 << 5),
DIRTY_FOGCOLOR = (1 << 2),
DIRTY_FOGCOEF = (1 << 3),
DIRTY_TEXENV = (1 << 4),
DIRTY_ALPHACOLORREF = (1 << 5),
// 1 << 6 is free! Wait, not anymore...
DIRTY_STENCILREPLACEVALUE = (1 << 6),
@@ -155,7 +157,10 @@ enum {
DIRTY_SHADERBLEND = (1 << 17), // Used only for in-shader blending.
DIRTY_UVSCALEOFFSET = (1 << 18), // this will be dirtied ALL THE TIME... maybe we'll need to do "last value with this shader compares"
// Texclamp is fairly rare so let's share it's bit with DIRTY_DEPTHRANGE.
DIRTY_TEXCLAMP = (1 << 19),
DIRTY_DEPTHRANGE = (1 << 19),
DIRTY_WORLDMATRIX = (1 << 21),
DIRTY_VIEWMATRIX = (1 << 22), // Maybe we'll fold this into projmatrix eventually
+3 -3
View File
@@ -871,11 +871,11 @@ void TransformDrawEngine::ApplyDrawState(int prim) {
glstate.viewport.set(left, bottom, right - left, top - bottom);
float zScale = gstate.getViewportZScale() * (1.0f / 65535.0f);
float zCenter = gstate.getViewportZCenter() * (1.0f / 65535.0f);
float zScale = gstate.getViewportZScale();
float zCenter = gstate.getViewportZCenter();
float depthRangeMin = zCenter - zScale;
float depthRangeMax = zCenter + zScale;
glstate.depthRange.set(depthRangeMin, depthRangeMax);
glstate.depthRange.set(depthRangeMin * (1.0f / 65535.0f), depthRangeMax * (1.0f / 65535.0f));
#ifndef MOBILE_DEVICE
float minz = gstate.getDepthRangeMin() * (1.0f / 65535.0f);
+47 -2
View File
@@ -152,6 +152,33 @@ enum DoLightComputation {
LIGHT_FULL,
};
// Depth range and viewport
//
// After the multiplication with the projection matrix, we have a 4D vector in clip space.
// In OpenGL, Z is from -1 to 1, while in D3D, Z is from 0 to 1.
// PSP appears to use the OpenGL convention. As Z is from -1 to 1, and the viewport is represented
// by a center and a scale, to find the final Z value, all we need to do is to multiply by ZScale and
// add ZCenter - these are properly scaled to directly give a Z value in [0, 65535].
//
// z = vec.z * ViewportZScale + ViewportZCenter;
//
// That will give us the final value between 0 and 65535, which we can simply floor to simulate
// the limited precision of the PSP's depth buffer. Then we convert it back:
// z = floor(z);
//
// vec.z = (z - ViewportZCenter) / ViewportZScale;
//
// Now, the regular machinery will take over and do the calculation again.
//
// All this above is for full transform mode.
// In through mode, the Z coordinate just goes straight through and there is no perspective division.
// We simulate this of course with pretty much an identity matrix. Rounding Z becomes very easy.
//
// TODO: Skip all this if we can actually get a 16-bit depth buffer along with stencil, which
// is a bit of a rare configuration, although quite common on mobile.
void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransform) {
char *p = buffer;
@@ -214,6 +241,7 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
boneWeightDecl = boneWeightInDecl;
}
bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled() && !gstate.isModeThrough();
bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear();
bool doTextureProjection = gstate.getUVGenMode() == GE_TEXMAP_TEXTURE_MATRIX;
@@ -336,6 +364,10 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
WRITE(p, "uniform highp vec2 u_fogcoef;\n");
}
if (!gstate.isModeThrough()) {
WRITE(p, "uniform highp vec4 u_depthRange;\n");
}
WRITE(p, "%s %s lowp vec4 v_color0;\n", shading, varying);
if (lmode) {
WRITE(p, "%s %s lowp vec3 v_color1;\n", shading, varying);
@@ -357,6 +389,18 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
}
}
// See comment above this function (GenerateVertexShader).
if (!gstate.isModeThrough()) {
// Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection.
WRITE(p, "\nvec4 depthRoundZVP(vec4 v) {\n");
WRITE(p, " float z = v.z / v.w;\n");
WRITE(p, " z = z * u_depthRange.x + u_depthRange.y;\n");
WRITE(p, " z = floor(z);\n");
WRITE(p, " z = (z - u_depthRange.z) * u_depthRange.w;\n");
WRITE(p, " return vec4(v.x, v.y, z * v.w, v.w);\n");
WRITE(p, "}\n\n");
}
WRITE(p, "void main() {\n");
if (!useHWTransform) {
@@ -383,7 +427,8 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
if (gstate.isModeThrough()) {
WRITE(p, " gl_Position = u_proj_through * vec4(position.xyz, 1.0);\n");
} else {
WRITE(p, " gl_Position = u_proj * vec4(position.xyz, 1.0);\n");
// The viewport is used in this case, so need to compensate for that.
WRITE(p, " gl_Position = depthRoundZVP(u_proj * vec4(position.xyz, 1.0));\n");
}
} else {
// Step 1: World Transform / Skinning
@@ -476,7 +521,7 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
WRITE(p, " vec4 viewPos = u_view * vec4(worldpos, 1.0);\n");
// Final view and projection transforms.
WRITE(p, " gl_Position = u_proj * viewPos;\n");
WRITE(p, " gl_Position = depthRoundZVP(u_proj * viewPos);\n");
// TODO: Declare variables for dots for shade mapping if needed.