The position is read once as (x, y, z, 1) for the clip, fog, texgen and
light vector rows, and lighting works on Vec4F32 throughout, without
going through arrays. Each light's constant term, its position minus
the world translation, is computed once per state instead of per
vertex. The texture matrix rows are now prepared with the transform
state, so loading that matrix dirties it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They're truncated to float24 with one AND, and their exponents, fractions
and signs worked out for all terms at once, each term then broadcasting its
lane (new CrossSIMD Vec4S32::SplatLane). The term count becomes a template
parameter so the lanes are constants. About 75-83 M transforms/s on an M1,
from 68; results unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The four clip space rows of a vertex, the texture matrix's three, the world
normal's three, the vector to a light, and each row of the combined matrix
now take one GERowSum4 call. Same results (pspautotests, frametests and
the probe regression set unchanged).
GERowSum4 scales its result by adding to the exponent instead of
multiplying, and CrossSIMD gets a Vec4S32::Max for the largest lsb. About
68 M transforms/s against 26 for four scalar GERowSums on an M1 (a pure
NEON version with per-lane shifts did about 79); replaying the frametest
dumps gets only about 1% faster, up to 11% on Taiko no Tatsujin.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE keeps the specular and spot exponents to 5 significant bits,
truncated, and from 512 up (infinity and NaN included) uses 496. Only
the specular one was truncated, and neither saturated. A huge or NaN
exponent overflowed pspPow's bits: highlights wrong on every backend,
and on D3D11, where the overflowing float to int conversion gives a
NaN, black (Everybody's Golf, #22418). With the exponent clamped, t
stays in range for every base the backends produce.
Measured, now bit exact in the software renderer and within 4 levels
on Vulkan.
Fixes#22418
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
IsLargerThanHalf's non-SIMD loop assigned instead of or-ing, so only blue
counted: a yellow or red light gave no diffuse or specular on riscv64 and
loongarch64 (gpu/commands/light, gpu/exact/lighting and curves failed
there). The NEON version took a channel of 1 for none; it now agrees with
SSE, which counts any channel above zero.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NaN and +inf directions light like (1, 1, 1), -NaN and -inf like (-1, -1, -1)
(gpu/commands/light).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It used (0, 0, 1) instead, lighting everything facing the camera. The PSP
leaves the direction zero: no diffuse, and specular from the eye vector
alone (gpu/probe exp164). ULJM05302 has such a light on its cars, and the
Yu-Gi-Oh (ULJM05940) replay becomes exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE never forms a world position for lighting: the vector to a point
or spot light is the light position minus the world translation, minus
the model position times the world matrix, summed like a matrix row.
gpu/probe exp153-158 (point lights 0.02-20 units away, spot exponents up
to 40, game-like world matrices; exp153 is a Syphon Filter vertex) are
exact, and Syphon Filter (#13568) becomes exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found by the probe regression run on the first lighting probes:
- The normal stays as the world matrix leaves it, and N.L and N.H are
scaled by its GE reciprocal length, like the spot direction. Exact with
scaled, sheared and rotated world matrices (gpu/probe exp3).
- A negative factor (a spot pointing away with cutoff -1) scales the light
by 0 instead of subtracting (exp3, exp4).
- Shade mapping UVs use the same GE arithmetic as lighting (exp2-4).
- The half vector uses the GE's add.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The float24 truncations, reciprocal and rsqrt tables, GE adder and row sums,
the setup reciprocal for planes, the mip log2, texture coordinate precision,
the lighting pow and color scaling, and the antialiased line alpha, from
TransformUnit, Rasterizer and Lighting, so they can be unit tested. No
behavior change.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE's pow multiplies the exponent and the float's bits exactly and floors,
instead of a rounded float multiply, which mattered for e = 1.1 (gpu/probe
exp102). The hardware backends keep the float version.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The quadratic attenuation term is the light vector's squared length from its
normalization, not d * d (gpu/probe exp63 and exp100: point and spot
attenuation now exact).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The spot direction isn't normalized: its dot with the light vector is scaled
by the direction's rsqrt. And attenuation and spot scale the light's colors
as separate 8-bit factors rather than one product (gpu/probe exp97-100, exact
on all spot jobs).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE normalizes with its own reciprocal square root, a 2 x 128 segment
table like its reciprocal's (measured on gpu/probe exp69), and sums dot
products and the world matrix rows like clip space rows. Used for normals,
light vectors, the half vector, the normalized normal texgen source, and the
world position. Bit exact on exp61, exp64 and exp69; point light attenuation
is 99.9%.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
The light and material colors first make an 8-bit product, and N.L becomes an
8-bit s = floor(256 * f), which is expanded like a color before the second
multiply: ((2x + 1) * (2s + 1)) >> 10. Bit exact on gpu/probe exp61-62, apart
from N.L itself being about 2^-15 high on the PSP with a scaled world matrix.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Environment map S and T are (N.L + 1) / 2 with L the light's vector as
lighting uses it: from the vertex to the light for point and spot lights,
a zero vector staying zero, and the half vector for a light that does
specular. Whether lighting or the light is enabled still doesn't matter
(gpu/lighting/shademap).
The vertex shader ID now carries the type and computation of the shade
mapping lights (the ubershader reads them from u_lightControl), so both
shader caches get a new version.
Fixes the hair shine in iDOLM@STER SP (#12376).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The viewer is at infinity along view space +z, so in world space, where
lighting happens, it's the view matrix's third column rather than
(0,0,1): turning the camera moves the highlights (gpu/lighting/specular).
The shaders read it from u_view.
In a Need for Speed Carbon frame replayed on a PSP, this and the pow
bring the error of the cars from MSE 957 to 120 (Vulkan).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE computes these powers as exp2(e * log2(x)), with log2 and exp2
each a straight line between powers of two (Mitchell's approximation),
and only uses the top 4 bits of the specular coefficient's mantissa.
Through a highlight's falloff a true pow is 10-30 steps of 255 brighter
at the exponents games use. Measured in gpu/lighting/specular.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
In many cases, games use lighting just for diffuse or something, this
helps skip what's not needed too. Good improvement in a scene from a
Naruto game.
This seems to be a bit more accurate. Color blending seems correct now,
but the factors and especially pow results are off.
Also, normalize normal to 0, 0, 1, which seems to match results better.