The fragment shader's default precision is lowp, which NVIDIA's Vulkan driver
runs as fp16, and the product (2c+1)(2f+1) reaches 511*511, past fp16's range.
Split/Second's bloom saturated whole channels; its two bloom dumps go from MSE
2354/9305 to 80/153, the same as OpenGL.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Me & My Katamari textures a 512x512 render target through 0x04288000, and later
snapshots read the textures past its depth buffer at linear addresses. Through
the swizzled depth mirror those came back permuted, which left a black band
over the horizon. Other snapshots at a mirror still go through it, since they
hold depth as the game saw it there. Software now matches the PSP on the three
Katamari dumps (#16131).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The recorder leaves VRAM out of a snapshot as unchanged when an earlier snapshot
had it, and that earlier one can be a render target's, which software playback
skipped. Copy such a target snapshot (around what the replay drew), as the PSP
replayer now does. Software now matches the PSP on the three Madden NFL 10 dumps
(#16131), Need for Speed ProStreet (#16175) and Hammerin' Hero, and no dump gets
worse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ReadFramebufferForDebug scaled the buffer down, cropped to its drawn size,
whenever the render width was more than the drawn width times maxScaleFactor.
At 1x that's any buffer wider than what has been drawn in it, though nothing
needs scaling.
Headless compares at maxScaleFactor 1, so this cut off most of the screen in
Gangs of London, which draws a 256 px wide region: its dumps went from 78 to
62,017 pixels differing from the PSP.
Now it only scales down when the render scale is above maxScaleFactor, and
then scales the whole buffer, since a display at an offset can sit past the
drawn width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShiftDepthForOffsetRendering copied through a single-sampled temp buffer, so
with MSAA CopyDepthRect fell back to a depth blit, which Vulkan can't do with
multisampled images (PerformBlit asserts). Give the temp buffer the same
samples and copy; multisampled depth is never blitted or drawn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With MSAA, a framebuffer textured as CLUT indices was read resolved, so the
indices along edges were averages of the samples and looked up wrong colors.
Dragon Ball Z: Tenkaichi Tag Team draws its outlines from a CLUT32 lookup of
its framebuffer, and they came out noisy and miscolored. Copy each pixel's
first sample with texelFetch into a temporary buffer and look up that.
This needs the multisampled color images to be sampled, and transitioned
back to attachment layout before rendering to them again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With MSAA, a pixel that a primitive only partly covers otherwise
extrapolates them to the pixel center, outside the primitive, and samples
textures outside its UV range. Without MSAA nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BlitFramebuffer drew depth with a raster copy. With MSAA that reads the
single-sampled depth, which is never resolved: Silent Hill: Origins resizes
its light buffer mid-frame, and with garbage depth its depth-EQUAL light
passes drew nothing, so the flashlight was missing. The raster copy isn't
exact without MSAA either: Burnout Dominator's recursive rendering (#11100)
goes from 112k to 6k pixels off the PSP with a copy. Now depth is copied
wherever the backend can copy just part of it (Vulkan).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ConvertStencilFunc5551 rewrites INCR to INVERT when the test is EQUAL 0,
which is only right where the test passed and the value is known to be 0.
It rewrote sFail too, so a pixel that failed against a set stencil got it
cleared again. Warriors Orochi 2 draws its sky in layers that each test
EQUAL 0 and INCR on pass and fail, so every other layer drew again over
the previous one and covered the mountains (#20867): 28650 -> 10285
pixels off the PSP on Vulkan and OpenGL. NOTEQUAL 0's INVERT to ZERO had
the same problem on sFail.
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>
CheckDepthUsage marked the framebuffer's depth as used this frame before
SetDepthFrameBuffer looked for newer depth in a framebuffer sharing it, so
depth from an earlier frame never counted as newer. Brooktown High clears
depth through another framebuffer as the last thing in a frame, and the
next frame's draws test against it: everything failed the depth test and
the hardware backends showed a black screen (#7223).
Now a pass whose first depth use tests against the depth checks for newer
depth first. One that only writes depth keeps the old order, so it doesn't
copy what it overwrites anyway. Of the benchmark savestates, God of War and
Daxter now copy depth once a frame, and God of War comes closer to the
software renderer for it; Burnout and LocoRoco would have copied too
without that restriction.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The display can point inside a larger framebuffer: Silent Hill flips
between two halves of a 1024-wide 565 buffer. Presenting it found the
framebuffer and the offset, but screenshots looked up only an exact
address match, and otherwise read RAM, which with a hardware renderer
holds something stale (Silent Hill: Origins' savestate thumbnails showed
the loading screen). Screenshots of the display now use the same lookup
and read back the 480x272 at the offset.
Headless screenshots go the same way: eight Silent Hill frame dumps
(Origins and Shattered Memories) now match the PSP exactly on Vulkan and
OpenGL, from 0.8k-104k differing pixels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game does shadow mapping without depth textures (#16126):
- It renders the scene once into 44170000 with depth, then draws it again
into 44170400 (the same buffer 256 pixels to the right) with the same
depth address and an EQUAL depth test. We render 44170400 into the
same framebuffer at an X offset, which moves the depth test along with
the color, so it compared against the wrong depth and the shadow pass
drew nothing. Copy the depth over to the offset once per pass.
- It reads its 256x256 565 light-depth map as CLUT32. The reinterpret to
8888 went into an existing 8888 buffer at the same address that was
only 128x128, which cut the map off below row 128. Grow it first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The viewport (and u_xywh, which must match it) used the framebuffer's
estimated drawing size, so anything drawn past it was clipped even though the
buffer has the rows and the scissor allows them. Tokimeki Memorial 4 draws its
characters to y 271 into a buffer whose height was guessed from a 256 tall
viewport: the bottom 16 rows were cut off, a line through every character, and
its arms broke when the two halves were stitched (#6379, #11249).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ResizeFramebufFBO makes the resized buffer the current render target, and
the rebind after the upload then kept drawing to it. The game's draws to its
own render target went into the uploaded buffer until it changed the
framebuffer pointer. God of War: Ghost of Sparta and Chains of Olympus lost
most of their scenes this way (#12215, #11953, #16170, #11123, #12169).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FlushImm bound the render target with whatever format the last regular prim
left in gstate_c.framebufFormat. ThrillVille draws immediate prims first, so
its 5551 framebuffer was created as 565 and displayed with the wrong colours.
Co-Authored-By: Claude Opus 5.5 <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>