Commit Graph
48543 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 763bcde73e Sample the last texel where the projected q isn't positive
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>
2026-10-10 01:04:23 +02:00
Henrik RydgårdandClaude Opus 5.5 b9b1a29c42 Read CLUT8 from 16-bit framebuffers in the shader, replacing the SOCOM hack
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>
2026-10-10 01:04:23 +02:00
Henrik RydgårdandClaude Opus 5.5 8a852ec47c Fix the Burnout Dominator lens flare at even render scales
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>
2026-10-10 01:04:23 +02:00
Henrik RydgårdandClaude Opus 5.5 b28d70c6fe Fix the SOCOM CLUT8-from-5551 depal: index and texture width
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>
2026-10-10 01:04:23 +02:00
Henrik RydgårdandClaude Opus 5.5 b28a975d88 Update the mip bias uniforms when only the bias changes
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>
2026-10-10 01:04:23 +02:00
Henrik RydgårdandClaude Opus 5.5 1fe8071875 Vulkan: Apply the texture LOD bias in the shader where samplers ignore it
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>
2026-10-10 01:04:23 +02:00
Henrik Rydgård 0ac523c4b5 Merge pull request #22475 from hrydgard/cars-brightness
Tantalus games: Fix brightness, fix vertical stripes
2026-10-09 19:22:00 +02:00
Henrik Rydgård 1e532014e5 Merge pull request #22481 from hrydgard/vulkan-fixed-blend-precision
Vulkan: Fix the new more exact blend to use the best available precision
2026-10-09 19:17:14 +02:00
Henrik Rydgård 4464ef4efd Merge pull request #22432 from sum2012/patch4
Add some kernel NIDs and sceReg write overlay by Zcode
2026-10-09 18:57:39 +02:00
Henrik Rydgård 324e6c3954 Merge pull request #22476 from hrydgard/community-security-fixes
Two security fixes by acts-1631, and more found by Claude
2026-10-09 18:55:57 +02:00
Henrik Rydgård 8218e0b210 Merge pull request #22479 from hrydgard/record-dirty-blocks
GE dump recording: Track every VRAM block a range touches
2026-10-09 18:54:23 +02:00
Henrik RydgårdandClaude Opus 5.5 ce310e69a6 Keep the GE-exact FIXA/FIXB blend within fp16's range
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>
2026-10-09 18:50:22 +02:00
Henrik RydgårdandClaude Opus 5.5 b882155d23 Only use the GE-exact FIXA/FIXB blend with highp in the fragment shader
Old GLES devices without highp fragment floats get the previous plain multiply.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 18:43:26 +02:00
Henrik RydgårdandClaude Opus 5.5 f35ddce87f Vulkan: Do the GE-exact FIXA/FIXB blend math in highp
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>
2026-10-09 18:43:26 +02:00
Henrik Rydgård 761cf347e6 Merge pull request #22478 from hrydgard/msvc-fixes
Fix a couple of problems with recent commits in MSVC debug builds
2026-10-09 18:22:05 +02:00
Henrik RydgårdandClaude Opus 5.5 e705d62f0b GL: Only set GL_DEPTH_STENCIL_TEXTURE_MODE where stencil texturing exists
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>
2026-10-09 18:11:59 +02:00
Henrik Rydgård b0ea555b79 Fix a couple of problems with recent commits in MSVC debug builds 2026-10-09 17:52:55 +02:00
Henrik RydgårdandClaude Opus 5.5 0c9f9c05a8 GE dump recording: Track every VRAM block a range touches
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>
2026-10-09 17:41:30 +02:00
Henrik Rydgård 9bb0c92361 Merge pull request #22474 from hrydgard/softgpu-replay-targets
Fix framedump replay with SoftGPU in PPSSPP
2026-10-09 17:31:23 +02:00
Henrik RydgårdandClaude Opus 5.5 4be43ce23a GL: Forget the cached framebuffer bindings after deleting framebuffers
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>
2026-10-09 17:22:53 +02:00
Henrik RydgårdandClaude Opus 5.5 a71a037e09 Compat: Read back MX vs ATV Reflex's luminance reduction too
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>
2026-10-09 17:01:37 +02:00
Henrik RydgårdandClaude Opus 5.5 e896203f32 Reinterpret between 16- and 32-bit framebuffers by PSP pixel at higher render scales
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>
2026-10-09 17:01:37 +02:00
Henrik RydgårdandClaude Opus 5.5 a9b18a9192 Compat: Read back Cars Race-O-Rama's luminance reduction for its brightness adaptation
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>
2026-10-09 17:01:37 +02:00
Henrik RydgårdandClaude Opus 5.5 ed0d0c1aec GE dump playback: Copy a needed target snapshot at a depth mirror linearly
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>
2026-10-09 16:57:14 +02:00
Henrik RydgårdandClaude Opus 5.5 2f4ce91a15 GE dump playback: Copy render target snapshots later ones rely on in software mode
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>
2026-10-09 16:57:14 +02:00
Henrik Rydgård 7ec4d02b24 Merge pull request #22472 from hrydgard/msaa-fixes
MSAA fixes by Claude
2026-10-09 16:53:35 +02:00
Henrik Rydgård 0c195692d2 Merge pull request #22473 from hrydgard/readback-maxres-crop
Readback: Only scale a framebuffer down when it's rendered bigger than maxScaleFactor
2026-10-09 16:53:20 +02:00
Henrik RydgårdandClaude Opus 5.5 ed7de3ea76 Readback: Only scale a framebuffer down when it's rendered above maxScaleFactor
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>
2026-10-09 16:22:05 +02:00
Henrik RydgårdandClaude Opus 5.5 896eb31e87 Copy depth for offset rendering with MSAA too
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>
2026-10-09 15:49:15 +02:00
Henrik RydgårdandClaude Opus 5.5 4202368b1e Vulkan: Look up multisampled framebuffers through a CLUT by their first sample
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>
2026-10-09 15:49:15 +02:00
Henrik RydgårdandClaude Opus 5.5 70c5b8f07f Vulkan: Interpolate texture coordinates and colors at the centroid
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>
2026-10-09 15:49:15 +02:00
Henrik RydgårdandClaude Opus 5.5 71367037c0 Copy depth between framebuffers instead of drawing it, where supported
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>
2026-10-09 15:49:15 +02:00
Henrik RydgårdandClaude Opus 5.5 a521f257ce CmdLine: Add --msaa
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:49:15 +02:00
Henrik Rydgård 06fbbf1218 Merge pull request #22471 from hrydgard/parallel-loop-max-threads
Allow specifying a cap on the number of threads used in ParallelLoop
2026-10-09 15:38:09 +02:00
Henrik RydgårdandClaude Opus 5.5 6e0d6f4357 Savestates: Terminate fixed-size name strings on load
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>
2026-10-09 15:32:06 +02:00
Henrik RydgårdandClaude Opus 5.5 9372d4b490 sceIo: Validate PGD block sizes and dir listing state
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>
2026-10-09 15:32:06 +02:00
Henrik RydgårdandClaude Opus 5.5 864778a11b FileSystems: Reject parent directory references in savestate file names
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>
2026-10-09 15:32:06 +02:00
Henrik RydgårdandClaude Opus 5.5 1d18d182e1 MemSlabMap: Validate slab ranges from savestates
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>
2026-10-09 15:32:05 +02:00
Henrik RydgårdandClaude Opus 5.5 d7a577044f Sas: Validate voice positions and pitch from savestates
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>
2026-10-09 15:32:05 +02:00
Henrik RydgårdandClaude Opus 5.5 0bcf7c8e2b Ctrl: Validate the sample buffer indices from savestates
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 15:32:05 +02:00
Henrik RydgårdandClaude Opus 5.5 65fdadf5dd GE: Validate display list stack pointers and pending interrupt list IDs on load
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>
2026-10-09 15:32:05 +02:00
Henrik RydgårdandClaude Opus 5.5 8e6f8de19b Kernel: Validate thread, MipsCall and interrupt indices from savestates
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>
2026-10-09 15:32:05 +02:00
Henrik Rydgård 8b626e0648 Merge pull request #22466 from hrydgard/hw-framebuffer-fixes
HW backends: Various framebuffer fixes
2026-10-09 14:50:05 +02:00
Henrik Rydgård 0810fee60f Merge pull request #22469 from hrydgard/virtkey-create-frame-dump
Add bindable key to create frame dumps, allow headless to save high-res screenshots
2026-10-09 14:49:12 +02:00
Henrik Rydgård 53147de87d Merge pull request #22470 from hrydgard/mipsstate-standard-layout
Make MIPSState standard-layout again, so offsetof is valid
2026-10-09 14:49:00 +02:00
Henrik Rydgård 831acb099e Allow specifying a cap on the number of threads used in ParallelLoop 2026-10-09 14:33:58 +02:00
Acts1631 b0e8b20acd Validate MPEG demux savestate bounds
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.
2026-10-09 14:04:34 +02:00
Acts1631 62ee0879aa Validate thread queue savestate geometry
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.
2026-10-09 14:04:34 +02:00
Henrik Rydgård 9948c57e13 Make MIPSState standard-layout again, so offsetof is valid
Turns out that the actual problem here was "private", not the vector.
2026-10-09 13:54:28 +02:00
Henrik Rydgård 88f9379376 Merge pull request #22450 from hrydgard/atrac-mpeg-security-fix
Fix Atrac heap overflow and MPEG uninitialized memory disclosure
2026-10-09 13:51:22 +02:00