Nearly every segment's K is floor(2^39 / (65536 + 256 i) / 16) * 16. The
fit to probe data left a few segments loose and slightly low; segment 39
made Blade Dancer's large triangle one depth unit high where the PSP's
depth test rejects it (frame dump now exact). Segments 39, 162, 253 and 255
now use the formula, which every probe accepts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A triangle with collinear vertices lights no pixels on the PSP, even when its
edge runs through pixel centers (gpu/probe exp118, through and transform
mode). The binner only dropped them when all x or all y were equal, so
degenerate strip triangles drew lines of pixels along shared edges. Fixes
the last differences in two frame dumps (Issue 15129, ULUS10041).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DOUBLEINVSRCALPHA and DOUBLEINVDSTALPHA are 255 - 2a without a clamp: for
a >= 128 the factor is negative and its term subtracts,
-(((2c + 1) * (2|f| + 1)) >> 10), with only the sum clamped. Measured with
every factor and equation against a CPU-filled destination (gpu/probe
exp116, all exact). The x86 jit clamps the factors at 0, so these blends
use the C++ pixel function.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Segment 161's K was 16 low: exp54's one inexact Gouraud triangle needs
q >= 40211 at index 41275 (its y gradient -1366), and exp39/exp41 bound K
from above, leaving a window of 16 that the depth-only fit had missed. Every
color and depth probe is now exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The exponent times the log (the float's bits) keeps units of 16 in the bits,
truncated toward zero, not floored at 1. Fixes the last specular pixels at
small exponents (gpu/probe exp102 e = 0.3, exp3, exp4 e = 0.05); all lighting
probes are now bit 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>
Antialiased lines light the same pixels as plain ones, and each pixel's
alpha (replacing the vertex alpha) is 128 - |v|, v the largest integer
below 256 times the pixel center's offset from the line along the minor
axis (gpu/probe exp112: about 98% of line pixels exact, through and
transform mode; the rest look like the GE's stepper). Echochrome's MSE
against the PSP goes from 170 to 0.12.
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>
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 GE picks a triangle pixel's mip level (auto and slope modes) with the q
at the second pixel of its row's four-pixel span in the direction it walks
the row: x = 4k + 1 left to right, but 4k + 2 for triangles whose long edge
is the right side (the same condition as the plane anchor). When that pixel
is outside the triangle, the span's first covered pixel in that order. Auto
LOD under perspective is exact (gpu/probe exp93, exp103-106, the latter built
from a GTA LCS triangle), and the slope mode probes stay exact.
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 check ANDs every CLUT entry, but took any common bit as proof that no
alpha is zero, so a white palette with alphas 0, 0x10, ... lost its alpha
test. Only the alpha bits count now. Ace Combat's subtitles become bit exact
against the PSP.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The auto level is the largest of the four UV gradients (du/dx, du/dy, dv/dx,
dv/dy) in texels per pixel, taken from the s and t planes, minus the pixel's
q, both through the GE's float-bits log2 (gpu/probe exp92: exact for affine
mappings at any rotation and anisotropy). Before, only du/dx and dv/dy from a
2x2 quad's differences counted. Under perspective (exp93) about 14% of
pixels are still a sixteenth of a level off.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the separate-CLUT bit, mip level n puts n above the index bits, wrapped
to the CLUT: CLUT4 adds n * 16 as before, CLUT8 with a 16-bit palette adds
(n & 1) * 256, and a 32-bit palette's 256 entries wrap it back to 0
(gpu/probe exp90). Also in the x86 sampler JIT.
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>
DrawSprite samples texel centers, but the GE's sprite UV planes usually land
slightly off them, which bilinear filtering shows: on most sprite sizes every
pixel blends its neighbors (gpu/probe exp87). Bilinear sprites now go through
the plane path. Gallery Fake's dump becomes bit exact.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With separate specular lighting and color doubling, the GE doubles the
secondary color as well (gpu/probe exp86, exact for modulate, add and
replace).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A perspective u = s * R(q) keeps 24 significant bits, truncated, instead of
a float24's 16 (gpu/probe exp82: large UVs from a texture matrix, as NFS Most
Wanted uses). Through-mode points keep 15 bits of their UVs, as the UV planes
do for triangles (exp83, exp84).
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>
The 8-bit fog of each vertex goes through the same plane as depth and color.
Bit exact on gpu/probe exp21 (all 9 cases, from 94-98%).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A pixel is lit when the line passes through its diamond and doesn't end inside
it, with points on the top corner, its two edges and the left corner counting
as inside (also where the line only touches the diamond). Exact for 313 of 320
random lines on gpu/probe exp72; a few ties at line ends still differ.
PPSSPP's DDA matched about 20%.
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>
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>
Per pixel, from the interpolated q = 1/w: 16 + log2(slope) - log2(q), both
with the same float-bits log2, plus the bias. Bit exact on gpu/probe
exp58-60, including nearest level rounding.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sprites interpolate UVs with the same fixed point planes as triangles, and
texture coordinates become texels at 1/16 texel, truncated toward zero, also
in the sampler JIT. Bit exact on gpu/probe exp57 (through mode and transform
mode sprites, nearest and bilinear).
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>
A white vertex color still gets doubled, and so does replace. Found with
gpu/probe exp53.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE lerps the top and bottom texel pairs, truncates each to 8 bits, then lerps
those vertically. Bit exact on gpu/probe exp52 (also in ppsspp-re tools/geprobe).
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>
Bit exact against the PSP on every pixel of gpu/probe exp36-41 (over a million
pixels of large, small, steep and slanted triangles):
- Per triangle, the doubled area's 16-bit significand indexes a 256-segment
reciprocal table, linear over each segment's low 8 bits (measured for all
inputs; not the z/w table).
- The x and y depth gradients are fixed point, 14 fractional bits per
subpixel, from the exact edge cross products times that reciprocal.
- The plane is anchored at the leftmost vertex, or the rightmost when the long
edge (top to bottom) is strictly the right side of the triangle.
- Each pixel takes the plane at its center, floored.
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>
The GE samples a pixel's texture coordinates at its center, so a 1:1 sprite
starting at u0 shows texel floor(u0 + 0.5) first (floor(u0 - 0.5) when
mirrored). The fast path truncated u0, half a texel off for fractional UVs.
Measured with gpu/probe; integer UVs are unaffected.
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>
With depth clip off, the GE doesn't clip triangles and lines at the near
plane: the part beyond it is drawn with extrapolated depth, and a vertex behind
the camera (w <= 0) culls the whole primitive instead. With it on, they're
clipped, and a vertex that gets clipped away can't cull the primitive by being
outside the screen range (w = 0 included). Rects, which aren't clipped, are
culled by a vertex behind the camera either way. Measured with gpu/probe.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE counts a vertex as outside when |z| > w, comparing the float24 clip
coordinates without dividing, and culls a primitive only when all its vertices
are outside, with depth clamp on or off. Points were never culled, and lines,
triangles and rectangles were culled on a single outside vertex with clamp off.
Measured with gpu/probe for every primitive type, both clamp modes and a
range of w.
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>
The hack skips the game's stretch blit, so the display framebuffer stays
black and savestate thumbnails and screenshots came out empty. Do the
stretch from the source image when reading back the display framebuffer.
AGENTS.md: Commit finished work without asking, push only when asked
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Without SSE or NEON, triangle pixels got the secondary color in place of
the primary one plus it, so lit triangles came out black. It showed as
the "unexplained" known failures on riscv64 and loongarch64, and broke
the new gpu/lighting/shademap there. Reproduced on arm64 by building
without NEON.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>