diff --git a/hw/xbox/nv2a/nv2a_regs.h b/hw/xbox/nv2a/nv2a_regs.h index ae9e192ef2..9dd9b18a03 100644 --- a/hw/xbox/nv2a/nv2a_regs.h +++ b/hw/xbox/nv2a/nv2a_regs.h @@ -1056,6 +1056,7 @@ # define NV097_SET_TEXGEN_VIEW_MODEL_LOCAL_VIEWER 0 # define NV097_SET_TEXGEN_VIEW_MODEL_INFINITE_VIEWER 1 # define NV097_SET_FOG_PLANE 0x000009D0 +# define NV097_SET_SPECULAR_PARAMS 0x000009E0 # define NV097_SET_SCENE_AMBIENT_COLOR 0x00000A10 # define NV097_SET_VIEWPORT_OFFSET 0x00000A20 # define NV097_SET_POINT_PARAMS 0x00000A30 @@ -1259,6 +1260,7 @@ # define NV097_SET_CLEAR_RECT_HORIZONTAL 0x00001D98 # define NV097_SET_CLEAR_RECT_VERTICAL 0x00001D9C # define NV097_SET_SPECULAR_FOG_FACTOR 0x00001E20 +# define NV097_SET_SPECULAR_PARAMS_BACK 0x00001E28 # define NV097_SET_COMBINER_COLOR_OCW 0x00001E40 # define NV097_SET_COMBINER_CONTROL 0x00001E60 # define NV097_SET_SHADOW_ZSLOPE_THRESHOLD 0x00001E68 diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index b3c72bb2c2..b4980e95e0 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -230,8 +230,12 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } /* Lighting */ - if (state->lighting) { - + if (!state->lighting) { + mstring_append(body, " oD0 = diffuse;\n"); + mstring_append(body, " oD1 = specular;\n"); + mstring_append(body, " oB0 = backDiffuse;\n"); + mstring_append(body, " oB1 = backSpecular;\n"); + } else { //FIXME: Do 2 passes if we want 2 sided-lighting? static char alpha_source_diffuse[] = "diffuse.a"; @@ -269,12 +273,6 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz continue; } - /* FIXME: It seems that we only have to handle the surface colors if - * they are not part of the material [= vertex colors]. - * If they are material the cpu will premultiply light - * colors - */ - mstring_append_fmt(body, "/* Light %d */ {\n", i); if (state->light[i] == LIGHT_LOCAL @@ -310,14 +308,10 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz u, i, u, i); mstring_append_fmt(body, " float attenuation = 1.0;\n" - " float nDotVP = max(0.0, dot(tNormal, normalize(vec3(lightInfiniteDirection%d))));\n" - " float nDotHV = max(0.0, dot(tNormal, vec3(lightInfiniteHalfVector%d)));\n", + " float nDotVP = max(0.0, dot(tNormal, normalize(lightInfiniteDirection%d)));\n" + " float nDotHV = max(0.0, dot(tNormal, lightInfiniteHalfVector%d));\n", i, i); - /* FIXME: Do specular */ - - /* FIXME: tBackDiffuse */ - break; case LIGHT_LOCAL: /* Everything done already */ @@ -349,12 +343,12 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz " if (nDotVP == 0.0) {\n" " pf = 0.0;\n" " } else {\n" - " pf = pow(nDotHV, /* specular(l, m, n, l1, m1, n1) */ 0.001);\n" + " pf = pow(nDotHV, %f);\n" " }\n" " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n" " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n" - " vec3 lightSpecular = lightSpecularColor(%d) * pf;\n", - i, i, i); + " vec3 lightSpecular = lightSpecularColor(%d) * attenuation * pf;\n", + state->specular_power, i, i, i); mstring_append(body, " oD0.xyz += lightAmbient;\n"); @@ -374,26 +368,44 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz break; } - mstring_append(body, - " oD1.xyz += specular.xyz * lightSpecular;\n"); + switch (state->specular_src) { + case MATERIAL_COLOR_SRC_MATERIAL: + mstring_append(body, + " oD1.xyz += lightSpecular;\n"); + break; + case MATERIAL_COLOR_SRC_DIFFUSE: + mstring_append(body, + " oD1.xyz += diffuse.xyz * lightSpecular;\n"); + break; + case MATERIAL_COLOR_SRC_SPECULAR: + mstring_append(body, + " oD1.xyz += specular.xyz * lightSpecular;\n"); + break; + } mstring_append(body, "}\n"); } - } else { - mstring_append(body, " oD0 = diffuse;\n"); - mstring_append(body, " oD1 = specular;\n"); - } - mstring_append(body, " oB0 = backDiffuse;\n"); - mstring_append(body, " oB1 = backSpecular;\n"); + /* TODO: Implement two-sided lighting */ + mstring_append(body, " oB0 = backDiffuse;\n"); + mstring_append(body, " oB1 = backSpecular;\n"); + } if (!state->specular_enable) { mstring_append(body, " oD1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); mstring_append(body, " oB1 = vec4(0.0, 0.0, 0.0, 1.0);\n"); } else { if (!state->separate_specular) { - mstring_append(body, " oD1 = specular;\n"); - mstring_append(body, " oB1 = backSpecular;\n"); + if (state->lighting) { + mstring_append(body, + " oD0.xyz += oD1.xyz;\n" + " oB0.xyz += oB1.xyz;\n" + ); + } + mstring_append(body, + " oD1 = specular;\n" + " oB1 = backSpecular;\n" + ); } if (state->ignore_specular_alpha) { mstring_append(body, diff --git a/hw/xbox/nv2a/pgraph/methods.h.inc b/hw/xbox/nv2a/pgraph/methods.h.inc index 894dff7709..322c72be31 100644 --- a/hw/xbox/nv2a/pgraph/methods.h.inc +++ b/hw/xbox/nv2a/pgraph/methods.h.inc @@ -96,6 +96,7 @@ DEF_METHOD_RANGE(NV097, SET_FOG_PARAMS, 3) DEF_METHOD_RANGE(NV097, SET_TEXGEN_PLANE_S, 4*4*4) DEF_METHOD(NV097, SET_TEXGEN_VIEW_MODEL) DEF_METHOD_RANGE(NV097, SET_FOG_PLANE, 4) +DEF_METHOD_RANGE(NV097, SET_SPECULAR_PARAMS, 6) DEF_METHOD_RANGE(NV097, SET_SCENE_AMBIENT_COLOR, 3) DEF_METHOD_RANGE(NV097, SET_VIEWPORT_OFFSET, 4) DEF_METHOD_RANGE(NV097, SET_POINT_PARAMS, 8) @@ -178,6 +179,7 @@ DEF_METHOD(NV097, CLEAR_SURFACE) DEF_METHOD(NV097, SET_CLEAR_RECT_HORIZONTAL) DEF_METHOD(NV097, SET_CLEAR_RECT_VERTICAL) DEF_METHOD_RANGE(NV097, SET_SPECULAR_FOG_FACTOR, 2) +DEF_METHOD_RANGE(NV097, SET_SPECULAR_PARAMS_BACK, 6) DEF_METHOD(NV097, SET_SHADER_CLIP_PLANE_MODE) DEF_METHOD_RANGE(NV097, SET_COMBINER_COLOR_OCW, 8) DEF_METHOD(NV097, SET_COMBINER_CONTROL) diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index c63b17b4db..d878326c9a 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -19,6 +19,8 @@ * License along with this library; if not, see . */ +#include + #include "hw/xbox/nv2a/nv2a_int.h" #include "ui/xemu-notifications.h" #include "ui/xemu-settings.h" @@ -1803,6 +1805,42 @@ DEF_METHOD_INC(NV097, SET_FOG_PLANE) pg->vsh_constants_dirty[NV_IGRAPH_XF_XFCTX_FOG] = true; } +// Based on curve fitting to observed values from DirectX uses. +// x = -log2(30.80722523) / power +// c3_param = -1.01441946 * pow(exp2( x ), 2) + 0.01451685 +#define SPECULAR_POWER_NUMERATOR_CONSTANT 30.80722523f +#define SPECULAR_POWER_COEFFICIENT_A -1.0141946f +#define SPECULAR_POWER_CONSTANT_COEFFICIENT 0.01451685f +//#define SPECULAR_POWER_LOG_CONSTANT (-2.f * log2(SPECULAR_POWER_NUMERATOR_CONSTANT)) +#define SPECULAR_POWER_LOG_CONSTANT -9.8903942108154297f +static float reconstruct_specular_power_from_c3(uint32_t c3_parameter) +{ + float c3 = *(float*)&c3_parameter; + + // FIXME: This handling is not correct, but is visually distinct without causing a crash. + // It does not appear possible for a DirectX-generated value to be positive, so while this differs from hardware + // behavior, it may be irrelevant in practice. + float invert = 1.f; + if (c3 > 0.0f) { + invert = -1.f; + c3 *= invert; + } + + c3 -= SPECULAR_POWER_CONSTANT_COEFFICIENT; + float ret = SPECULAR_POWER_LOG_CONSTANT / log2(c3 / SPECULAR_POWER_COEFFICIENT_A); + assert(!isnan(ret) && "Failed to reconstruct specular power factor"); + return ret * invert; +} + +DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS) +{ + int slot = (method - NV097_SET_SPECULAR_PARAMS) / 4; + NV2A_DPRINTF("NV097_SET_SPECULAR_PARAMS[%d] 0x%X\n", slot, parameter); + if (slot == 3) { + pg->specular_power = reconstruct_specular_power_from_c3(parameter); + } +} + DEF_METHOD_INC(NV097, SET_SCENE_AMBIENT_COLOR) { int slot = (method - NV097_SET_SCENE_AMBIENT_COLOR) / 4; @@ -2740,6 +2778,15 @@ DEF_METHOD_INC(NV097, SET_SPECULAR_FOG_FACTOR) pgraph_reg_w(pg, NV_PGRAPH_SPECFOGFACTOR0 + slot*4, parameter); } +DEF_METHOD_INC(NV097, SET_SPECULAR_PARAMS_BACK) +{ + int slot = (method - NV097_SET_SPECULAR_PARAMS_BACK) / 4; + NV2A_DPRINTF("SET_SPECULAR_PARAMS_BACK[%d] 0x%X\n", slot, parameter); + if (slot == 3) { + pg->specular_power_back = reconstruct_specular_power_from_c3(parameter); + } +} + DEF_METHOD(NV097, SET_SHADER_CLIP_PLANE_MODE) { pgraph_reg_w(pg, NV_PGRAPH_SHADERCLIPMODE, parameter); diff --git a/hw/xbox/nv2a/pgraph/pgraph.h b/hw/xbox/nv2a/pgraph/pgraph.h index 64b671e71d..156b3010b6 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.h +++ b/hw/xbox/nv2a/pgraph/pgraph.h @@ -197,6 +197,9 @@ typedef struct PGRAPHState { float light_local_position[NV2A_MAX_LIGHTS][3]; float light_local_attenuation[NV2A_MAX_LIGHTS][3]; + float specular_power; + float specular_power_back; + float point_params[8]; VertexAttribute vertex_attributes[NV2A_VERTEXSHADER_ATTRIBUTES]; diff --git a/hw/xbox/nv2a/pgraph/shaders.c b/hw/xbox/nv2a/pgraph/shaders.c index ce80dc127a..79f66026d9 100644 --- a/hw/xbox/nv2a/pgraph/shaders.c +++ b/hw/xbox/nv2a/pgraph/shaders.c @@ -97,6 +97,9 @@ ShaderState pgraph_get_shader_state(PGRAPHState *pg) state.ignore_specular_alpha = !GET_MASK( pgraph_reg_r(pg, NV_PGRAPH_CSV0_C), NV_PGRAPH_CSV0_C_ALPHA_FROM_MATERIAL_SPECULAR); + state.specular_power = pg->specular_power; + state.specular_power_back = pg->specular_power_back; + /* vertex program stuff */ state.vertex_program = vertex_program, state.z_perspective = pgraph_reg_r(pg, NV_PGRAPH_CONTROL_0) & diff --git a/hw/xbox/nv2a/pgraph/shaders.h b/hw/xbox/nv2a/pgraph/shaders.h index 9496cbbea2..b384b679bf 100644 --- a/hw/xbox/nv2a/pgraph/shaders.h +++ b/hw/xbox/nv2a/pgraph/shaders.h @@ -81,6 +81,8 @@ typedef struct ShaderState { bool separate_specular; bool ignore_specular_alpha; + float specular_power; + float specular_power_back; bool lighting; enum VshLight light[NV2A_MAX_LIGHTS];