From 177a4d78f868d335c240e0d34bf6514b17f8fb36 Mon Sep 17 00:00:00 2001
From: Jannik Vogel <email@jannikvogel.de>
Date: Sat, 25 Jul 2015 21:01:17 +0200
Subject: [PATCH] Foggen + Fog WIP

---
 hw/xbox/nv2a.c         | 56 +++++++++++++++++++++++++++-
 hw/xbox/nv2a_psh.c     |  5 +--
 hw/xbox/nv2a_shaders.c | 84 +++++++++++++++++++++++++++++++++++++++++-
 hw/xbox/nv2a_shaders.h |  4 ++
 hw/xbox/nv2a_vsh.h     | 21 +++++++++++
 5 files changed, 164 insertions(+), 6 deletions(-)

diff --git a/hw/xbox/nv2a.c b/hw/xbox/nv2a.c
index 68c83d3fcd..c2a59846d6 100644
--- a/hw/xbox/nv2a.c
+++ b/hw/xbox/nv2a.c
@@ -2838,6 +2838,21 @@ static void pgraph_bind_shaders(PGRAPHState *pg)
         }
     }
 
+    /* Fog */
+    state.fog_enable = pg->regs[NV_PGRAPH_CONTROL_3]
+                           & NV_PGRAPH_CONTROL_3_FOGENABLE;
+    if (state.fog_enable) {
+        /*FIXME: Use CSV0_D? */
+        state.fog_mode = GET_MASK(pg->regs[NV_PGRAPH_CONTROL_3],
+                                  NV_PGRAPH_CONTROL_3_FOG_MODE);
+        state.foggen = GET_MASK(pg->regs[NV_PGRAPH_CSV0_D],
+                                NV_PGRAPH_CSV0_D_FOGGENMODE);
+    } else {
+        /* FIXME: Do we still pass the fogmode? */
+        state.fog_mode = 0;
+        state.foggen = 0;
+    }
+
     /* Texture matrices */
     for (i = 0; i < 4; i++) {
         state.texture_matrix_enable[i] = pg->texture_matrix_enable[i];
@@ -2962,6 +2977,39 @@ static void pgraph_bind_shaders(PGRAPHState *pg)
 
     }
 
+    /* Fog */
+    {
+        GLint loc;
+        uint32_t fog_color = pg->regs[NV_PGRAPH_FOGCOLOR];
+        loc  = glGetUniformLocation(pg->shader_binding->gl_program, "fogColor");
+        if (loc != -1) {
+            glUniform4f(loc,
+                        GET_MASK(fog_color, NV_PGRAPH_FOGCOLOR_RED) / 255.0,
+                        GET_MASK(fog_color, NV_PGRAPH_FOGCOLOR_GREEN) / 255.0,
+                        GET_MASK(fog_color, NV_PGRAPH_FOGCOLOR_BLUE) / 255.0,
+                        GET_MASK(fog_color, NV_PGRAPH_FOGCOLOR_ALPHA) / 255.0);
+        }
+
+        /* FIXME: PGRAPH regs have a 16 byte stride in emulation! can't just
+         *        upload this as an array =(
+         */
+        loc = glGetUniformLocation(pg->shader_binding->gl_program,
+                                   "fogParam[0]");
+        if (loc != -1) {
+            glUniform1f(loc, *(float*)&pg->regs[NV_PGRAPH_FOGPARAM0]);
+        }
+        loc = glGetUniformLocation(pg->shader_binding->gl_program,
+                                   "fogParam[1]");
+        if (loc != -1) {
+            glUniform1f(loc, *(float*)&pg->regs[NV_PGRAPH_FOGPARAM1]);
+        }
+
+        loc = glGetUniformLocation(pg->shader_binding->gl_program, "fogPlane");
+        if (loc != -1) {
+            glUniform4fv(loc, 1, pg->fog_plane);
+        }
+    }
+
     /* For each vertex weight */
     for (i = 0; i < 4; i++) {
         char name[32];
@@ -4358,7 +4406,11 @@ static void pgraph_method(NV2AState *d,
     case NV097_SET_FOG_PARAMS ...
             NV097_SET_FOG_PARAMS + 8:
         slot = (class_method - NV097_SET_FOG_PARAMS) / 4;
-        pg->regs[NV_PGRAPH_FOGPARAM0 + slot*4] = parameter;
+        if (slot < 2) {
+            pg->regs[NV_PGRAPH_FOGPARAM0 + slot*4] = parameter;
+        } else {
+            /* FIXME: No idea where slot = 2 is */
+        }
         break;
     case NV097_SET_TEXGEN_VIEW_MODEL:
         SET_MASK(pg->regs[NV_PGRAPH_CSV0_D], NV_PGRAPH_CSV0_D_TEXGEN_REF,
@@ -6783,7 +6835,7 @@ static void pgraph_method_log(unsigned int subchannel,
     static unsigned int last = 0;
     static unsigned int count = 0;
     if (last == 0x1800 && method != last) {
-        NV2A_DPRINTF("pgraph method (%d) 0x%x * %d",
+        NV2A_GL_DPRINTF(true, "pgraph method (%d) 0x%x * %d",
                      subchannel, last, count);
     }
     if (method != 0x1800) {
diff --git a/hw/xbox/nv2a_psh.c b/hw/xbox/nv2a_psh.c
index 47d2baeb6c..a34c1b6a95 100644
--- a/hw/xbox/nv2a_psh.c
+++ b/hw/xbox/nv2a_psh.c
@@ -280,9 +280,8 @@ static QString* get_var(struct PixelShader *ps, int reg, bool is_dest)
             return qstring_from_str("c_0_1");
         }
         break;
-    case PS_REGISTER_FOG: // TODO
-        //return qstring_from_str("fog");
-        return qstring_from_str("vec4(1.0)");
+    case PS_REGISTER_FOG:
+        return qstring_from_str("clamp(pFog, 0.0, 1.0)");
     case PS_REGISTER_V0:
         return qstring_from_str("v0");
     case PS_REGISTER_V1:
diff --git a/hw/xbox/nv2a_shaders.c b/hw/xbox/nv2a_shaders.c
index f94a286340..e18940f8cd 100644
--- a/hw/xbox/nv2a_shaders.c
+++ b/hw/xbox/nv2a_shaders.c
@@ -115,6 +115,9 @@ static QString* generate_fixed_function(const ShaderState state,
     qstring_append(s,
 "\n"
 /* FIXME: Add these uniforms using code when they are used */
+"uniform vec4 fogColor;\n"
+"uniform vec4 fogPlane;\n"
+"uniform float fogParam[2];\n"
 "uniform mat4 texMat0;\n"
 "uniform mat4 texMat1;\n"
 "uniform mat4 texMat2;\n"
@@ -255,6 +258,85 @@ static QString* generate_fixed_function(const ShaderState state,
         }
     }
 
+    /* Fog */
+    if (state.fog_enable) {
+
+        /* From: https://www.opengl.org/registry/specs/NV/fog_distance.txt */
+        switch(state.foggen) {
+        case FOGGEN_SPEC_ALPHA:
+            assert(false); /* FIXME: Do this before or after calculations in VSH? */
+            if (state.fixed_function) {
+                /* FIXME: Do we have to clamp here? */
+                qstring_append(s, "float fogDistance = clamp(specular.a, 0.0, 1.0);\n");
+            } else if (state.vertex_program) {
+                qstring_append(s, "float fogDistance = oD1.a;\n");
+            } else {
+                assert(false);
+            }
+            break;
+        case FOGGEN_RADIAL:
+            qstring_append(s, "float fogDistance = length(tPosition.xyz)");
+            break;
+        case FOGGEN_PLANAR:
+        case FOGGEN_ABS_PLANAR:
+            qstring_append(s, "float fogDistance = dot(fogPlane.xyz,tPosition.xyz)+fogPlane.w;\n");
+            if (state.foggen == FOGGEN_ABS_PLANAR) {
+                qstring_append(s, "fogDistance = abs(fogDistance);\n");
+            }
+            break;
+        case FOGGEN_FOG_X:
+            if (state.fixed_function) {
+                qstring_append(s, "float fogDistance = fogCoord;\n");
+            } else if (state.vertex_program) {
+                qstring_append(s, "float fogDistance = oFog.x;\n");
+            } else {
+                assert(false);
+            }
+            break;
+        default:
+            assert(false);
+            break;
+        }
+
+        switch (state.fog_mode) {
+        case FOG_MODE_LINEAR:
+        case FOG_MODE_LINEAR_ABS:
+            qstring_append(s, "float fogFactor = fogDistance * fogParam[1] + fogParam[0];\n");
+            qstring_append(s, "fogFactor -= 1.0;\n"); /* FIXME: WHHYYY?!! */
+            break;
+        case FOG_MODE_EXP:
+        case FOG_MODE_EXP_ABS:
+            assert(false); /* FIXME: fogParam[0] and fogParam[0] ?? */
+            qstring_append(s, "float fogFactor = exp(fogDistance);\n");
+            break;
+        case FOG_MODE_EXP2:
+        case FOG_MODE_EXP2_ABS:
+            assert(false); /* FIXME: fogParam[0] and fogParam[0] ?? */
+            qstring_append(s, "float fogFactor = exp(fogDistance * fogDistance);\n");
+            break;
+        default:
+            assert(false);
+            break;
+        }
+        /* Calculate absolute for the modes which need it */
+        switch (state.fog_mode) {
+        case FOG_MODE_LINEAR_ABS:
+        case FOG_MODE_EXP_ABS:
+        case FOG_MODE_EXP2_ABS:
+            qstring_append(s, "fogFactor = abs(fogFactor);\n");
+            break;
+        default:
+            break;
+        }
+        /* FIXME: What about fog alpha?! */
+        qstring_append(s, "vec4 tFog = vec4(fogColor.rgb, fogFactor);\n");
+    } else {
+        /* FIXME: Is the fog still calculated / passed somehow?!
+         * Maybe vec4(fogColor, fogCoord) ?
+         */
+        qstring_append(s, "vec4 tFog = vec4(0.0);\n");
+    }
+
     /* If skinning is off the composite matrix already includes the MV matrix */
     if (state.skinning == SKINNING_OFF) {
         qstring_append(s, "tPosition = position;\n");
@@ -273,7 +355,7 @@ static QString* generate_fixed_function(const ShaderState state,
     qstring_append(s, "vtx.D1 = specular * vtx.inv_w;\n");
     qstring_append(s, "vtx.B0 = backDiffuse * vtx.inv_w;\n");
     qstring_append(s, "vtx.B1 = backSpecular * vtx.inv_w;\n");
-    qstring_append(s, "vtx.Fog = vec4(0.0,0.0,0.0,1.0) * vtx.inv_w;\n");
+    qstring_append(s, "vtx.Fog = tFog * vtx.inv_w;\n");
     qstring_append(s, "vtx.T0 = tTexture0 *  vtx.inv_w;\n");
     qstring_append(s, "vtx.T1 = tTexture1 * vtx.inv_w;\n");
     qstring_append(s, "vtx.T2 = tTexture2 * vtx.inv_w;\n");
diff --git a/hw/xbox/nv2a_shaders.h b/hw/xbox/nv2a_shaders.h
index 56f82b8707..3271cb5415 100644
--- a/hw/xbox/nv2a_shaders.h
+++ b/hw/xbox/nv2a_shaders.h
@@ -65,6 +65,10 @@ typedef struct ShaderState {
     bool texture_matrix_enable[4];
     enum VshTexgen texgen[4][4];
 
+    bool fog_enable;
+    enum VshFoggen foggen;
+    enum VshFogMode fog_mode;
+
     enum VshSkinning skinning;
 
     bool normalization;
diff --git a/hw/xbox/nv2a_vsh.h b/hw/xbox/nv2a_vsh.h
index bb23b1dad9..8cb49e9ccf 100644
--- a/hw/xbox/nv2a_vsh.h
+++ b/hw/xbox/nv2a_vsh.h
@@ -33,6 +33,27 @@ enum VshTexgen {
     TEXGEN_REFLECTION_MAP,
 };
 
+enum VshFogMode {
+    FOG_MODE_LINEAR,
+    FOG_MODE_EXP,
+    FOG_MODE_ERROR2, /* Doesn't exist */
+    FOG_MODE_EXP2,
+    FOG_MODE_LINEAR_ABS,
+    FOG_MODE_EXP_ABS,
+    FOG_MODE_ERROR6, /* Doesn't exist */
+    FOG_MODE_EXP2_ABS
+};
+
+enum VshFoggen {
+    FOGGEN_SPEC_ALPHA,
+    FOGGEN_RADIAL,
+    FOGGEN_PLANAR,
+    FOGGEN_ABS_PLANAR,
+    FOGGEN_ERROR4,
+    FOGGEN_ERROR5,
+    FOGGEN_FOG_X
+};
+
 enum VshSkinning {
     SKINNING_OFF,
     SKINNING_1WEIGHTS,