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>
Read as an integer, a float's bits are its log2 with the mantissa a
straight line between powers of two, scaled by 2^23, and writing an
integer back is the matching exp2. That's exactly the GE's
approximation, without log2/exp2/floor. pspPow now also returns 1 for
e <= 0 itself, so the callers drop their checks. Shader languages
without integers fall back to a true pow.
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>
Environment map S and T are (N.L + 1) / 2 with L the light's vector as
lighting uses it: from the vertex to the light for point and spot lights,
a zero vector staying zero, and the half vector for a light that does
specular. Whether lighting or the light is enabled still doesn't matter
(gpu/lighting/shademap).
The vertex shader ID now carries the type and computation of the shade
mapping lights (the ubershader reads them from u_lightControl), so both
shader caches get a new version.
Fixes the hair shine in iDOLM@STER SP (#12376).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The viewer is at infinity along view space +z, so in world space, where
lighting happens, it's the view matrix's third column rather than
(0,0,1): turning the camera moves the highlights (gpu/lighting/specular).
The shaders read it from u_view.
In a Need for Speed Carbon frame replayed on a PSP, this and the pow
bring the error of the cars from MSE 957 to 120 (Vulkan).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The shaders and the software renderer only add specular when N.L >= 0,
as the GE does (gpu/lighting/specular); the CPU lighter, used for points,
lines and rectangles, didn't check.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GE computes these powers as exp2(e * log2(x)), with log2 and exp2
each a straight line between powers of two (Mitchell's approximation),
and only uses the top 4 bits of the specular coefficient's mantissa.
Through a highlight's falloff a true pow is 10-30 steps of 255 brighter
at the exponents games use. Measured in gpu/lighting/specular.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With animated control points the pole is only nearly degenerate, so the
vanishing derivative is rounding noise rather than exactly zero, and the
absolute threshold missed it. The resulting random normals still showed
as dark patches on Pac-Man Arrangement's ghosts (#12354).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Software transform expands each point, line and rectangle to four
vertices, and gave up on the whole draw when that didn't fit
VERTEX_BUFFER_MAX. Batching only counted input vertices, so a batch over
16384 points (or 32768 line or rectangle vertices) vanished silently,
whether it came from one PRIM or several merged ones. Count the expanded
vertices when batching, and submit a PRIM too big on its own in parts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With only one patch, the open-last-edge adjustment assumed the first edge
was closed, so one knot interval came out as 2 instead of 1, and the
patch wasn't the Bezier patch a fully clamped cubic is. Positions were off
by up to about 7% of the patch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GLES sampler uniforms and texture slots for the control points and
weights, the Vulkan storage buffer bindings, and the u_spline_counts
uniform, which becomes padding (the C++ side already was).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Where all the control points along a patch edge meet at one point, like
the top of a dome, one derivative is zero and so is the cross product,
and normalizing it gave NaN. Use the limit instead, built from the mixed
second derivative. Fixes the dark spots on the ghosts' heads in Pac-Man
Arrangement (#12354).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Reloading the ini clears the per-key lookup caches, which could keep
saying "no replacement" for textures the new ini replaces.
- With ignoreAddress, hash ranges were skipped when sizing the
replacement, though ComputeHash applies them. cache_ is now keyed by
the full key, so its lookups hit too.
- Textures sharing files but differing in size, hash range or filtering
no longer share one ReplacedTexture (the first one's settings won).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They were uploaded but never drawn, and the block transfer hack drew
whatever source was left over. Draw them straight into the backbuffer
pass, without post shaders, which would need to bind their own targets.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Debuggers add them from their own threads, racing ClearTempBreakpoints
on the emu thread.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The restored draw context can be a new device, so the cached pointers
could go stale.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Map fails after device removal, leaving pData garbage. Skip the upload or
draw instead of writing through it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The draw engine is created on the loader thread while the UI thread may
already be rendering and invoking the callback.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Flush() on an empty queue now trims the state and CLUT rings, since its
callers flush because one is full and push right after.
- BinQueue::Full() uses >=, so an overshoot can't go unnoticed.
- IsExactSelfRender compares against the target the queued draws were
binned for, not gstate, which already has the next one during a flush.
- A depth test without depth writes marks the depth buffer as read.
- The DarkStalkers untextured sprite recomputes the binner state around it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With xxhash 0.8.4, XXH3 is faster than StableQuickTexHash on ARM64
(about 37 vs 24 GB/s on a Snapdragon X), and its scalar path, which
RISC-V builds get, is about as fast as the quick hash's. It doesn't
collide the way the quick hash does (#8249).
Texture replacement still uses its own hash setting, so texture packs
are unaffected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
These own GPU objects, memory or refcounts in their destructors (or assert
there that they were torn down), so a copy would double-free. Nothing copies
them today; this keeps it that way. The manager base classes cover every
backend's subclass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The texture and fragment test caches dropped their GLRTexture objects on
DeviceLost without queueing them for deletion, leaking them on every
Android background/resume. The deleter already skips the GL calls when
the context is gone.
- GLRenderManager::ThreadEnd cleared unsubmitted init and render steps
without freeing the data they own. Run them through the dry run instead,
which now also frees stereo matrices and shader code.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Release the file reference when loading a level fails or finds nothing,
since only a loaded level takes ownership of it.
- Free the PNG image when the size changed since the header was read.
- Delete the VFS when a pack without an ini has no hash-named textures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Put the anisotropy level in the sampler key, so changing it applies on
Vulkan and D3D11.
- Release CLUT textures at shutdown on GLES and D3D11.
- Fix the depth readback viewport, which squeezed the image whenever the
read rectangle was smaller than the fbo.
- Test the computed depth, not the unset result, in the equal-depth clear
check.
- Don't read back a CLUT from a framebuffer without an fbo.
- Tolerate null entries when releasing post-shader objects and CLUT
textures after a failed creation.
- ImGe: Don't crash on a framebuffer without an fbo, or on GetVFB under the
software renderer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DeviceLost saves the cache and clears everything, and the next save wrote
back only what was drawn since, so each Android background/resume cycle
shrank the cache.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
RecordNextFrame now refuses while a recording is active and during frame
dump playback (which asserted on the next replay). The callback handoff to
the CPU thread is locked, and a failed file open no longer crashes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The block transfer overlap check passed the stride in pixels where bytes
are expected, so it only covered part of the rectangle.
- A selfrender/selfdepth flush in UpdateState dropped the current draw's
pending writes and reads, so later transfers didn't wait for it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A list dropped for a bad pc or a GE error stayed RUNNING: its ID was never
freed and sceGeListSync on it never returned. Complete it like a finished
one.
- FlushImm switches to through mode and another vertex decoder, so flush the
queued draws first even when the immediate flags match.
- Clear leftover temporary GE breakpoints when setting or clearing the next
break, so a step that never got there doesn't trip later.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Flush before the queued draws would decode more than VERTEX_BUFFER_MAX
vertices. The batch was limited by index count, which doesn't bound a
sparse index range, and DecodeVerts silently stopped while DecodeInds
still emitted indices for the undecoded draws.
- Give TestBoundingBox its own scratch buffer. It used offsets in decoded_,
which can hold decoded vertices that aren't flushed yet.
- Read 32-bit indices the way the PSP does, ignoring the upper 16 bits.
IndexConverter and the fast bounding box test used all 32, so a game
setting them indexed far past the decoded vertices.
- D3D11: Flush in FinishDeferred like the other backends, since indices
are still read from PSP memory at flush time (#10095).
- Don't JIT new vertex decoders once the code space is full.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Wait on actionComplete instead of a bare condition variable wait, which
could miss the wakeup and hang until resume.
- Serialize requesters, so two debuggers can't overwrite each other's
action, and make SetCmdValue/FlushDrawing wait too.
- Give up and withdraw the request when stepping ends, instead of waiting
forever (this deadlocked game shutdown against the Win32 GE debugger).
- Run requests during CPU stepping, which already accepted them.
- Clear the stepping state on Core_Resume from GE stepping and on
GPU_Shutdown.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The OpenGL and Vulkan shader caches store raw shader IDs (and, for Vulkan,
pipeline keys) on disk. Add the rule to AGENTS.md and point to it from the
persisted types.
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>
- DDS files with mips stored level 0's file reference in level 1 too, so it
was freed twice.
- The VFS was deleted on config changes (and ini reloads) while load tasks
still used it. Now each cached texture waits for its task and releases its
file references through the old VFS first, and reloads afterwards.
- A failed ini reload turns replacement off instead of leaving it on without
a VFS.
- Release file references on purge.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>