For GPUs without depth clamp (old Mali), found by comparing with
--disable-gpu-features=depth-clamp on the Mac:
- Write Z / 65536 like the vertex shader, not / 65535.
- Pass the fragment shader Z after the vertex depth rounding, which hardware
clamp sees too (Puyo Puyo Fever 2's ground at Z = 0).
- Also clamp draws whose Z only touches 0 or 65535.
- Draws that skip the bounds check (skinned, morphed) get the fragment clamp
and min/max discard wherever the hardware versions would be on, since those
don't depend on the check either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading a 16-bit framebuffer as CLUT8 with u = 1 + 2x puts every pixel center
exactly on a byte edge. The GE lands on the edge only when the sprite's u
gradient is exact (gpu/probe exp256), otherwise just below it. The software
transform now nudges the short sprites' u down a little, sprite by sprite, and
the shader truncates to the GE's 1/16 texel. SOCOM keeps reading its pixels'
high bytes; Syphon Filter and Resistance: Retribution's 64x128 bright pass
now reads the next pixels' low bytes, as on the PSP.
Syphon Filter's warehouse and dark room dumps get much closer to the PSP
(18310 -> 7192, 9885 -> 3925 px on Vulkan), and Killzone: Liberation's improve.
Resistance's and Syphon Filter mission 5's get further away, from the 5551
precision difference their bright pass also depends on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GE samples at the pixel center like the GPU; the comment saying it uses the
corner was wrong. SOCOM's u = 1 + 2x reads the high byte because a sprite's
fixed-point UV gradient falls just short of a texel edge (gpu/probe exp87,
exp163). Move to the PSP pixel's center and 1/16 byte below it, the GE's step,
instead of to its left edge. That's the same byte for one or two bytes per
pixel, but not for four, where the edge could pick the component before. No
frame dump changes, at 1x or 2x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
int(t * 254.99) gave each byte value minus one: c / 255 * 254.99 lands just
below c. With 255.99 it's c for every byte. The texel also gets the best
available precision: in fp16 the product rounds, and 16 of the 256 bytes
came out one low. Burnout Dominator's car select lens flare gets closer to the
PSP on Vulkan (57 -> 48 px); nothing else in the frame dump set changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
They reach the thousands (bytes across a wide framebuffer), past what fp16
holds to a fraction, and NVIDIA's Vulkan driver runs the default lowp as fp16
(as in #22481). The byte picked out of each pixel could then land on the wrong
one. No change on the M1.
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>
The shader depal CLUT8 mode now handles 565, 5551 and 4444 framebuffers
(the format goes in the depal format bits, which this mode didn't fill
before), rebuilding the 16-bit pixel and taking its low or high byte.
This replaces SOCOMClut8Replacement, which assumed the high byte.
- The GE samples a pixel at its corner, the GPU at its center. With u
stepping two bytes per pixel (SOCOM and Bend's games use u = 1 + 2x)
that's a whole byte, so the shader steps back to the corner.
- The CLUT index shift, mask and start are applied, as in the other
depal modes.
- The self-render copy region counted CLUT8's U in pixels, not bytes.
SOCOM, Star Wars: The Force Unleashed and the NPJG90068 dumps improve a
lot. Syphon Filter and Resistance: Retribution now read the right bytes,
which exposes a 16-bit precision difference in their bright pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flare's occlusion result is rendered into every other 16-bit pixel of a
palette whose other half is uploaded from memory, then read back as a CLUT.
Two things broke it at 2x:
- The CLUT8-from-8888 shader depal picked the byte out of each pixel using
the scaled texture size, so at 2x it read the wrong byte. It now works in
PSP units (from u_texclamp, which it now always gets) and samples the
pixel's center, not its edge, which is a render pixel boundary when
scaled.
- The palette upload was linearly filtered when stretched, blending each
16-bit half with its neighbour. Burnout Dominator joins Ridge Racer in
NearestFilteringOnFramebufferCreate.
1x and 3x only worked because their sample points happened to land inside
the right texels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MoltenVK doesn't support a sampler mipLodBias (the portability subset's
samplerMipLodBias is false), so the PSP's LOD bias did nothing on Mac and
iOS, and biased textures came out a mip level blurrier than on OpenGL and
the PSP: MLB's signs and crowd, Burnout Legends, MediEvil, Ridge Racer 2.
Query the portability subset, and where the bias is missing, pass the auto
mode bias to texture() from a uniform instead.
VK_ENABLE_BETA_EXTENSIONS, which declares the portability subset, is now
defined for App Store iOS builds too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Compute the product as (c+0.5) * (2f+1)/512, which is still exact in fp32, and
use mediump where highp isn't available instead of falling back to the old
blend. Simulated over all inputs, about 3% of fp16 results come out a level
too bright, against nearly half with the plain blend factors. With mediump
forced on NVIDIA's Vulkan driver (which runs it as fp16), the Split/Second
bloom dumps land within 2% of the highp result.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
The GE's bilinear filter truncates (4-bit weights, twice), and rounding the
GPU's finer filtered value made each texel about a level brighter. God Eater
Burst's bloom is faint and feeds back into itself pass after pass, so that
roughly doubled it: its dumps go from 15-31k px off the PSP to 0.4-3k, the
#17033 ones from 79k/107k to 25k/47k. Split/Second and Call of Duty: Road to
Victory improve too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GE's blend terms are floor((2c+1)(2f+1)/1024), which truncates, while the
hardware multiplies by f/255 and rounds, so every pass of a fixed color blend
chain came out up to a level brighter. God Eater Burst's bloom is many such
passes and ended up far too bright.
For additive FIXA/FIXB blends that aren't just 0 and 0xFFFFFF, the shader
now computes the src term exactly and lowers it by 0.5 - (2B+1)/1024, and the
blend constant is the GE's dst factor (2B+1)/512, so the hardware's rounding
of the sum lands on the GE's floor (except where the src term would go below
zero). Blend constants are passed as floats to make that possible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fragment Z is the PSP's Z / 65536, and flat draws land exactly on an
integer Z. floor(z * 65535) put that Z - Z/65536 just above the boundary, so
float error (which differs with W) could drop it by one: Patlabor redraws a
HUD icon at the same depth with GEQUAL, and the second draw failed on
Vulkan. Floor Z + 0.01 instead, and store Z / 65535, as clears and the depth
clamp path do. Also fixes Heroes Phantasia #6485.
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>
Remove two unused helper functions that were left behind after refactoring:
1. unpackUVec3: Left over from commit 4329aaa31c ("GPU: Apply color
test mask as a uint") which refactored color test to use
roundAndScaleTo8x4 with direct uint comparison instead of unpacking
to uvec3 components.
2. packFloatsTo8x4: Left over from commit aec22491fe ("Don't expand
alphaColorRef to 128 bytes") which changed u_alphacolorref from vec4
to uint, making the float-to-uint packing function unnecessary.
Both functions were defined but never called, wasting shader compilation
time and binary size for shaders that enable color testing.
Signed-off-by: Chris Healy <cphealy@gmail.com>
Seems in lowp precision (as seen on some Android devices) this got
rounded up to 256 when multiplied by 1.0, causing the u8 to overflow, giving
us the wrong value instead of nicely rounding down to 255 with the floor
function.
Fixes#19235
Note that we also offset the lookup slightly to miss the wrap-around
points. The existing 31 scale factor instead of 32, together with that
half-texel, are enough to avoid that problem.
Fixes#18300
Haven't been using these for a while.
I've come to the conclusion here that I think it's better to try to
deal with the issues using safe workarounds like copies, instead of
relying on features with somewhat iffy driver support that are not
universal across APIs anyway.