From 92e813b53ef107585c19228dd1e52b5e4bf453da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 30 Sep 2026 23:12:08 -0600 Subject: [PATCH] SoftGPU: Scale specular and attenuated light like diffuse Specular uses the same 8-bit factor expansion as diffuse, and attenuation (with the spot factor) is a second 8-bit factor applied after it, also to ambient. Exact on gpu/probe exp63 for specular, 99.5% for point lights (the attenuation's own precision is not matched). Co-Authored-By: Claude Opus 5.5 (1M context) --- GPU/Software/Lighting.cpp | 77 +++++++++------------------------------ 1 file changed, 18 insertions(+), 59 deletions(-) diff --git a/GPU/Software/Lighting.cpp b/GPU/Software/Lighting.cpp index ada779a8fb..cb8c2abe3f 100644 --- a/GPU/Software/Lighting.cpp +++ b/GPU/Software/Lighting.cpp @@ -201,52 +201,17 @@ void GenerateLightST(VertexData &vertex, const WorldCoords &worldpos, const Worl vertex.texturecoords.t() = PSPShadeMapCoord(gstate.getUVLS1(), worldpos, worldnormal, viewDir); } -#if defined(_M_SSE) -#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) -[[gnu::target("sse4.1")]] -#endif -static inline int LightCeilSSE4(float f) { - __m128 v = _mm_set_ss(f); - // This isn't terribly fast, but seems to be better than calling ceilf(). - return _mm_cvt_ss2si(_mm_ceil_ss(v, v)); +// How the GE scales light by a factor (gpu/probe exp61-63): the light and material colors make an +// 8-bit product x = ((2l + 1) * (2m + 1)) >> 10, and each factor (N.L or the specular power, then the +// attenuation and spot) becomes an 8-bit s = floor(256 * f) that is expanded like a color: +// ((2x + 1) * (2s + 1)) >> 10. A factor of 1 (s = 256) leaves x as it is. +static inline Vec4 LightColorProduct(const Vec4 &lightFactor, const Vec4 &materialFactor) { + return (lightFactor * materialFactor) >> 10; } -#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) -[[gnu::target("sse4.1")]] -#endif -static inline __m128i LightColorScaleBy512SSE4(__m128i factor, __m128i color, __m128i scale) { - // We can use 16-bit multiply here (faster than 32-bit multiply) since our top bits are zero. - __m128i result18 = _mm_madd_epi16(factor, color); - // But now with 18 bits, we need a full multiply. - __m128i multiplied = _mm_mullo_epi32(result18, scale); - return _mm_srai_epi32(multiplied, 10 + 9); -} -#endif - -template -static inline int LightCeil(float f) { -#if defined(_M_SSE) - if (useSSE4) - return LightCeilSSE4(f); -#elif PPSSPP_ARCH(ARM64_NEON) - return vcvtps_s32_f32(f); -#endif - return (int)ceilf(f); -} - -template -static Vec4 LightColorScaleBy512(const Vec4 &factor, const Vec4 &color, int scale) { - // We multiply s9 * s9 * s9, resulting in s27, then shift off 19 to get 8-bit. - // The reason all factors are s9 is to account for rounding. - // Also note that all values are positive, so can be treated as unsigned. -#if defined(_M_SSE) && !PPSSPP_ARCH(X86) - if (useSSE4) - return LightColorScaleBy512SSE4(factor.ivec, color.ivec, _mm_set1_epi32(scale)); -#elif PPSSPP_ARCH(ARM64_NEON) - int32x4_t multiplied = vmulq_n_s32(vmulq_s32(factor.ivec, color.ivec), scale); - return vshrq_n_s32(multiplied, 10 + 9); -#endif - return (factor * color * scale) >> (10 + 9); +static inline Vec4 LightColorScale(const Vec4 &x, float f) { + const int s = std::min((int)(256.0f * f), 256); + return ((x * 2 + Vec4::AssignToAll(1)) * (2 * s + 1)) >> 10; } static inline void LightColorSum(Vec4 &sum, const Vec4 &src) { @@ -334,10 +299,9 @@ static void ProcessSIMD(VertexData &vertex, const WorldCoords &worldpos, const W // ambient lighting if (lstate.ambient) { - int attspot512 = (int)LightCeil(256 * 2 * attspot + 1); - if (attspot512 > 512) - attspot512 = 512; - Vec4 lambient = LightColorScaleBy512(lstate.ambientColorFactor, mac, attspot512); + Vec4 lambient = LightColorProduct(lstate.ambientColorFactor, mac); + if (attspot < 1.0f) + lambient = LightColorScale(lambient, attspot); LightColorSum(final_color, lambient); } @@ -351,13 +315,10 @@ static void ProcessSIMD(VertexData &vertex, const WorldCoords &worldpos, const W } if (lstate.diffuse && diffuse_factor > 0.0f) { - // The GE (gpu/probe exp61-62): the light and material colors make an 8-bit product - // x = ((2l + 1) * (2m + 1)) >> 10, and the factor an 8-bit s = floor(256 * f), which is then - // expanded like a color: ((2x + 1) * (2s + 1)) >> 10. - const int s = (int)(256.0f * attspot * diffuse_factor); Vec4 mdc = state.colorForDiffuse ? colorFactor : state.material.diffuseColorFactor; - const Vec4 x = (lstate.diffuseColorFactor * mdc) >> 10; - const Vec4 ldiffuse = ((x * 2 + Vec4::AssignToAll(1)) * (2 * s + 1)) >> 10; + Vec4 ldiffuse = LightColorScale(LightColorProduct(lstate.diffuseColorFactor, mdc), diffuse_factor); + if (attspot < 1.0f) + ldiffuse = LightColorScale(ldiffuse, attspot); LightColorSum(final_color, ldiffuse); } @@ -368,12 +329,10 @@ static void ProcessSIMD(VertexData &vertex, const WorldCoords &worldpos, const W specular_factor = pspLightPow(specular_factor, state.specularExp); if (specular_factor > 0.0f) { - int specular_attspot = (int)LightCeil(256 * 2 * attspot * specular_factor + 1); - if (specular_attspot > 512) - specular_attspot = 512; - Vec4 msc = state.colorForSpecular ? colorFactor : state.material.specularColorFactor; - Vec4 lspecular = LightColorScaleBy512(lstate.specularColorFactor, msc, specular_attspot); + Vec4 lspecular = LightColorScale(LightColorProduct(lstate.specularColorFactor, msc), specular_factor); + if (attspot < 1.0f) + lspecular = LightColorScale(lspecular, attspot); LightColorSum(specular_color, lspecular); } }