Without them nothing culls triangles beyond the far plane: depth clamp draws
them, as with the stands over the pitch in FIFA Soccer on macOS. Fixes FIFA
and Hellboy on OpenGL there, and DBZ Tenkaichi Tag Team, Monster Hunter 3rd
and Street Riders on both backends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With one vertex behind the near plane, the GE splits the remaining quad from
the vertex before the clipped one (gpu/probe exp43, exp44, as in SoftGPU's
Clipper.cpp). The other diagonal could put a far off-screen near-plane vertex
in both halves, so range culling dropped the whole triangle: the floor in
#16131's box test vanished. The index generator reverses triangles to flip
culling, so the batch's order is passed along.
Triangles entirely beyond the far plane are now culled even when in front of
the near plane, with the cull-distance epsilon, so z/w = 1.0000172 (exactly
1.0 to the GE) still counts as inside.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing writes depth then, so the shader write is wasted. On Apple's OpenGL
it also broke Tales of Phantasia (PixelDepthRounding): once the shader writes
gl_FragDepth, a discarded fragment still writes stencil, so a color-tested
stencil pass marked the whole area and the background tiles drawn with
stencil EQUAL 0 vanished.
New FS ID bit, so both shader caches get a version bump.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the PSP the color and alpha/stencil write masks apply to clears like to
drawing (gpu/stencil/writemask, ppsspp-re geprobe exp254). Clear-mode draws
now take the same per-channel heuristic as other draws, and a clear with RGB
masked is drawn rather than done as a hardware clear.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Partial red, green and blue write masks now always go through the shader with
a framebuffer read. Across the frame dump set this only improves games outside
the old list (Split/Second, Ultimate Ghosts 'n Goblins, older DiRT 2 and OutRun
dumps without a game ID). Partial alpha masks keep the old heuristic: alpha is
the stencil, with its own write mask, and taking the shader path for them made
Pro Yakyu Spirits and Gangs of London worse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the PSP the stencil is the alpha bits, so after the colors of another
format are reinterpreted into a 5551, 4444 or 8888 buffer, its stencil has to
follow the new alpha. Ultimate Ghosts 'n Goblins sets 5551 alpha with a masked
565 blue write and then stencil tests on it; Vulkan drew its glow through a
stale stencil. The memory stencil upload's pipeline and passes are shared.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The vertex shader stores depth as z / 65536, so reading it back with 65535
came out one too low whenever z's fraction was below z / 65536. Both the CPU
conversion and the shader path (used on GLES and at higher render
resolutions) had it. The shader also ran at the fragment shader's default
lowp, which mobile GPUs honor; 24 bits of depth need highp, sampler included
on GLES.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GLES texture cache keeps 16-bit CLUTs in clutBuf_ converted to its own
component order, and GetClutTexture converted them again, so Fushigi no
Dungeon 4's tiles came out black on OpenGL. The framebuffer depal path
already passed clutBufRaw_.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The separate depal pass reads whole pixels, not the byte each texel picks, so
Star Wars: The Force Unleashed's color grade turned magenta. Vulkan and
OpenGL now match each other and the PSP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HLSL types an unsuffixed hex literal as uint, so asint(x) - 0x3F800000
went unsigned and wrapped for every x below 1. The huge t then turned
into a NaN, and anything lit with specular or powered diffuse came out
black on D3D11 (the flags in Everybody's Golf's language select, #22418).
GLSL types it as int and was unaffected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
The exact path evaluates its own columns, so the float SampleU results went
unused. Pursuit Force's frame dump replays 16% faster in the software
renderer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each quad is its left edge down, the diagonal from the bottom left up to the
top right, then its right edge down (gpu/probe exp175, exact; fixes
gpu/primitives/bezier and spline, whose end pixels depend on direction and
order).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
The cross product of two tangents, each the difference of the de
Casteljau's last two points: the row's along u, and the columns' last
points evaluated along u for v. The lerps pass their operand through at
the patch edges. Vertex normals stay unused (gpu/probe exp142-143).
Patch normals are now also generated for shade mapping with lighting
off, on every backend: the GE uses them there too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
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>
- 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>
- Delete TexCacheEntry objects dropped on rehash instead of leaking them.
- Don't leave a released null entry in cache_ when the framebuffer match
returns before the slot is refilled.
- Reset clutRenderAddress_ in Clear(), which releases the dynamic CLUT FBOs.
- Don't cache a null texture in drawPixelsCache_ when creation fails.
- Fix the reversed subtraction in the failed-FBO retry check.
- Remove the never-taken buffered-rendering early-out in UpdateRenderSize.
Taking it would leave existing VFBs without an fbo.
- Include smoothedDepal in the depal shader cache key, and print/parse the
debug IDs as 64-bit.
- Release depal pipelines through Draw2DPipeline::Release so the shader
source isn't leaked, and make that null-safe.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The blit cost remembered only the last buffer a decoder wrote into, forever.
Move the texture cache's video list (with its ageing out a few flips after
the last write) into GPUCommon, so the texture cache, the blit cost and
SoftGPU all share one. That also counts both of a double-buffered player's
frames, which exposed that a clear drawn with texturing still enabled was
being charged as a blit - skip clears and draws without texture coordinates.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
videos_ only learns about the CSC output, so the texture we actually draw is
just an ordinary 512x512 8888 texture whose contents happen to be different
every frame: hash, miss, throw it in the secondary cache, rebuild, forever.
So track the copy. NotifyVideoCopy marks the destination as video when the
source is, and the copy funnels call it: sceDmacMemcpy, sceKernelMemcpy, and
the four replaced memcpy/memmove variants. It sits outside their "is either
side VRAM" gate, since a RAM-to-RAM copy of a frame is still a frame.
Being a video texture also gets it forced linear filtering and keeps it out of
texture upscaling, which is what you want for a video either way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A `video = true` in textures.ini opted a pack into replacing and dumping video
textures. Both halves are a bad deal. Dumping writes a file per decoded frame,
which fills a disk rather than producing anything a pack can use, and replacing
means a hash lookup on content that is different every frame and will never be
found twice.
It was also the only reason the texture cache still hashed video textures at
all, so it cost every game that has ever played a cutscene, not just the packs
that set it.
Video textures are now never replaced and never dumped, and skipHash is simply
isVideo. An existing ini keeping the key is harmless - unknown options are
ignored.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
NotifyWriteFormattedFromMemory appends to videos_ unconditionally. A game
blitting its decoded frame to the display buffer does that every displayed
frame while it waits for the next one to decode, so the same two or three
addresses come back over and over: Death Jr pushes 733 entries where there are
two distinct buffers, Tekken 6 around 53,000 where there are three. IsVideo()
walks that vector linearly on every texture. Refresh the matching entry instead
of appending a new one - Death Jr now holds 2 entries and Tekken 6 holds 18,
peak size 2 and 3.
The other half is the two TODOs that were already sitting there. A video
texture is new every frame by definition, so re-hashing it only confirms what
the VIDEO flag already said, and the secondary cache has nothing to offer a
frame that will never recur. Skip both, and with them the secondary lookup that
would otherwise key off a hash we no longer compute.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>