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>
Skinning did each component's weighted bone entries and its chain of
GEAdds separately in scalar code. The components now go in lanes, with
GEMulFloat24x4 for the weighted entries, one-term row sums for the
products and GEAdd4 for the sums, in the same order. Checked against the
scalar version on every vertex of 143 skinned frame dumps.
GEMulFloat24x4 matches ProductToFloat24, so inf and NaN bone entries
behave as before. GEAdd4 handles two zero operands without its fallback.
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>
gpu/vertices/carry, recorded on a PSP: a draw whose format has no UVs uses the last ones read, by any
earlier draw in draw order, textured or not. They're kept as read and get the scale and offset of the
draw that uses them. Normals carry over the same way.
We kept the UVs after scaling, only read them with texturing on, and after a cached indexed draw
carried the highest vertex instead of the last one drawn. The carried values move from statics in
ReadVertex to TransformUnit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The tessellator's output was queued as ordinary vertices and run through
a vertex decoder at flush, although the decoded layout is the same as
SimpleVertex. That decode did two things: the UV scale/offset, and
clearing gstate_c.vertexFullAlpha. The tessellator now does both, and
SubmitCurve writes its output where DecodeVerts would have put it and
queues it as predecoded. DecodeVerts only copies such a draw, when it
decodes elsewhere, like the Vulkan and GLES push buffers.
The software renderer decodes by itself and keeps the old path, as do
through mode curves and anything with draws already queued.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The range check was written as tests for outside, which are all false for
NaN, so a NaN vertex passed and was converted to an integer. That differs
by CPU: arm64 gives 0, which lands off screen, while x86-64 gives INT_MIN,
which wraps, and the triangle's area overflowed into a huge sliver. In the
recording-opengl and recording-vulkan dumps that drew a beam across the
top right that a PSP replay of the dump doesn't show. Now such a vertex is
flagged, and its triangles dropped, on both.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like z, a primitive whose vertices are all beyond the same x or y plane
(|x| > w) isn't drawn, for every primitive type and with clipping on or off;
a vertex on the plane is inside (gpu/probe exp176, gpu/clipping/xycull).
Immediate-mode vertices carry screen coordinates in their clip position, so
only z culls them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A vertex with z exactly -w isn't clipped, so with clipping on the GE discards
a triangle whose such vertex lands outside the 4096 guard band. The check
skipped it (gpu/clipping/guardband).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A texture that fits in the GE's 8 KB texture cache keeps what was first
read until TEXFLUSH, across primitives and draws, so a later self-textured
draw doesn't see an earlier one's writes (gpu/probe exp174: three passes
over a 16x16 4444 buffer differ in 177 pixels with and without a flush
between them, both now exact). FF Type-0's (NPJH50443) blur; two of its
PSP replays become exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It used (0, 0, 1) instead. The PSP keeps it zero, under every light kind
and type (gpu/probe exp173). SOCOM (UCES01242) lights meshes whose s8
normals are all zero; its PSP 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>
Found by the probe regression run (ppsspp-re geprobe/regress.py), which
gave different counts on the two:
- Vec4<int> >> was a logical shift on SSE, arithmetic elsewhere. A negative
spot factor lit pixels white on x86 (gpu/probe exp100).
- Vec4<float>::ToRGBA rounded on SSE and truncated on NEON and the generic
path. Spline colors were one lower on ARM (exp48-51, the PSP rounds).
- Texel coordinates past the int range: the C++ sampler now gives INT_MIN
like the x86 JIT's conversion, as the PSP does with a repeating texture (exp5).
- The viewer direction is normalized with the GE's normalization instead of
the SIMD float one (exp2, exp3).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GEProduct multiplies the significands as integers instead of going through
frexpf/ldexp/trunc in doubles, and GERowSum aligns and adds them as integers.
About 4.5x faster per matrix row on arm64, same results.
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>
UVs and vertex colors are morphed with the GE's float24 products and adder
too, colors per channel expanded to 8 bits and the sum floored (gpu/probe
exp107). With both morph targets and skinning weights, the GE morphs
everything first, weights included, then skins once (exp108). All exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each triangle of a quad has its own UV planes, which a sprite built from the
corners doesn't reproduce. Textured triangle rectangles now only become
sprites in through mode with nearest filtering. Gears of Destiny's text boxes
become bit exact against the PSP, and no dump compared gets worse.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Strips, fans and triangle pairs that form rectangles are drawn as sprites,
but a sprite's first column is (x + 6) >> 4 and a triangle's (x + 7) >> 4.
For a left edge 9/16 into a pixel, draw them as triangles (NBA 2K13's menu
boxes, now 99.99% exact against the PSP).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The view z as a row of the combined world-view matrix, then
float24(GEAdd(z, fog end) * fog slope). Bit exact on gpu/probe exp20 (all 22
sweeps, from 99.6-99.95%); infinite and NaN parameters keep their old path.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Skinning scales each bone matrix by its weight (float24 products) and runs
one accumulator through all the bones' translations and terms with the GE's
adder; morphing sums the weighted frames the same way. The software renderer
recomputes the decoder's float results for positions and normals. Bit exact
on gpu/probe exp67, exp68, exp70 and exp71.
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>
In UV gen mode 0 the software renderer now decodes UVs without the prescale
and applies u * scale + offset itself: the product as a float24, then the
GE's guard-bit-less add. Bit exact on gpu/probe exp66 for float, 16-bit and
8-bit UVs (which are unsigned).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The texture matrix is summed like a position row, and projected texture
coordinates go through the same s, t, q planes as perspective ones, with q
the texture matrix's q over w. Points use the planes too. Bit exact on
gpu/probe exp64-65, except the normalized normal source, whose normalization
isn't matched.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both use the GE's fixed point depth plane. Color is screen-linear, also in
transform mode, with 8-bit channels as the plane values (gpu/probe exp54). For
perspective UVs, s = u * R(w), t and q = R(w) become 15-bit integers at the
triangle's largest exponent, go through the plane, and u = s * R(q) per pixel
(exp55-56, bit exact at 1/16 texel).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Measured with gpu/probe exp43-47, bit exact:
- New vertices are interpolated from the inside vertex, t = d_in / (d_in -
d_out) with d = z + w through the GE adder, using the GE's reciprocal;
out - in is truncated to float24 before the multiply by t, and t and each
coordinate use float24 products and adds.
- With one vertex outside, the quad is split from the vertex before it in the
GE's order: (p, a, b) and (p, b, n). The GE takes odd strip triangles in
reverse and fan triangles in order, so SendTriangle now says when the
vertices it gets are reversed from that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Measured with gpu/probe (exp30, exp32, exp34, exp35, exp42), bit exact on
all of it (51911 points through general world, view and perspective
matrices, position and depth):
- The vertex position is truncated to a float24.
- Each product in a matrix row keeps the exact value truncated at a fixed
bit weight, 2^-15 below the two inputs' exponents combined, so a
significand product of 2 or more keeps a 17th bit.
- A row is one multi-operand sum: every product and the translation are
truncated to the bit weight of the term with the largest exponent, added
exactly, and the result becomes a float24. There's no order.
- World, view and projection are combined as (world * view) * projection,
each entry summed the same way and kept as a float24.
The Coded Arms PSP replay gets worse (MSE 26 to 52). Its decals depend on
per-pixel depth, whose interpolation hasn't been measured yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
X and Y now go through the same float24 math as Z: clip coordinates from the
combined matrix (also when lighting needs the world position), the GE's
reciprocal, and the viewport offset added without guard bits, which around
2048 lands on the 1/16 subpixel grid. The 12.4 conversion truncates instead of
rounding up at 10/16.
Triangles are sampled exactly at pixel centers, not 1/16 before them, with left
and top edges inclusive. Sprites start at (x + 6) >> 4 horizontally and
(y + 7) >> 4 vertically and end at (v + 7) >> 4, and their 2x2 loop no longer
treats ties differently by pixel parity. Measured with gpu/probe; against PSP
replays, Coded Arms goes from MSE 33 to 26 and the Test Drive map from 17 to 7.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE converts the fog factor to 8 bits per vertex, min(floor(256 * f), 255),
and interpolates that linearly in screen space. Clamping after interpolation
instead was up to 127 levels off on triangles that cross the fog range.
Measured with gpu/probe; points and wacky values still match exactly.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Truncating both terms to the precision of the larger one is clearing the
low 8 + (exponent difference) bits of each, after which a float sum is
exact. Only the products still go through doubles, since a product of two
float24s needs up to 32 bits before it's truncated. Same results.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Measured on hardware (gpu/depth/transformprecision, gpu/depth/precision):
clip Z and W come out of the combined matrix as float24s, z/w uses the
GE's reciprocal, which linearly interpolates 128 table segments and isn't
quite 1/w, and the viewport's z * scale + center adds without guard bits:
the smaller term is truncated to the precision of the larger one.
Fixes the Test Drive Unlimited map (#12786) and the missing background in
Fuuun Shinsengumi Bakumatsuden (from #16131), where vertices land right at
the minimum depth.
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>