A tile's head and busy flag, which the drawing threads write, shared a
cache line with its tail, which the emulation thread writes for every
item it hands the tile. And a drawing thread waiting for work read
every tile's head and tail again and again. Now the tail has a line of
its own, and a waiting thread watches one counter that DistributeItems
bumps, looking at the tiles only when it changes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ApplyGESkinning computes skinned positions and normals the way the GE
does and overwrites what the decoder skinned in float, so that was
wasted, 4% of the emulation thread in God of War. A new decoder option,
callerSkins, leaves out the weights, position and normal steps for
weighted vertices outside through mode and only reserves the float
position and normal. The software renderer's transform unit gets its
own decoders with it; bounding boxes, splines and the debugger still
use the ones that skin. Checked to give the same output with the vertex
decoder JIT as without the option.
God of War takes 4.0 s for five emulated seconds (from 4.1 s).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
BinQueue's head, tail and size were atomics, so each push was two
locked increments, but only the emulation thread uses them: the drawing
threads read items, which the tiles publish. And each BinItem was built
on the stack in pieces and then copied with wider loads, a store
forwarding stall. Now the indices are plain and items are built in
place (PushItem). AddTriangle was 8% of the emulation thread's time in
God of War, which now takes 4.1 s for five emulated seconds (from 4.2 s),
Wipeout 3.5 s (from 3.7 s).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There are 24 of them. SoftDirty(-1), which some paths set, now means
just those (plus unused bits up to 31) rather than 64 bits of which
40 never got cleared.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
God of War draws thousands of short strips per frame, each continuing
the previous one's vertices with nothing changed in between. At the
first draw of such a run, SoftGPU now looks ahead in the display list
for the draws that follow it, past only commands that can't change
anything (NOPs, unchanged state, VADDR to where the vertices continue),
and the transform unit decodes the whole run at once and then skins and
transforms it in chunks that two helper threads take alongside the
emulation thread. The draws then read their vertices from that, and the
UV and normal they carry to the next draw are updated as reading their
last vertex would.
The helpers wait a moment for the next run before their tasks end, as
the drawing threads do: waking them costs the emulation thread a system
call. The thresholds were picked under WSL2, where that costs more than
on bare metal, so they're worth measuring again there.
God of War takes 4.2 s for five emulated seconds (from 4.5 s).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SSE2 has no 32-bit lane multiply, so Vec4S32::Mul is emulated with two
pmuludq and shuffles, and the bilinear sampler spent a tenth of its time
there. The texel row offsets, the texture functions, fog and blending
only multiply values that fit 16 bits, which Mul16 does in one pmaddwd.
The bilinear lerp of two channels packed as 16-bit halves uses the new
MulHalves (pmullw), which multiplies both at once.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ApplyGESkinning took a vertex at a time with its components in three of
four lanes. Now four vertices go through each bone together, a lane
each, with the bone matrix entries, the weights and the positions taken
apart once for the GE's multiply (GEMulOperand). A bone whose weight is
zero in some lanes adds zeros there, leaving the sums as skipping it
would. The per-component order of the adds is the GE's as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A draw's vertices now go through the position stage of ReadVertex (the
clip coordinates, the viewport, the screen rounding and the GE's fog)
four at a time, a vertex per lane, before the rest. GERowSumLanes is
GERowSum4 with a lane per vertex instead of per matrix row, sharing the
positions' parts between the five rows, and GERecip4 is GERecip for four
w's. A group with a lane those don't handle (non-finite coordinates, a
w that's zero, denormal or too large) goes through ReadVertex as before.
Wipeout takes 3.8 s for five emulated seconds (from 4.2 s), God of War
4.7 s (from 5.0 s).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A drawing task ended as soon as it found no work, so the emulation
thread woke threads again and again, each a system call it paid for
(about 15 us under WSL2). A thread now waits up to 200 us for more,
unless the emulation thread is waiting for the drawing to finish.
More drawing threads also slow the emulation thread, which is what
limits these games, more than they help it: in God of War and Wipeout
on a 16 core Ryzen, six beat four, eight and the 31 the thread manager
has. With both, God of War takes 5.0 s for five emulated seconds (from
5.2 s with four threads, 7.4 s with all), Wipeout 4.2 s (from 4.7 s).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GERowSum4 split every matrix entry into exponent, fraction and sign for
each vertex. GERowSumRows does that once when the transform state is
computed, for the clip matrix, the world matrix's normal rows, the fog
row and the texture matrix.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ApplyTexturing's per-pixel path built each pixel's color from four
strided channel values, which the compiler stored in pieces and then
loaded as a vector, a store forwarding stall per pixel. On x86 and
ARM64, where the sampler takes its color in a register, the channel
rows are transposed in registers instead, and back afterwards.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GEUVSpan stored the four 64-bit plane values, and the reciprocal table's
entries, to arrays one by one and loaded them as vectors, a store
forwarding stall each time, which took most of its time on x86.
CrossSIMD gets LoadS64Low (the low halves of four 64-bit values) and
LoadPairs (four pairs of 32-bit values split into two vectors) to do it
without memory. Wipeout runs about 2% faster.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With 2048, God of War filled the queue often enough that the emulation
thread spent a fifth of its drawing time waiting for room. With 8192 it
stays ahead of the drawing threads: about 6% less wall time there (with
eight drawing threads), and the same in Wipeout. Larger made no
difference.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ReadVertex took the GE's viewport arithmetic one coordinate at a time,
with a reciprocal of w for each, and stored the position, the UVs and
the whole vertex in pieces that were then reloaded with wider loads,
each a store forwarding stall. x, y and z now go through one GERecip and
the four-lane multiply and add (the scalar path stays for non-finite
input and zero or denormal w), the position is loaded as a vector, and
the vertex is written straight into its destination.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The C++ paths draw and sample four pixels at a time in vector lanes,
while a JIT function only does one, and with a JIT function in use the
span and quad paths were skipped. On x86 the JITs had become slower:
Wipeout took 13% more wall time and 25% more CPU with them, God of War
3% more. They were also a second implementation of every GE rule, and
ARM never had them.
The SoftwareRendererJit setting goes with them. RasterizerRegCache.h
keeps only the types the pixel and sampler functions take, as
RasterizerTypes.h.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A sprite's u gradient comes from the setup reciprocal of its whole area, so it's
exact only for a power of two area, whatever the height: the Δy only cancels on
paper. Checked against exp87's sizes and exp256, where 64x128 CLUT8 sprites with
u = 1 + 2x read the next pixel's low byte on a PSP and 64x272 ones their own
high byte.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GE doesn't divide by a q <= 0: it samples the last texel in u and v,
whatever s and t are, in clamp and repeat modes alike (gpu/texmtx/negq). The
hardware backends divided, which mirrors a projected texture behind its
projector: Fate/Extra's character shadows ran up the school's walls (#18667).
The software renderer sampled (0, 0) instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the span drawing and sampling faster, the setup a triangle repeats
for each tile it touches weighs more, so the rule is now eight tiles per
thread instead of sixteen (64x32 on eight threads). Against sixteen, in
wall time on five games: God of War -2.6%, Wipeout -1.7%, LocoRoco -5.2%,
Tekken 6 -5.6%, Burnout -3.3%, with 2.5-8% less CPU. Four per thread used
the least CPU but was slower than eight in wall time in most of them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Staged spans without mip levels pass no level arrays, and the sampler goes
straight to its one level path. 4% fewer instructions in God of War.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The texels come out corner by corner, so no transpose, and R with B, G with
A, share 32-bit lanes as 16-bit halves: no lerp of 8-bit values needs more
than 16 bits, and masking after each shift truncates both halves as before.
2.5% fewer instructions in God of War.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Without mip levels and with one filter for both, a span's texturing is the
quad sampler at level 0: staged spans call it directly instead of going
through ApplyTexturing, which also skips the level calculation. God of War
retires 7% fewer instructions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SampleLinearQuadT filtered each pixel's four texels as 16-bit channels in a 64-bit integer, then
transposed the results into a vector per channel for the texture function. Now the lerps run on a
channel of all four pixels at a time, producing that layout directly. God of War about 6% faster.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fallback (which RISC-V uses) did its integer arithmetic in plain int,
where signed overflow and left shifts of negative values are undefined,
and converted out of range floats with a cast, which is undefined too:
RISC-V gives INT_MAX for NaN where SSE gives INT_MIN and NEON 0. With GCC
on RISC-V that broke GEUVSpanCore in the GEMath unit test. Now the
arithmetic wraps through uint32_t, like the SIMD instructions, and an out
of range or NaN conversion gives INT_MIN, like SSE.
Also skip GERowSum's zero terms before computing their shift, as their
exponent can be INT_MIN. Found with -fsanitize=undefined on the fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The coverage and the drawn range took the same three edge bounds, each a
64-bit division. 1% fewer cycles in God of War.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The span functions come in three blend kinds (none, source alpha over the
destination, and the general one), and states with logic ops or signed blend
factors get the pixel at a time function up front instead of checking per
span. 4.5% fewer instructions in God of War.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CheckDepthTestPassed4 only has an SSE path, so on ARM it compared lane by
lane. Adds Vec4S32::LoadU16 for the depth values.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A flat secondary color of zero (no specular) isn't added to each span, and
a flat secondary color or absent fog fills its buffer once per triangle
rather than once per row. 3% fewer instructions in God of War.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DrawRectangle computed each span's UVs from the planes in 64 bits through the general GEUVSpan. It
now fills the staged rows' buffers (flat colors, z and fog, the coverage and the early depth test)
and draws them with DrawStagedSpans, which takes the 32-bit UV path when the planes allow it. Each
pixel still picks its mip level from its own q.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a plane's values at both ends of the chunk fit in 32 bits, so do all between, and 32-bit
wrapping arithmetic gets them exactly: then in vector lanes, with the clamp.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The coverage stage was a scalar loop per pixel with the snap edge as a branch. A chunk starts a span,
so relative to it the bounds fit in 32 bits and the snap edge's pixel is a fixed offset per lane.
Also read the depth buffer for the early depth test with one load where SSE isn't used.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The span pixel function and the four-pixel bilinear sampler work in vector lanes, a channel per
vector, but took and returned the colors a pixel at a time, so every span was transposed twice on the
way. Now they take a channel at a time with a stride. The staged rows keep each chunk's colors that
way from the start (their stage walks a channel at a time anyway), and DrawRectangle builds them so.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DrawSpanVector now covers everything but logic ops and the signed blend factors. Stencil tests pick
the SFail, ZFail or ZPass op per lane, and lanes that only get a new stencil keep their old color.
Pixels are read and written in the framebuffer's own format, converting the whole span at once, and
a span with all four pixels drawn is stored in one go.
Only drawn pixels are written: spans are aligned in screen coordinates, so with a draw offset one can
reach into a tile another thread is drawing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The last stage of a staged row (texturing, the secondary color and the
pixels) is now a template on what's fixed per triangle: textured or not,
the UV planes fitting 32 bits, the mip level's q and through mode.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When a triangle's q, s and t plane sums fit 32 bits wherever its spans
are walked, and the planes' exponents keep the GE's UV arithmetic in the
normal float range (GEUVSpanSafe), a span's plane values are one vector
add each and GEUVSpanCore runs without its range checks. Tested bit for
bit against the scalar code under those conditions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Where the pixel function is the C++ one (and textures have planes), a
row is now drawn in stages over up to 64 pixels at a time: coverage,
depth and the early depth test, then the colors, secondary colors and fog
of all its pixels, then texturing and the pixels span by span. Each stage
keeps little state, where the single span loop spilled most of its.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The four-pixel sampler now runs the texture function (modulate, decal,
blend, replace and add, with color doubling) on all four pixels at once,
one vector per channel, instead of GetTextureFunctionOutput per pixel.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Where the pixel function is the C++ one, triangles and sprites now hand
it a whole span of four pixels. Unless the state has a stencil or color
test, a logic op or a signed blend factor, the span goes through one pixel
per vector lane: clamping, alpha test, fog, depth range, test and write,
blending, dither and the color write mask, as the per-pixel code does
them. Checked against the per-pixel code span by span in 8 games and 263
frame dumps.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Depth, colors, fog and the UV planes are now started once per row and
stepped a span at a time, instead of evaluated from scratch at every
span.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each row's covered pixels now come from the three edges directly, as a
range from 64-bit integer bounds at the pixel centers, and only the spans
in it are visited, with lane masks from the range. The edge functions
per span (32-bit, with a coarse row narrowing) are gone. The mip level's
q comes from the row's covered range instead of edge tests per pixel. A
tall triangle's long edge, which the GE checks at the other end of a span
for its first or last pixel, is still checked per pixel.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GEUVSpan does the GE's per-pixel UV arithmetic for four pixels at once:
the plane values to float24s by exponent arithmetic, GERecip with its
table entries gathered, and the truncated 24-bit products from the 16-bit
significands' product. Lanes it doesn't cover (values past 2^24,
denormals, results out of range) send the span to the scalar code.
Tested bit for bit against it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the four pixels share their mip level (always, for a triangle's
span), their texel coordinates, clamping or wrapping and texel offsets
are computed in vector lanes, and only the texel and palette reads go
one by one. Adds Vec4S32::Min.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE walks rows in spans of four pixels and picks the mip level's q
once per span, so the rasterizer now does the same instead of 2x2
quads: triangle spans are aligned like the GE's, so the four pixels
share their level, which is computed once. Auto LOD without planes takes
its vertical derivative from the row below. Sprites, which always have
planes, lose their unused incremental UVs.
Without a sampler JIT, the four bilinear samples of a span are now taken
in one call. Same results on 263 frame dumps and 8 games.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A texel's byte offset is a sum of one for its row and one for its column,
swizzled or not, so the four texels of a bilinear sample now take two of
each, and the CLUT offset of a mip level is found once per sample. The
UV planes at a quad's four pixels are one sum plus steps, as for depth.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rasterizer takes a GERecip per pixel, so it's inline now, with its
table exported. The q for the mip level is the same for the two pixels
of a quad row unless they're in different 4-pixel spans, so it's only
looked up again then.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Where there's no sampler JIT (everything but x86-64), bilinear sampling
switched on the texture format per pixel, on the CLUT format per texel,
and went through swizzle checks and vector-to-stack round trips. Now a
template instance per texture format, swizzling and CLUT format is
picked with the state, and the filter does all four channels at once in
16-bit lanes of a uint64_t. Same results (checked on 263 frame dumps).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The per-pixel texture coordinates called ldexp with a shift that's fixed
per triangle or line. The shift keeps the values in double's normal
range, so multiplying by a precomputed 2^-shift is exact. Lines also
convert their end values to fixed point once instead of per pixel.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
N.L changes sign from vertex to vertex, so the branch on it was mispredicted a lot. A factor that isn't
positive scales the color to 0, so the term can always be added. Vertex reading about 6% faster in
Tekken 6.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every GEAdd4 and GEMulFloat24x4 checked its lanes for the cases it can't do and branched to a
fallback, a vector-to-scalar move and a branch per operation, about 14% of skinning. The unchecked
variants only collect those lanes; a vertex where any appears is skinned again with the checked ones.
The products use GEMulFloat24x4 (the same as a one-term GERowSum4 here), and the position's and
normal's add chains are interleaved. Skinning is about 20% faster.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>