Commit Graph
50315 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 04bdf46bc6 Line info: Bound the DWARF line program parse
- An extended opcode's length was added to the position unchecked, so a huge
  one wrapped back to the opcode itself and looped forever (and, with a row
  in the loop, grew the table until memory ran out). Lengths must now fit in
  the unit. This runs on every loaded module and on a .elf next to the EBOOT.
- Cap the rows at a couple per instruction and the distinct files at 65536,
  since one-byte opcodes and short names could otherwise make gigabytes of
  tables from a large file.
- ELF and unit bounds checks in 64 bits (they wrapped on 32-bit hosts), the
  section headers copied out rather than read unaligned, a bounded compare
  for the section name, and line and SLEB arithmetic that can't overflow.
- Rows at the same address now keep the last, as the comment says.
  std::unique kept the first, so an end-of-sequence row could hide the next
  sequence's first row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:26:53 +01:00
Henrik RydgårdandClaude Opus 5.5 366a0744e6 Savestates, cheats, symbols, misc: Bound parsing of untrusted data
- Savestates: containers of non-raw elements (strings, classes) now grow as
  their data is read, since a count bounded only by the bytes left could still
  ask for gigabytes. Cap the decompressed size at 256 MB and fail cleanly if a
  buffer can't be allocated, instead of terminating.
- Symbol maps: a line starting with a NUL underflowed the newline trimming,
  which then walked back through the stack writing NULs. These load
  automatically from next to the ISO.
- Cheats: stop if an IfAddr skip wraps the line index (32-bit hosts), which
  ran the same code forever.
- Atrac: reject negative chunk sizes in ParseWaveAT3 (an infinite loop), and
  check the AA3 header and fact chunk are long enough for what's read.
- SND0 loop count check divided rather than multiplied (wrapped on 32-bit).
- Replays: don't index an empty data vector, or report more bytes than were
  copied.
- UTF-8: u8_nextchar read past the end when called there, and the emoji check
  re-measured its string_view with strlen. StringWriter::F moved backwards on
  vsnprintf errors. JSON getInt clamps instead of an undefined cast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:23:17 +01:00
Henrik RydgårdandClaude Opus 5.5 c5673294ab Firmware, PS1, PKG: Fix wrapping offsets
- PSAR: an entry's chunk size could wrap the u32 position back to an earlier
  entry, looping forever (this runs on a disc's DATA.BIN at boot). Entries
  must now end inside the archive.
- MDS: the session offset check wrapped in u32, and the track name block
  wasn't checked at all, both reading gigabytes past the file.
- PKG: check the data range without the u64 sum that could wrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:17:59 +01:00
Henrik RydgårdandClaude Opus 5.5 50e9b6691c Archives, caches, HTTP: Bound sizes from headers and servers
- Zip VFS: cap entries at 512 MB (the old 4 GB cap let a 2^32 size wrap the
  allocation to nothing on 32-bit hosts), fail reads that come up short
  instead of returning uninitialized memory, and return 0 rather than
  (size_t)-1 from Read on a corrupt stream, which crashed texture loading.
- Zipped disc images are capped at 4 GB and must fit a size_t before we
  allocate them.
- Installing a zipped ISO took the name after the last '/' only, so a name
  using backslashes could write outside the games folder on Windows. Require
  a safe filename, and report failures instead of always succeeding (which
  could delete the source zip after a failed extract).
- Disk cache files: reject block sizes we don't create (0 divided by zero),
  drop index entries that share a block (eviction then never finished), and
  stop evicting when no progress is possible. Negative or empty reads no
  longer index the cache out of bounds.
- HTTP: reject implausible Content-Length values, and don't cache files in RAM
  that can't fit (the size wrapped on 32-bit hosts, overflowing the buffer).
- Gzip-encoded HTTP responses are capped at 256 MB decompressed.
- Directory VFS: don't open directories as files, check Ftell, and bound the
  case-insensitive index recursion.
- Skip zip entries without a name when identifying zips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:16:53 +01:00
Henrik RydgårdandClaude Opus 5.5 382a8eecea Images: Limit sizes, and handle ZIM and JPEG load failures
- JPEGs over 8192 pixels in either dimension are rejected (in jpgd, so it
  covers sceJpeg too), and its allocation size is computed in size_t, since
  the int product overflowed and gave a heap overflow. Image loading also
  checks the caller's maximum before decoding.
- Image loading premultiplied a ZIM that failed to load (null pointer), and
  treated 16-bit ZIMs as 32-bit (twice the buffer). Reject those, and ZIMs
  over 8192, and a zlib stream that ends early.
- PNG: pngLoadPtr's error path could free a caller's uninitialized pointer,
  and pngLoad didn't check its allocation or decode.
- Texture replacements: reject header sizes of 0 or over 8192 for every
  format (DDS sizes overflowed an int), transcode KTX2 mips with their own
  height, and don't copy more ZIM rows than were decoded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:11:00 +01:00
Henrik RydgårdandClaude Opus 5.5 56cf35ac95 ELF, modules: Bound header fields before using them
- ElfReader: do the relocation segment and table bounds checks in 64 bits
  (p_offset + p_filesz wrapped, and relocations were read gigabytes past the
  file), range-check R_MIPS_32's four bytes, require section names to end
  inside the file, and check the Rel2 field widths fit the 16-bit commands.
- Pair HI16 relocations with their LO16 in linear time, and cap the total
  relocation data processed at the file size. Several sections can point at
  the same table, and a table of only HI16s was quadratic.
- PRX: reject decrypted sizes larger than the input, and check the KIRK input
  (which starts past the header) fits rather than only the output.
- Module loading: cap elf_size before allocating, zero the buffer, give gzip
  the real input length, range-check the module info, export entries and
  their NID tables and thread parameters instead of their first byte, and
  don't trust header names to be terminated.
- PBP modules need the offsets that are read, sceKernelLoadModuleByID copes
  with a seek past the end, and sceKernelLoadModuleBufferUsbWlan checks the
  buffer.
- ParamSFO: table offset math in 64 bits (wrapped on 32-bit hosts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 12:07:13 +01:00
Henrik RydgårdandClaude Opus 5.5 3413784d8e Block devices, ISO: Validate disc image headers and tables
- CSO: ReadBlocks with count 0 no longer runs off the index (the path table
  ioctl did this for any table under a sector), plain frames are only copied
  from what's actually in the read buffer, and images over 8 GB are rejected,
  here and in the compressor (whose index is sized from it).
- UDF: do the allocation descriptor bounds check in 64 bits, cap and
  range-check the root directory before allocating it, and require a full
  file identifier descriptor.
- CHD: reject unit sizes the block math can't handle (divide by zero, reads
  outside the hunk), report short reads to libchdr, and don't hand out a
  failed hunk.
- NPDRM: only treat the block buffer as cached once it was filled.
- LZRC: stop match copies at the end of the output, reject distance 0.
- ISO: cap directory sizes at 4 MB, check that a record's header fits in
  the sector, and mark a directory read on errors so its children aren't
  appended again on every lookup.
- Cap the PARAM.SFO reads at 1 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:59:21 +01:00
Henrik Rydgård 9f5c502d8f Merge pull request #22936 from hrydgard/cpu-clip
GPU: Clip and cull on the CPU while keeping hardware transform, with CPU-lit clipped triangles
2026-11-05 10:04:43 +01:00
Henrik RydgårdandClaude Opus 5.5 05b5ef163f GPU: Light the triangles the CPU clips, in a second vertex stream
The GE clips lit triangles after lighting, interpolating the lit colors. So that lit draws can take the CPU clip
path too, the CPU now lights the ends of the edges it clips, once per vertex, and the vertices clipping makes take
the interpolated colors. They come in a second vertex stream, 8 bytes a vertex (cpulit0 for color0, cpulit1 for
color1, its alpha set where they are to be used), and the vertex shader uses them in place of its own lighting
there (VS_BIT_CPU_LIT). The original vertices keep the shader's lighting.

Every lit draw gets the stream where the CPU lights the triangles it clips (no clip distances, or a game flagged
CorrectCullAfterClip), with a static buffer of zeroes when nothing is clipped, so whether a draw is clipped
doesn't change its pipeline. The colors take attribute locations 3 and 4, those of the skinning weights, which
are never vertex attributes, to stay within GLES2's eight.

Implemented for Vulkan, OpenGL, D3D11 and Metal. Shader cache versions are bumped (new shader ID bit and vertex
format bit).

Regression risk: a new vertex shader input and second vertex buffer on GPUs without clip distances, every lit
draw on those. Tested on Mali (G52, G71, G78) and PowerVR (BXM, GE8320) phones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 00:53:12 +01:00
Henrik RydgårdandClaude Opus 5.5 e1ba8145db GPU: Clip and cull draws crossing the Z planes on the CPU, keeping hardware transform
Where the GPU can't clip and cull like the GE (no clip or cull distances, the near plane not on the minimum Z,
or a game flagged CorrectCullAfterClip), a draw whose bounding box crosses a Z plane used to go to software
transform. Mixing that with hardware transform on other passes over the same surface made their depth tests
fail on PowerVR.

Now the CPU decodes such a draw to RAM, clips its triangles at the near plane like the GE (into one triangle or
two), drops those entirely beyond the far plane, and range culls the vertices clipping makes. The vertices,
including the new ones interpolated from the decoded ones, then go through hardware transform like the rest.

Lit draws, flat shading and normal-based texgen aren't linear in the decoded vertices, so they're left to the
clip distance where there is one, and culled only when culled whole. Without clip distances, lit draws still
take software transform.

Regression risk: draws that cross a Z plane on GPUs without clip or cull distances, and the flagged games, now
take a new path, on every backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 00:42:26 +01:00
Henrik RydgårdandClaude Opus 5.5 39b7e8dc2f Vulkan, OpenGL, Metal: Let draws bind a second vertex stream
Vulkan pipelines can have a second vertex binding, and draws a second vertex buffer for it. OpenGL input
layout entries can come from a second buffer, with its own stride, given with the draw. Metal gets a second
vertex buffer index. Nothing uses them yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 00:29:16 +01:00
Henrik Rydgård 0051dca319 Merge pull request #22935 from hrydgard/gpu-misc-fixes
GPU: Fix black frame dump replays, the depth clamp range, and DBZ's culling
2026-11-05 00:20:40 +01:00
Henrik RydgårdandClaude Opus 5.5 1676765456 GE dump: Only draw the zero-normal reset patch on the software renderer
Only the software renderer keeps the sign it resets, and on the hardware renderers the patch made about 70 of
the frame dumps replay black (MX vs. ATV Untamed among them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:52:20 +01:00
Henrik RydgårdandClaude Opus 5.5 48aa69c748 Compat: Clip and cull DBZ Tenkaichi Tag Team after clipping
With clip and cull distances, the GPU drops ground textures on modded maps (#18561): 25551 pixels off the PSP
render on a Pixel 3a, against 1987 with the flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:06:22 +01:00
Henrik RydgårdandClaude Opus 5.5 cabbea312b GPU: Count only what's drawn in the Z range for the depth clamp
With depth clipping, what's behind the near plane is clipped away, but its screen Z still counted, and behind
the eye it's meaningless. Every draw crossing the near plane then took the fragment shader depth clamp on GPUs
without depth clamp, whose depth doesn't match the other draws' rasterized depth, so equal-depth passes failed
(Monster Hunter 3rd HD's ground on PowerVR GLES). The near plane's Z counts instead, and doesn't need the clamp
at exactly zero, where losing what rounds below is the near clip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:06:12 +01:00
Henrik RydgårdandClaude Opus 5.5 b62bcc9e12 Vulkan: Allow it on Motorola's PowerVR BXM-8-256 phones with any driver
The entry hid the Vulkan option on those (Moto G54 and similar) with drivers before 0x005d9576, for the crash
in #18681. Vulkan is the default there, and works on a G54 with that driver. The list is only used on Mac
now, so it and its check are only built there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:05:36 +01:00
Henrik RydgårdandClaude Opus 5.5 5a5c11f984 Headless: Print the number of pipelines created
Vulkan and Metal pipelines, and OpenGL programs, including those precompiled from the shader cache. Benchmark
times include the compiles, so this shows when two builds differ in how many they need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:05:36 +01:00
Henrik RydgårdandClaude Opus 5.5 77e417e59f CrossSIMD: Make the Store functions const
They only write to the destination.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:05:36 +01:00
Henrik Rydgård c68f43c731 Merge pull request #22911 from hrydgard/texcache-index
TextureCache: Find the textures a block transfer overlaps through a flat index
2026-11-04 19:28:05 +01:00
Henrik Rydgård 9366aa7dc2 Merge pull request #22933 from hrydgard/more-ui-work
Fix a bunch of UI bugs
2026-11-04 19:12:03 +01:00
Henrik RydgårdandClaude Opus 5.5 324f608a94 TextureCache: Keep the range index and the map together in a TexCacheMap
The map is only handed out const, and entries only come and go through Insert(), Erase(), Release(), Clear()
and SetBufw(), which keep the index in step. Before, every place that changed cache_ had to update the index
by hand, and one that didn't would leave a dangling pointer for the next block transfer to write through.
A new entry now gets the fields its range needs before it goes in, and the slot of one going to the
secondary cache is erased right away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:59:56 +01:00
Henrik RydgårdandClaude Opus 5.5 3863e2f90f TextureCache: Find the textures a block transfer overlaps through a flat index
Invalidate() runs for every block transfer, and walked the cache_ map from 1 MB before the address,
setting a flag on the few textures that overlap. MX vs. ATV Untamed does about a thousand a second,
each walking about 380 of its 745 textures to find six, and its one 1 MB texture means a tighter bound
on texture size wouldn't help.

Now an array of the textures' address ranges sits next to cache_, sorted by address, with textures
over 64 KB in a short list of their own, so a search only starts 64 KB back. It's updated wherever
an entry enters or leaves cache_ (including the moves to and from secondCache_) or changes its bufw.
It marks the same textures as the map walk did (compared on every call in MX vs. ATV, Wipeout Pure,
God of War: Chains of Olympus and Tekken 6). In MX on a Cortex-A53, Invalidate() went from 889
samples to 27.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:42:49 +01:00
Henrik Rydgård 9e972e0dd3 GameInfoCache: Parse the game ID out of save data. nice to display. 2026-11-04 18:29:51 +01:00
Henrik Rydgård 6baa0a9805 Fix issue in game tab settings with increase/decrease size. 2026-11-04 18:19:15 +01:00
Henrik Rydgård d0b271820a Make SasAudio addresses clickable 2026-11-04 18:14:33 +01:00
Henrik Rydgård 029d9582a0 Visual fixes on pause screen 2026-11-04 18:07:50 +01:00
Henrik Rydgård b5f91caae4 Windows app (not headless): Inform the user of bad command line parameters, but proceed. 2026-11-04 18:07:29 +01:00
Henrik Rydgård 3ca6ab6a79 Fix ImDebugger font screwup 2026-11-04 18:07:17 +01:00
Henrik Rydgård 9e78ef5677 Misc UI and cmdline parse work 2026-11-04 17:58:18 +01:00
Henrik Rydgård 3bc60270ea Merge pull request #22931 from hrydgard/parseuri-hardware-tests
sceParseUri: Rewrite the parser against hardware, and implement sceUriUnescape
2026-11-04 14:20:10 +01:00
Henrik Rydgård 148e9c82d2 Merge pull request #22930 from hrydgard/vblank-timing
sceDisplay/sceCtrl: Charge the vblank interrupt's and ctrl peeks' CPU time, as measured on hardware
2026-11-04 14:19:29 +01:00
Henrik Rydgård 7b2860ec42 Add icon for VR settings 2026-11-04 11:41:53 +01:00
Henrik Rydgård ac31417b71 Merge pull request #22928 from hrydgard/softgpu-zero-normals
SoftGPU: Light zero normals by the sign of the last Bezier patch
2026-11-04 11:31:21 +01:00
Henrik RydgårdandClaude Opus 5 96cda92669 sceParseUri: rewrite the parser against hardware, and implement sceUriUnescape
Nothing tested sceParseUri, and it showed. Measured on a real PSP by the new
net/parseuri tests:

- A URI without "//" was rejected outright, so mailto:, urn:, about: and even
  file:///tmp/x all failed. They parse fine; that is what noSlash is for.
- A fragment ended up appended to the path, because the code looked for "#"
  inside the query rather than the whole remainder.
- An empty path came back as "/", because Url insists on one.
- Ports take at most five digits and at most the value 65536, which wraps to
  zero; anything longer or larger is refused. A host runs while the characters
  are alphanumeric, dot or dash, so an underscore or a bracketed IPv6 address
  falls through to the path.
- The three error codes are real and specific - 80433060 bad URI, 804331FE no
  output pointer, 80433022 work area too small - where everything returned -1.
  Leaving out either area is a size query that succeeds.

So sceUriParse no longer goes through the Url class, which is built for HTTP and
has nowhere to put a fragment.

sceUriEscape kept the wrong two characters: hardware leaves "*" alone and escapes
"~", which is the other way round from RFC 3986. It also fills what it can before
reporting a buffer too small, and leaves the length output alone when it does.

sceUriUnescape was a null entry, so calling it got "library not linked". It is
implemented, including the odd positive return value - one per plain character
and five per escape - that the hardware hands back.

Also lists the two exports libparse_uri.prx has that we did not know about, so a
game calling one is named in the log rather than appearing as a bare NID.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-04 11:11:20 +01:00
Henrik RydgårdandClaude Opus 5.5 201e1be9ef SoftGPU: Light zero normals by the sign of the last Bezier patch
The GE keeps the sign of the z of the last Bezier patch's normal at its last
corner (flipped by patch facing, 0 when sideways or degenerate), and lights a
normal that is exactly zero as that sign times (1,1,1) in model space
(ppsspp-re geprobe exp226-253). Normals the tessellation generates don't take
it. Nayuta no Kiseki and Zettai Zetsumei Toshi 3 rely on it.

Frame dump playback draws a flat +z patch after INIT, as the PSP replayer does
before each replay, so both start from +1.

A run's chunks read the sign and the generated-normals flag, so changing either
waits for the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:07:23 +01:00
Henrik Rydgård c0479ac6f2 Merge pull request #22926 from hrydgard/libretro-depth-raster
libretro: Add the Lens flare occlusion (depth raster) option
2026-11-04 11:02:45 +01:00
Henrik RydgårdandClaude Opus 5.5 7af0b9d432 test.py: threads/scheduling/alarmcosts passes now
The test checks ranges instead of rounding on hardware's noise.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-04 10:41:00 +01:00
Henrik RydgårdandClaude Opus 5.5 58c009c07e Add the display/vblankphase and ctrl/peekcost tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-04 10:40:53 +01:00
Henrik RydgårdandClaude Opus 5.5 c7efd845b8 sceCtrl: Peeking costs more the more samples it copies
~2us plus ~0.2us per sample on hardware, 15us for 64 (pspautotests
ctrl/peekcost). We charged a flat 1.5us.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-04 10:40:42 +01:00
Henrik RydgårdandClaude Opus 5.5 5702b0f511 sceDisplay: Charge the vblank interrupt's CPU time, and count lines from 0
On hardware the vblank interrupt comes as the last line (285) ends, and
hcount wraps to 0 inside it. It takes ~66us of CPU from whatever runs, or
~78us with a handler, which runs ~29us in. It releases the vblank waiters
after the handlers, and they get the CPU once it's done: ~81us after it,
~91us with a handler, ~3us later for each extra waiter. The vblank ends
~818us after it (pspautotests display/vblankphase).

We charged nothing and released waiters from a separate event 48us after
the vblank, and hcount started at 1 because user code rarely sees line 0:
the interrupt takes most of it. Now the interrupt carries those costs, the
waiters are released inside it, hcount runs 0-285 over the real frame
length, and a thread that waits for the vblank pays the ~6us that makes
waiters take turns ~12us apart, as on hardware.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-04 10:40:41 +01:00
Henrik RydgårdandClaude Opus 5.5 e199241906 libretro: Add the Lens flare occlusion (depth raster) option
The app's iDepthRasterMode: Auto, Low, Off or Always on. Its default depends on the device (Low on
Android and iOS, Off without SIMD), which a core option can't express, so "Default" picks that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:30:47 +01:00
Henrik Rydgård 8f83f1ea16 Merge pull request #22925 from hrydgard/dualsrc-fallback
GPU: Write the stencil as alpha without dual source blending where the blend allows
2026-11-04 08:40:09 +01:00
Henrik Rydgård a2001917b2 Merge pull request #22919 from hrydgard/pvr-clip-cull-distance
Vulkan: Don't use cull distances on PowerVR
2026-11-04 01:02:06 +01:00
Henrik Rydgård 62d8696d5f Merge pull request #22914 from hrydgard/ignored-fault-storm
MemFault: Stop ignoring bad accesses after 1000 in one frame
2026-11-04 01:01:41 +01:00
Henrik RydgårdandClaude Opus 5.5 2027130763 GPU: Write the stencil as alpha without dual source blending where the blend allows
When the color blend uses the src alpha and the stencil has to be written as alpha, both need the alpha
output, so it took dual source blending, and without it the alpha came out wrong. Where the src alpha is
only in the src factor (SRCALPHA or INVSRCALPHA, with DSTALPHA, INVDSTALPHA or a fixed color for the
dst), the shader now applies the src factor (REPLACE_BLEND_PRE_SRC), which leaves the alpha output for
the stencil on every GPU. That's WipEout Pure's additive glows, among others. On a Mac (MoltenVK), none
of the 1,141 dumps in the frame dump collection change with dual source blending on. With it off,
God of War's black squares and one of WipEout Pure's shield dumps now match its render exactly, and
two more WipEout Pure dumps come closer (3,631 -> 2,793 and 4,642 -> 4,163 pixels off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 00:56:16 +01:00
Henrik RydgårdandClaude Opus 5.5 a41186c3b0 Vulkan: Don't use cull distances on PowerVR
On a BXM-8-256 (Moto G54), a vertex shader that writes both clip and cull distances doesn't get culled,
while either one alone works. Geometry the PSP culls got drawn: the ground in DBZ Tenkaichi Tag Team,
scenery in Monster Hunter 3rd. With cull distances off, those draws get clipped and culled on the CPU
instead, and the frame dumps match a desktop GPU's render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 00:28:32 +01:00
Henrik Rydgård aad016741c Merge pull request #22918 from hrydgard/claude-heredoc-hook
Agents: Refuse Bash heredocs with backslashes on Windows
2026-11-04 00:27:37 +01:00
Henrik Rydgård 4ea2f6506f Merge pull request #22916 from hrydgard/depth-raster-skip-list-end-wait
Depth raster: Wait for it before GE callbacks instead of at every list end
2026-11-04 00:07:02 +01:00
Henrik RydgårdandClaude Opus 5.5 e1deb5665c Depth raster: Wait for it before GE callbacks instead of at every list end
Since a37e1f3660, the CPU waits for the depth rasterizer every time a display list ends, because Syphon
Filter reads its depth buffer for the light flares right after. It reads it in the list's GE interrupt
callback though, and so do Wipeout Pure and Resistance: Retribution. On hardware the drawing is done
when that runs, so that's where to wait now, letting the worker overlap with the CPU until then.
sceGeDrawSync and sceGeListSync wait as before.

With interpreter loads from the depth buffer counted and the worker slowed down by 10-50 ms a batch,
no read in Syphon Filter: Logan's Shadow, Wipeout, Resistance or Midnight Club: L.A. Remix landed while
it was busy. Without any wait, nearly all of Syphon Filter's, Wipeout's and Resistance's did.

L.A. Rush reads its depth buffer only after sceGeDrawSync, and the new compat flag
DepthRasterNoCallbackWait skips the callback wait for it.

10 emulated seconds on a Moto G54 (two Cortex-A78, six A55):

  Wipeout Pure     7.24 s -> 6.50 s
  Resistance      17.98 s -> 13.26 s
  Midnight Club    8.75 s -> 8.01 s
  L.A. Rush        5.95 s -> 4.76 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 23:32:11 +01:00
Henrik Rydgård 7164fce7c4 Merge pull request #22897 from hrydgard/adhoc-ptp-segments
Ad hoc relay: Deliver back-to-back PTP sends about 1 ms apart
2026-11-03 23:09:50 +01:00