The four-pixel bilinear sampler read its sixteen texels one at a time,
each with its CLUT lookup and index transform, and loaded them as
vectors afterwards. For CLUT4 and CLUT8 with a 32-bit palette and no
index shift (by far the most common), and for 8888, the texels are now
gathered in lanes: the index bytes, the nibble, the mask and start, then
the palette entries. CrossSIMD gets Vec4S32::Gather and GatherU8 for it,
one load per lane. LocoRoco executes 5.5% fewer instructions, Burnout 3%.
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>
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>
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>
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 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>
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>
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>
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 pixel and sampler JITs only exist on x86-64, but elsewhere a cache
miss still flushed the binner before the compile that did nothing, and
the cache never filled. So on ARM every switch to a new pixel or sampler
state was a full flush: hundreds per frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
Skipping them is only a speed-up, but it changes how states get batched and
optimized, which changes the rendered output (NBA 2K13 and Virtua Tennis
frame dumps). Keep the old behaviour until that's understood.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A code space clear frees the functions the current state points to, but
the state was kept as long as the GE registers didn't change. Track the
clear generations and recompute, also when a compile during the state
computation clears the caches.
Also skip the binner flush for compiles on builds without the software
JIT, where Compile() does nothing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Rename LogType to Log
* Explicitly use the Log:: enum when logging. Allows for autocomplete when editing.
* Mac/ARM64 buildfix
* Do the same with the hle result log macros
* Rename the log names to mixed case while at it.
* iOS buildfix
* Qt buildfix attempt, ARM32 buildfix
Seeing as SampleLinearLevel is near the top in the profiler,
optimize actual bilinear filtering using SSE2. Solid win in the
synthetic benchmark (https://godbolt.org/z/fqh3xvbGx, also doubles
as correctness check), no visible difference in actual PPSSPP.
Note: profiler suggests that hot part of SampleLinearLevel is
elsewhere.
If the generation count happens to match, would still get a stale pointer
and crash. Let's just make the generation count static so it always
increases.
This is common (for example, maybe a pixel state is updated but sampler is
not), and reduces time spent in ComputeRasterizerState() quite a bit in
Darkstalkers, where jits are available (i.e. Intel currently.)
* samplerjit: Remove unused x/y parameters.
Still need to tune the accuracy of filtering, but those were not the
right way.
* softgpu: Better approximate slope mip level mode.
This isn't exactly right, but it's closer.
* softgpu: Calculate auto from largest difference.
Direction shouldn't matter.
A texture larger than 512 is "valid", but simply wraps/clamps at 512.
Importantly, the texture coords are still calculated at the specified
size, which can be up to 32768.