diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 3529006898..89ed6ecfd3 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -122,6 +122,7 @@ typedef struct ShaderBinding { GLint light_infinite_direction_loc[NV2A_MAX_LIGHTS]; GLint light_local_position_loc[NV2A_MAX_LIGHTS]; GLint light_local_attenuation_loc[NV2A_MAX_LIGHTS]; + int specular_power_loc; GLint clip_region_loc[8]; diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index ad1c21f4a2..ad2f3e1b9b 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -193,8 +193,11 @@ static void update_shader_constant_locations(ShaderBinding *binding) if (binding->state.fixed_function) { binding->material_alpha_loc = glGetUniformLocation(binding->gl_program, "material_alpha"); + binding->specular_power_loc = + glGetUniformLocation(binding->gl_program, "specularPower"); } else { binding->material_alpha_loc = -1; + binding->specular_power_loc = -1; } } @@ -836,6 +839,10 @@ static void shader_update_constants(PGRAPHState *pg, ShaderBinding *binding, } } + if (binding->specular_power_loc != -1) { + glUniform1f(binding->specular_power_loc, pg->specular_power); + } + /* estimate the viewport by assuming it matches the surface ... */ unsigned int aa_width = 1, aa_height = 1; pgraph_apply_anti_aliasing_factor(pg, &aa_width, &aa_height); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 7474280c37..4bfd788c65 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -238,6 +238,8 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } else { //FIXME: Do 2 passes if we want 2 sided-lighting? + mstring_append_fmt(uniforms, "%sfloat specularPower;\n", u); + static char alpha_source_diffuse[] = "diffuse.a"; static char alpha_source_specular[] = "specular.a"; static char alpha_source_material[] = "material_alpha"; @@ -301,7 +303,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz " float nDotVP = max(0.0, dot(tNormal, VP));\n" " float nDotHV = max(0.0, dot(tNormal, halfVector));\n", i, i, i, i, i, - state->local_eye ? "VPeye" : "vec3(0.0, 0.0, -1.0)" + state->local_eye ? "VPeye" : "vec3(0.0, 0.0, 0.0)" ); } @@ -361,27 +363,15 @@ 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, %f);\n" + " pf = pow(nDotHV, specularPower);\n" " }\n" " vec3 lightAmbient = lightAmbientColor(%d) * attenuation;\n" " vec3 lightDiffuse = lightDiffuseColor(%d) * attenuation * nDotVP;\n" " vec3 lightSpecular = lightSpecularColor(%d) * attenuation * pf;\n", - state->specular_power, i, i, i); + i, i, i); - switch (state->ambient_src) { - case MATERIAL_COLOR_SRC_MATERIAL: - mstring_append(body, - " oD0.rgb += lightAmbient;\n"); - break; - case MATERIAL_COLOR_SRC_DIFFUSE: - mstring_append(body, - " oD0.rgb += diffuse.rgb * lightAmbient;\n"); - break; - case MATERIAL_COLOR_SRC_SPECULAR: - mstring_append(body, - " oD0.rgb += specular.rgb * lightAmbient;\n"); - break; - } + mstring_append(body, + " oD0.xyz += lightAmbient;\n"); switch (state->diffuse_src) { case MATERIAL_COLOR_SRC_MATERIAL: diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index cc6a3013c3..01a3c133f5 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -1814,39 +1814,110 @@ 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; +struct CurveCoefficients { + float a; + float b; + float c; +}; - // 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; +static const struct CurveCoefficients curve_coefficients[] = { + {1.000108475163, -9.838607076280, 54.829089549713}, + {1.199164441703, -3.292603784852, 7.799987995214}, + {8.653441252033, 29.189473787191, 43.586027561823}, + {-531.307758450301, 117.398468683934, 113.155490738338}, + {-4.662713151292, 1.221108944572, 1.217360986939}, + {-124.435242105211, 35.401219563514, 35.408114377045}, + {10672560.259502287954, 21565843.555823743343, 10894794.336297152564}, + {-51973801.463933646679, -104199997.554352939129, -52225454.356278456748}, + {972270.324080004124, 2025882.096547174733, 1054898.052467488218}, +}; + +static const float kCoefficient0StepPoints[] = { + -0.022553957999, // power = 1.25 + -0.421539008617, // power = 4.00 + -0.678715527058, // power = 9.00 + -0.838916420937, // power = 20.00 + -0.961754500866, // power = 90.00 + -0.990773200989, // power = 375.00 + -0.994858562946, // power = 650.00 + -0.996561050415, // power = 1000.00 + -0.999547004700, // power = 1250.00 +}; + +static float reconstruct_quadratic(float c0, const struct CurveCoefficients *coefficients) { + return coefficients->a + coefficients->b * c0 + coefficients->c * c0 * c0; +} + +static float reconstruct_saturation_growth_rate(float c0, const struct CurveCoefficients *coefficients) { + return (coefficients->a * c0) / (coefficients->b + coefficients->c * c0); +} + +static float (* const reconstruct_func_map[])(float, const struct CurveCoefficients *) = { + reconstruct_quadratic, // 1.0..1.25 max error 0.01 % + reconstruct_quadratic, // 1.25..4.0 max error 2.2 % + reconstruct_quadratic, // 4.0..9.0 max error 2.3 % + reconstruct_saturation_growth_rate, // 9.0..20.0 max error 1.4 % + reconstruct_saturation_growth_rate, // 20.0..90.0 max error 2.1 % + reconstruct_saturation_growth_rate, // 90.0..375.0 max error 2.8% + reconstruct_quadratic, // 375..650 max error 1.0 % + reconstruct_quadratic, // 650..1000 max error 1.7% + reconstruct_quadratic, // 1000..1250 max error 1.0% +}; + +static float reconstruct_specular_power(const float *params) { + // See https://github.com/dracc/xgu/blob/db3172d8c983629f0dc971092981846da22438ae/xgux.h#L279 + + // Values < 1.0 will result in a positive c1 and (c2 - c0 * 2) will be very + // close to the original value. + if (params[1] > 0.0f && params[2] < 1.0f) { + return params[2] - (params[0] * 2.0f); + } + + float c0 = params[0]; + float c3 = params[3]; + // FIXME: This handling is not correct, but is distinct without crashing. + // 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. + if (c0 > 0.0f || c3 > 0.0f) { + return 0.0001f; + } + + float reconstructed_power = 0.f; + for (uint32_t i = 0; i < sizeof(kCoefficient0StepPoints) / sizeof(kCoefficient0StepPoints[0]); ++i) { + if (c0 > kCoefficient0StepPoints[i]) { + reconstructed_power = reconstruct_func_map[i](c0, &curve_coefficients[i]); + break; } + } - 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; + float reconstructed_half_power = 0.f; + for (uint32_t i = 0; i < sizeof(kCoefficient0StepPoints) / sizeof(kCoefficient0StepPoints[0]); ++i) { + if (c3 > kCoefficient0StepPoints[i]) { + reconstructed_half_power = reconstruct_func_map[i](c3, &curve_coefficients[i]); + break; + } + } + + // The range can be extended beyond 1250 by using the half power params. This + // will only work for DirectX generated values, arbitrary params could + // erroneously trigger this. + // + // There are some very low power (~1) values that have inverted powers, but + // they are easily identified by comparatively high c0 parameters. + if (reconstructed_power == 0.f || (reconstructed_half_power > reconstructed_power && c0 < -0.1f)) { + return reconstructed_half_power * 2.f; + } + + return reconstructed_power; } 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); + pg->specular_params[slot] = *(float *)¶meter; + if (slot == 5) { + pg->specular_power = reconstruct_specular_power(pg->specular_params); } } @@ -2831,9 +2902,9 @@ DEF_METHOD_INC(NV097, SET_SPECULAR_FOG_FACTOR) 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); + pg->specular_params_back[slot] = *(float *)¶meter; + if (slot == 5) { + pg->specular_power_back = reconstruct_specular_power(pg->specular_params_back); } } diff --git a/hw/xbox/nv2a/pgraph/pgraph.h b/hw/xbox/nv2a/pgraph/pgraph.h index 156b3010b6..c74e370bba 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.h +++ b/hw/xbox/nv2a/pgraph/pgraph.h @@ -197,7 +197,9 @@ typedef struct PGRAPHState { float light_local_position[NV2A_MAX_LIGHTS][3]; float light_local_attenuation[NV2A_MAX_LIGHTS][3]; + float specular_params[6]; float specular_power; + float specular_params_back[6]; float specular_power_back; float point_params[8]; diff --git a/hw/xbox/nv2a/pgraph/vk/renderer.h b/hw/xbox/nv2a/pgraph/vk/renderer.h index eb0726ac2f..d728805475 100644 --- a/hw/xbox/nv2a/pgraph/vk/renderer.h +++ b/hw/xbox/nv2a/pgraph/vk/renderer.h @@ -190,6 +190,7 @@ typedef struct ShaderBinding { int light_infinite_direction_loc[NV2A_MAX_LIGHTS]; int light_local_position_loc[NV2A_MAX_LIGHTS]; int light_local_attenuation_loc[NV2A_MAX_LIGHTS]; + int specular_power_loc; int clip_region_loc;