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>
The float depal shader built the index as a * 128 + b * 64, but the byte the
GE reads is the pixel's high byte, a << 7 | b << 2 | g >> 3, so the glow came
out about a third as strong as on the PSP.
The depal pass also reset curTextureWidth to the buffer width, undoing the
doubled width that U (which counts bytes) needs. Each 240-pixel strip then
sampled the whole framebuffer squashed in half, leaving a ghost and a seam
down the middle. The crop bounds mixed bytes and pixels the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DIRTY_MIPBIAS wasn't part of DIRTY_BASE_UNIFORMS, so on Vulkan and D3D11 a
new bias (u_mipBias for 3D textures, u_texLodBias since the last commit)
only reached the shader along with some other base uniform. The fast prim
loop also changed texlevel in auto mode without dirtying it. Fixes the F1
Grand Prix railings on Vulkan, where the texture was drawn from the
smallest mip level.
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>
It needs GL 4.3 or GLES 3.1. On Apple's GL 4.1 every depth texture bind (Cars
Race-O-Rama's depal from depth, for one) raised GL_INVALID_ENUM, which then
showed up at whatever checked glGetError next. Without the mode a
depth-stencil texture samples depth, so nothing else changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both DirtyVRAM and GetTargetFlags counted blocks from the size alone, so a
range starting partway into a block missed its last block: a write there didn't
mark it dirty, and a snapshot didn't check it. And GetTargetFlags marked the
partly covered last block clean, since its `i < blocks` was always true. Either
way a later snapshot could be flagged unchanged while the dump didn't have the
data. Memcpy and memset also marked partly covered blocks clean.
Checked against the recorder's own copy of what the dump delivered: recording
from savestates, 4511 of Cars Race-O-Rama's unchanged snapshots and 103 of MX
vs ATV Reflex's didn't match VRAM (small textures at unaligned addresses), and
none do now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GLRFramebuffer's destructor binds the default framebuffer, while the queue
runner's cache still had the previous one as bound. A following readback from
that framebuffer skipped the bind and read the default framebuffer instead.
Cars Race-O-Rama and MX vs ATV Reflex recreate framebuffers every few frames,
so their luminance readback came back as zeros on about half the frames and
their brightness adaptation went wrong on GL. Now GL reads the same values as
Vulkan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The same engine as Cars Race-O-Rama, with the same 8x8 copy out of VRAM. Without
the readback the value the CPU reads never changes, so its brightness adaptation
stays frozen; with it, it follows the software renderer's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 16-to-32-bit reinterpret sampled a quarter texel either side of the
render pixel, which only lands in the two 16-bit halves of a 32-bit pixel at
1x: at 3x, two of every three render pixels took both halves from the same
16-bit pixel. The 32-to-16-bit one picked the half by render pixel too. Now
both go by the PSP pixel. Cars Race-O-Rama rewrites its 8888 frame through
565 views in 16-pixel columns, and drew vertical stripes at 2x and up
(#15914); now it renders like 1x. Nothing changes at 1x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game reduces the scene to 8x8 on the GPU, block transfers that into RAM,
and the CPU sets its brightness adaptation from it. With BlockTransferAllowCreateFB
the copy went into a GPU-side buffer instead, the CPU read stale RAM, and the
game kept getting darker on the hardware backends (#15914).
New compat flag BlockTransferReadbackSmall: transfers of up to 64 pixels into
RAM are still read back, a frame late, so it doesn't stall. Brightness from
the issue's savestate now follows the software renderer's (72 vs 84 at 6 s,
84 vs 95 at 20 s, from 25 and falling), at the same speed as before.
Co-Authored-By: Claude Opus 5.5 <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>
Add DoCString() for standalone fixed-size strings, and
TerminateLoadedCString() for names inside structs that are loaded whole.
These all get used as C strings (GetName(), logs, the debugger), and are
always terminated when created. Module names are copied from the ELF with
strncpy, so terminate those on creation too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The PGD block size comes from the file header (or a savestate), and is
used to size the block buffer and as a divisor and mask, so require a sane
power of two. On load, force the next read to fill the buffer, which isn't
saved. sceIoDread only checks the listing index against the end.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DirectoryFileSystem reopens the open files' guest paths on load, with
their original access mode, so a crafted state could create or truncate
host files outside the memstick or game folder. VirtualDiscFileSystem had
the same problem for reads. Guest paths never contain ".." here, the meta
file system resolves them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FillHeads() trusts start/end, so a bad slab wrote out of bounds of heads_.
On failure, reset the map since heads_ may point to the deleted old slabs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Out-of-range PCM positions made ReadSamples() compute a huge size and write
past the output, and sampleFrac/pitch/curSample index fixed buffers.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CALL/RET only check stackptr against exactly 32 and 0, and getList() doesn't
check the ID at all.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
waitType, thread priorities, MipsCall::numArgs, action type IDs and pending
interrupt numbers all index host arrays (some of them function pointer
tables) without further checks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A savestate could replace the demux buffer length after the buffer was
allocated from the media engine ringbuffer size. The saved length then
controlled a copy into the smaller allocation.
Deserialize into temporary values and reject lengths or cursors that do
not fit the constructor-established buffer before copying any data.
A savestate could specify a queue size larger than its separately
serialized capacity. Restoring the queue then calculated a negative
starting index and copied attacker-controlled IDs before the allocation.
Validate the logical size, capacity, and available serialized bytes
before allocating or restoring a queue.