Commit Graph
1403 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 4bec735331 SoftGPU: Pick the tile size from the thread count
Every tile a triangle touches sets it up again, so larger tiles do less
of that. The tile size is now picked at startup, as large as it can be
with at least sixteen tiles per thread over the screen. With fewer, a
scene whose drawing is concentrated on part of the screen leaves threads
idle: on eight threads, 128x32 tiles (four per thread) took LocoRoco a
third longer than 64x16, and God of War 5% longer. So eight threads keep
64x16, and four or fewer get larger tiles.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:04 -06:00
Henrik RydgårdandClaude Opus 5.5 3c3025a087 SoftGPU: Keep drawing queued when a display list stalls
Returning to the CPU flushed all queued drawing, also when a list only reached its stall address.
Wipeout Pure builds its lists as it goes and stalls about a hundred times a frame, so the main thread
spent a third of its time finishing the drawing at those flushes. A stalled list isn't done, so now
only a finished one flushes there, which makes Wipeout Pure 15% faster.

What relied on that flush now flushes itself: memory copies, sets, readbacks and uploads that overlap
the queued drawing, and save states, before memory is saved or loaded (FlushPendingDrawing). Syncs and
the display already did. Queued drawing still reads textures from PSP memory when it runs, so a game
that rewrites a texture between stalls without syncing would see the new one; on hardware the GE has
usually drawn with the old one by then.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:04 -06:00
Henrik RydgårdandClaude Opus 5.5 1228837688 SoftGPU: Wake only threads with tiles to draw, and chain the wakes
Waking a worker is a condition variable signal, a system call, and the main thread paid one per idle
worker every 48 queued items. With Tekken 6's tiny draws (about 5 vertices each) the workers keep
running dry, and those signals were about 10% of the main thread, which is what limits it there.

Now no more threads are woken than tiles have work, and only the first is woken from the main thread;
it wakes the rest.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:04 -06:00
Henrik RydgårdandClaude Opus 5.5 bec1eacd77 SoftGPU: Keep threads for drawing that textures from its target
A state that textures from what it draws to, or whose scissor reaches
past the stride, used to switch the binner to one thread, and every
primitive was drawn alone, copying the whole texture for its snapshot.
In Wipeout, God of War and GTA that was a third to half of the time.

Now it's decided per primitive. One is drawn after everything queued
and before what follows only if it needs that: it textures from texels
a queued primitive (or itself) writes, it writes texels a queued one
still has to read, or it reaches past the stride. Reads are bounded by
the vertices' texture coordinates plus the filter's neighbors and
mapped to tiles of the target; queued writes and reads are tracked per
tile. Everything else goes to the tiles as usual. The in-order ones are
split into bands of rows when large, since each pixel reads only the
snapshot, and the snapshot now only copies the previous primitive's
columns.

Same results on 263 frame dumps and 8 games.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:04 -06:00
Henrik RydgårdandClaude Opus 5.5 f718e89112 SoftGPU: Bin queued primitives into tiles that any thread can draw
Each thread used to own a band of the screen, fixed when the work was
first split, and got its own copies of the primitives touching it. Bands
were rarely balanced (edge bands had half the work of middle ones, and
on big.LITTLE CPUs half the threads are slower), and primitives queued
up to the 2048 limit before the threads started.

Now primitives go to 64x16 tiles, as indices into the shared queue, in
batches of 16. A thread takes any tile with work and draws its items in
order; only one thread holds a tile at a time, so per-pixel order is
kept. A flush or a full queue has the main thread draw tiles too instead
of waiting, and when there's only a little work it does that alone
rather than wake the threads. Items are reclaimed once every tile has
drawn them. This also drops the per-thread queues, up to 23 MB.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:04 -06:00
Henrik Rydgård d2ea558e49 Merge pull request #22430 from hrydgard/light-exponent
GPU: Saturate the lighting pow's exponent below 512, as the GE does
2026-10-05 14:42:43 -06:00
Henrik RydgårdandClaude Opus 5.5 dcaa4d5f16 GPU: Saturate the lighting pow's exponent below 512, as the GE does
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>
2026-10-05 13:32:24 -06:00
Henrik RydgårdandClaude Opus 5.5 251d42ab2e SoftGPU: Leave textures with an invalid level address to the generic samplers
The x86 sampler JIT returned zero for a whole sample when a texture level
pointer was null, and for a mipmapped texture it checked the next level
too, even when it isn't blended in. Tactics Ogre draws its font from a
mipmapped texture whose next level has an invalid address, at level 0,
so all its text was missing on x86-64. The generic samplers read a null
level's texels as zero only where they use that level, and still apply
the texture function. Now samplers for such textures aren't compiled,
and the JIT's null checks are gone. Test: gpu/textures/mipinvalid.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:05:46 -06:00
Henrik RydgårdandClaude Opus 5.5 0274b10e4c SoftGPU: Fix the portable check for a light color that contributes
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>
2026-10-02 16:07:19 -06:00
Henrik RydgårdandClaude Opus 5.5 20620da37f SoftGPU: Cull primitives entirely outside an x or y plane
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 3fcce3895e SoftGPU: Texture coordinates along lines as the GE computes them
Transform-mode lines take the triangle's per-vertex s, t, q (15 bits at the
larger exponent) along the line's gradient, like their color and depth, then
u = s * R(q). A pixel just inside a line's end samples short of the end
vertex's coordinate rather than at it (gpu/texmtx/prims: texel row 255, not a
wrap to 0).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 b684bacbc4 SoftGPU: An inf or NaN spot direction acts as the largest value of its sign
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 8ca6f7697f SoftGPU: A vertex on the near plane still needs its screen x/y in range
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 fb79979e01 SoftGPU: Small self-textures stay cached until TEXFLUSH
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 ecaa961850 SoftGPU: A zero normal gets no diffuse or specular
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 0357baf1b7 SoftGPU: Depth along lines as the GE computes it
The same way as Gouraud color: a gradient along the major axis from the
setup reciprocal, and each pixel's value from its center's signed distance
from v0, so an endpoint's pixel extrapolates past the endpoint's depth
(gpu/probe exp170, exact; it had 435 wrong pixels). Coded Arms' (ULUS10019)
last pixel, where a short line passed a GEQUAL test, and SOCOM's line depth.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 0870c0135b SoftGPU: Exact side test for a triangle's edges
Which side an edge is on decides whether a pixel center exactly on it is
inside. The test divided with truncation, so a vertex 0.2 subpixels left of
the opposite edge counted as on it, and a thin triangle's right edge became
inclusive. Peace Walker (ULUS10509), ULJM05302 and mali_missing_circle PSP
replays become exact.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 75c017295e SoftGPU: Only draw 1:1 textured triangle rectangles as sprites
Two triangles forming a rectangle each have their own UV planes. Scaled, a
sample can land right on a texel edge, where the plane's anchor decides the
side: Tokiden's (NPJH50878) 480x33 bar over 280x20 texels took the row
above in its second triangle (gpu/probe exp167). 1:1 samples are half a
texel from any edge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 e7b21b82ac SoftGPU: Draw single-threaded when the scissor reaches past the stride
Pixels past the framebuffer's stride land at the start of the next row,
which another binning task may be drawing, so the result raced. Tokimeki
Memorial (NPJH50127) blurs a 128 wide buffer under a full screen scissor;
its PSP replay is now exact (gpu/probe exp166).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 65421f1f6b SoftGPU: Lines shorter than a pixel keep their fog
With no steps along the line, both endpoint weights were zero, so fog (and
UVs) interpolated to zero: full fog. SOCOM's (UCUS98649) radar lines, a few
pixels long in an orthographic view, came out in the fog color.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 5aa0faa340 SoftGPU: A directional light with a zero direction gives no diffuse
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 d1b4f435d4 SoftGPU: Don't join bilinear-filtered through-mode sprites
The GE sets up UV planes per sprite, and their gradient is only exact for a
power of two area, so a 49 wide 1:1 sprite samples just below the texel
centers while the 128 wide sprite it was joined into hit them (gpu/probe
exp163, the ULJM05302 track map). Nearest sampling picks the same texels
either way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 74e2c8f7ee SoftGPU: Self-texturing primitives sample the texture as it was before them
The GE samples through its texture cache, so a primitive that textures from
its own render target mostly reads the old contents (geprobe exp81, exp159,
exp160). Give each such primitive a snapshot, refreshing only the rows the
previous one drew.

3rd Birthday (ULUS10567) bloom becomes exact on PSP replays, Burnout
Dominator's glare 93% -> 98% and Ridge Racer's blur 40% -> 94% of pixels.
The remaining differences are rows where the cache's lazily loaded 8-row
blocks already see the primitive's own writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 7df47b336b SoftGPU: Light vectors as one matrix row from the model position
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 683f593de5 SoftGPU: Line endpoints on a diamond's major-axis corner are outside
An endpoint exactly on the left or right corner of a pixel's diamond (top
or bottom for y-major lines) doesn't count as inside it: the pixel is lit
only if the line crosses the diamond (gpu/probe exp149). Makes exp72,
Virtua Tennis and echochrome exact.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 9cc9998cc2 SoftGPU: Antialiased line coverage with a plane-style slope
The minor offset walks from v0 with a slope quantized like a triangle
plane's gradient (setup reciprocal, 14 fraction bits per subpixel,
floored), rather than one product per pixel. Makes gpu/probe exp112
exact, and Virtua Tennis and echochrome lose their AA line misses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 10fcf4bde6 SoftGPU: Interpolated depth below 0 is 0
Next to an edge between two z = 0 vertices, the floored depth plane can
come out -1. The GE stores and range-tests 0 there, in through and
transform mode, with depth clamp on or off (gpu/probe exp148). Fixes a
seam across the sky in Frontier Gate Boost.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 cadf5d8b2d SoftGPU: Evaluate spline patches as the GE does
De Boor at t = k/256 with the Bezier path's lerps. Each blending factor
(t - knot) / span is quantized in 1/256 from the nearer of its two knots,
and a segment's last point is evaluated as the next one's first. Colors,
texture coordinates, generated UVs and normals work as for Bezier
patches. Bit exact on gpu/probe exp144-146; software renderer only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 9501f65bbf SoftGPU: Setup reciprocal segments 105 and 106 follow the K formula
Both were fit as exceptions with a K off the formula, but the probes that
pinned them are as happy with the formula's K and a slope of -128, which
the large-w UV planes need (gpu/probe exp136, exp138 now exact).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 8b3f3b20de SoftGPU: Don't skip zero alpha pixels when dithering
The forced alpha test for src alpha / inv src alpha blending assumed a
zero alpha pixel leaves the target unchanged, but with dithering on the
GE still writes dst + dither (gpu/probe exp141). Makes the Test Drive
(ULUS10249) frame dump exact.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 04c03ad922 SoftGPU: Evaluate Bezier patches as the GE does
Columns before rows, de Casteljau at t = k/256 (rounded toward the patch
middle). Positions and UVs lerp in 16-bit fixed point at the larger
operand's exponent; colors in 15-bit fixed point. Generated UVs are k/256.
Bit exact on gpu/probe exp48-51 and exp139-140. Software renderer only,
since it's about 15x slower than the float weights.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 61c7478679 SoftGPU: Clip vertex colors and fog with t rounded to 1/256
The near-plane clipper's new vertices get their colors and fog (both 8-bit
per vertex) by lerping with t rounded to the nearest 1/256, not truncated
(gpu/probe exp136: every clipped color and fog pixel exact; the colors were
recovered exactly from the PSP's plane values per clipped triangle).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 2fc53315a6 SoftGPU: Antialiased line alpha from the setup reciprocal
The minor-axis offset an antialiased line's alpha comes from is computed
with the triangle setup's reciprocal of the line's major length, walking
from v0, and floored; diagonal lines are y-major. exp112 went from 101
differing pixels to 23; the misses left sit at reciprocal segment starts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 6fe7d5d35c SoftGPU: Gouraud color along lines as the GE computes it
A line's color gradient along its major axis comes from the triangle
setup's reciprocal, with 14 fraction bits per subpixel, floored, and each
pixel is the start color plus the gradient times its center's signed
distance from v0 along the line. A diagonal line (|dx| == |dy|) is
y-major. gpu/probe exp137, exact; fixes the line strips in a Final Fantasy
Zero frame dump.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 700a314542 SoftGPU: Clip vertex texture coordinates in GE arithmetic
The near-plane clipper's new vertices get their texture coordinates with
the same float24 arithmetic as their position, not a float lerp. Fixes the
texel ties under a clipped fan in a GTA frame dump (gpu/probe exp136; clipped
colors and fog still differ).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 b10ab1d4aa SoftGPU: Span snapping on very tall triangles' long edge
When a triangle's long edge (top to bottom vertex) has 3 dy > 2^17 in
screen subpixels, the GE lights the first pixel of each 4-pixel span along
it (the last, for a right edge) when any of the span is inside, likely an
overflowing 3 dy span offset. Bottom ties end the long edge at the left
vertex. Such triangles take a scalar mask path. gpu/probe exp130-135, exact;
none of 216 frame dumps has such a triangle.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 9dd9076f38 SoftGPU: Triangle winding test in 64 bits
With vertices far apart in the 4096 pixel screen space the cross product
overflowed 32 bits, flipping its sign, so both windings of a large triangle
were dropped (gpu/probe exp132: the PSP draws it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 a471598d14 SoftGPU x86 jit: Fix the NAND logic op
It ANDed the destination into a temporary instead of the color, so NAND
came out as NOT color. Found by gpu/probe exp125 on an x86-64 build (the
C++ path was already right).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 9bca024674 SoftGPU: Sprite UVs with a different w at each vertex
The GE builds a sprite's UV plane from three corners whose s, t and q it
takes component by component from the two vertices: s from the vertex that
supplies the corner's x, t from the one that supplies its y (swapped when the
sprite is rotated), and q from the one that supplies its x. With different w
at the two vertices that bends the mapping; we used v1's w at every corner.
gpu/probe exp129: all four vertex orders at w = 1, 2.7 and differing w, exact.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 f5bfa77841 SoftGPU: Rotated sprites' first row and UV plane
A sprite drawn from bottom left to top right is rotated: its first row
rounds like a column ((y + 6) >> 4), and its UV plane comes from v0, v1 and
the bottom right corner, anchoring it at the bottom left. The other three
vertex orders were already exact (gpu/probe exp121-122, through and
transform mode). Fixes the last differences in an MGS frame dump (HUD
sprites).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 b1744e809e SoftGPU: Fix the stencil test's comparison shortcuts
The stencil test compares ref against the buffer's value, the other way
around from the alpha test, but shared its shortcuts: GREATER with ref 0 and
LESS with ref 0xFF became NOTEQUAL instead of NEVER, so those draws wrote
pixels the PSP rejects. Its own shortcuts are mirrored (gpu/probe exp120:
every function and op in 8888, 5551 and 4444, exact).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 47261f8b86 SoftGPU: Y-major lines count the other diamond edges as inside
Which boundary points of a pixel's diamond count as inside is transposed for
y-major lines: the left corner, the edges beside it and the top corner,
where x-major lines use the top corner, its edges and the left corner. exp72
went from 12 differing pixels to 1, and antialiased lines (exp112, same
pixels) from 117 to 101.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 b6dec1dfdc SoftGPU: Setup reciprocal segments from the table's formula
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 d8434424b1 SoftGPU: Drop zero-area triangles
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 f2bc80e66e SoftGPU: Signed doubled inverse alpha blend factors
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 0ae6385505 SoftGPU: Correct one segment of the triangle setup reciprocal
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 e0737c0ac2 SoftGPU: Truncate the lighting pow's log product to 19 fraction bits
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 6050c6fe02 SoftGPU: Light with the unnormalized normal, GE shade mapping
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 c8819d5087 SoftGPU: Make arm64 and x86 render the GE probes identically
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>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 df5b55d5b8 SoftGPU: Integer GE row products, clz for the setup exponent
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>
2026-10-02 15:09:43 -06:00