The triangle is convex, so once a row has been entered and left, the
rest of it is outside. This cut cycles by 5% in Wipeout Pure, where depth
raster is on for the lens flares.
The test checks the covered pixels against a per-pixel edge test.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TestBoundingBoxFast transformed and tested one vertex per iteration,
with a scalar divide and a min/max chain each vertex had to wait on.
It now transposes four vertices into lanes and does it all as vectors,
with the same operations in the same order, so the results don't change.
It was 16.6% of the emulator thread in Wipeout Pure (with depth raster
off) and 5% in GTA: Vice City Stories.
Per vertex, it's 1.8x faster on an M1, 2x on x86-64, 1.7x on a Galaxy S8+
and 10% faster in 32-bit mode there.
The test compares it with the old version on random draws in every
position and index format.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FakeJit was the native JIT for architectures without one, but the config
already forces those onto the IR interpreter, so CreateNativeJit() now
returns that instead. The vertex decoder fallback no longer needs
FakeEmitter.
JitBlockCache was the block cache of the pre-IR JITs. Its header keeps
the debug interface types the IR block caches use. Also removes the
JitInterface link hooks only it called, and the JitState fields tied to
it. This fixes a null dereference in the ImDebugger memory section,
which read GetBlockCache(), always null with the IR JITs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DrawEngineD3D11.cpp started with <wrl/client.h>, which brought in windows.h
without NOMINMAX, so its min macro broke the std::min in an inline function
of FramebufferManagerCommon.h. D3D11Util and ShaderManagerD3D11 had the same
order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resistance: Retribution sets its scissor to end at 480,272, one past its
drawing region. The CPU depth rasterizer (SoftwareRasterDepth) only
clipped to the scissor, so it wrote row 272 of the depth buffer, which is
where the game keeps its logo textures. The startup logos got a garbage
strip on hardware backends and failed to fade out.
SoftGPU already clips to the minimum of the two.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Since the sequential copies heuristic went (50696c5b1f), every partial readback
waits for the GPU. Motorstorm reads parts of the screen about 18 times a frame,
and ran about 75% slower than 1.20.4: 5403 blocking readbacks in 10 emulated
seconds, against 156.
With the flag, the first partial color readback of a buffer reads all of it into
a copy outside PSP memory, and the pieces until the next render to the buffer
are copied from there. Unlike the old heuristic, only what the game asked for is
written to PSP memory. Motorstorm is back to 153 blocking readbacks and 1.20.4's
speed, with identical output. Grand Knights History, which the heuristic was
added for, gets the flag too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each next to its .cpp, or with its neighbours in the unconditional list for
its directory. They're only for IDEs, so this changes nothing in the build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A draw that blends with the source alpha while setting the stencil (the
alpha on the PSP) needs dual source blending to write both. Without it the
alpha came out wrong, and later draws that blend with the destination alpha
differed: WipEout Pure, Juiced 2 and 16131 UCES00001 on a Mali-G71 and a
PowerVR GE8320. With framebuffer fetch, blend those in the shader instead,
which matches the Mac's Vulkan render now. Without fetch it would take a
framebuffer copy per draw, too slow for something this common.
A draw that writes no color can't need the source alpha, so it writes the
stencil as alpha directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With fetch, the shader applies the colour masks and logic ops to the real
bits, and OutRun 2006's water mask comes out like the software renderer's.
BlueToAlpha put the first step in alpha while the masked logic-op steps after
it worked on blue, so on the Adreno 840's OpenGL the water was off by twice as
much as on Vulkan. Now it matches or beats Vulkan there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ResizeFramebufFBO copied color and depth into the new FBO but left its
stencil cleared. Need for Speed Shift grows its framebuffer from 480 to 512
wide just before stencil testing, and the frame came out far too dark on
Android (112k px off the PSP on an Adreno 840, now 4k). MoltenVK happened
to keep it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PerformWriteStencilFromMemory bound the framebuffer it wrote the stencil to
and, unless it blitted, left it bound. When that wasn't the current render
target, the following draws landed in it. Draws that copy the target for
shader blending rebind it themselves, which hid this; with framebuffer fetch
they don't, and OutRun 2006's masked water draws went into the old 5551
buffer at the address instead of the new 565 one.
Over the framedumps set on Vulkan, 9 dumps get closer to the PSP (OutRun
ULUS10064_0002 12295 -> 483 px, F1 2009, God of War) and one a little further
(16131 ULUS10193, a slight brightness shift).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without clip distances, the fragment shader's min/max Z discard only came on
when both minz and maxz were set, while the clip planes take each side on
its own. Pursuit Force's sky has minz 0 and maxz 300 and lies past 300, so
the PSP drops it (its replays of 11399 UCES00019 and 11216 UCUS98703 show
that), but GPUs without clip distances drew it over the scene: 13.9k px off
the PSP on a Mali-G71, now 8 as with clip distances. The discard can now
come with the fragment depth clamp, for the side that's at the limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fragment shader's min/max Z discard declared it with the default
precision, the vertex shader with highp, and PowerVR refuses to link such a
program, so every draw that needed the discard was dropped. On a PowerVR
GE8320 that was Street Riders, Valkyria Chronicles, Flatout and Frontier
Gate, and with the discard taking one-sided depth ranges too (next commit),
most of Pursuit Force.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picking a byte out of a 16-bit framebuffer needs more than the defaults. The
texcoord reached the shader as a mediump varying, which Adreno keeps in fp16:
its byte coordinate (u times up to 2048) then picks the wrong byte for every
other one past 512, and SOCOM Tactical Strike's night vision came out striped
in the right half. On a PowerVR GE8320 the default lowp in the rest of the
shader made SOCOM's night vision and full screen effect far too bright (53k
and 17k px off the Mac's Vulkan render, now 0). Vulkan already uses highp.
Where the fragment shader has full precision, the vertex shader now writes the
texcoord in highp, and CLUT8 depal reads it that way and computes in highp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UpdateUniforms checked some locations against -1 before queueing the update,
but the GL thread fills them in when it links the program, which can be
later. On drivers that link slowly the check saw -1, the update was dropped
along with its dirty flag, and the uniform stayed unset. On a PowerVR GE8320
and a Mali-G71 the texture clamp values never arrived, so Harvest Moon's and
11655's composites sampled one point of the framebuffer (122k and 76k px off
the Mac's Vulkan render, now 0). The GL thread already skips -1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GL_TEXTURE_LOD_BIAS is desktop GL only, and the queue runner skipped it on
GLES, but GLES still reported sampler LOD bias as supported, so the bias was
dropped and textures used the wrong mip levels: Super Monkey Ball Adventure's
intro came out blurred on the Adreno 840. Report it unsupported, so the shader
applies it (GPU_USE_SHADER_LOD_BIAS), and give the GL backend the uniform that
Vulkan has in its uniform block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PosFloat loaded its clamp constants into XMM4/XMM5 but clamped with XMM2/XMM3,
and the S8/S16 morphs overwrote the morph weight pointer with the constant's
address. These only run when the JIT cache lands more than 2GB from the
constants, which CPUEquivalence's allocations made happen on Linux CI
(SplineTessellation). Checked by forcing every RipAccessible() false, which
SplineTessellation and VertexJit now pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Fixes#7395
The GE has two return address registers, and a CALL beyond them does
nothing: the list carries on after it. A signal CALL is done by the
firmware, which saves and clears them, so it starts the count over,
and the signal RET restores it. Shown on a PSP by the new
gpu/callstack/depth test.
Soul Calibur: Broken Destiny turns lighting on for its weapon trails
from a third-level CALL, so on hardware the trails stay unlit and take
their colors from the vertices. We ran that CALL and drew them white.
Plain CALLs now store a 0 base address in their stack entry, which a
signal CALL never does, so the depth can be counted without changing
the savestate layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Vulkan: Batch Jackass' tiled in-place color grade
Jackass: The Game color grades the framebuffer in 32x64 tiles, one channel
per pass, texturing from the framebuffer it renders to. Each of the 225
draws became a depal pass plus a pass drawing it back, 450 render passes
per frame. A new queue hack, enabled by the TiledDepalHack compat flag,
sorts these pairs into waves of non-overlapping tiles and runs each wave
as one depal pass and one draw pass, leaving 6.
Output is byte-identical to before on both dumps from the issue, at 1x
and 3x on desktop, and at 1x on both phones below.
Time saved per post-processed frame, from replaying the dumps from the
issue with headless --bench (each replay renders that frame once):
Redmi 9A (PowerVR GE8320): ~145ms at 1x, ~180ms at 2x
Unisoc UMS9632 (Mali-G57): ~55ms at 1x and 2x
Either is more than a whole 33ms frame, so on those GPUs this pass alone
ruled out 30fps.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fallback set the rasterizer's Z to a constant so nothing would clip, which
lost the clipping at the PSP's near plane too. Hardware depth clamp loses it
the same way, which the vertex shader can only make up for with a clip plane.
Give the rasterizer (z + w) / 65536 instead: 0 at the near plane, so it clips
there, and past w only beyond 65535 * w. And without clip distances, use the
fragment fallback instead of hardware depth clamp.
With --disable-gpu-features for all three on the Mac, the framedumps set now
differs from the full render by 15570 px instead of 184229 (WALL-E, Hokuto no
Ken, Frontier Gate, FF Zero). The full render doesn't change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
--disable-gpu-features=clip-distance,cull-distance,depth-clamp acts as if the
GPU lacked those, to test the fallbacks for old Mali on any machine. It
replaces the simulateOldMali constant. --hardware-transform=false runs every
draw through software transform.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bigger draws skipped the check and kept whatever clip flags the previous draw
left. Checked, the ones that cross the clip volume in Z get software clipping
where the GPU needs it: on the Mac (no cull distances), Valkyria Chronicles II
and Street Riders get closer to the PSP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Curves skipped the per-draw bounding box check and relied on the vertex
shader's near clip plane. On old Mali (no clip distances), that left the
geometry behind the PSP's near plane to the GPU: Test Drive's hills reached
over the sky.
A patch lies within the convex hull of its control points, so run the PRIM
check on those: a patch entirely outside is skipped, and only one with points
outside the clip volume in Z goes through software transform. Missing clip
distances are now also a reason to software-clip, for PRIM too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VertexDecoderRiscV.cpp got <cfloat> through SmallDataConvert.h, and three
headers used malloc/free without <cstdlib>, which libc++ no longer brings in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Config.h is edited about 100 times a year and each edit recompiled 198
files; now 172. SmallDataConvert.h pulls in arm_neon.h and was included by
headers that don't use it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A zstd frame doesn't always declare its size, and a small one can expand to
gigabytes, so this is the limit that keeps a crafted dump from exhausting
memory. The largest section in the 1042-dump test set is 82 MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follows up the command range validation:
- Don't require anything of the data commands' sizes or alignment. Version 2
dumps have empty VERTICES and CLUT commands, and old recorders didn't align
anything. Registers() drops a trailing partial word instead.
- Split register blocks bigger than the list buffer, which they overran.
- Clamp MEMCPYDATA and MEMSET to valid memory. Only their start was checked.
- Check the compressed sizes against the file, and the decompressed sizes
against what the frame declares, before allocating. Some version 5 dumps
leave the size out of the zstd frame; those are checked after decompressing.
- Don't let a large FRAMEBUF size wrap around in MarkNeededTargets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Crafted ppdmp command ranges can extend past the decompressed push
buffer. Frame buffer playback can then copy adjacent host heap data
into emulated VRAM, exposing process memory through rendered output.
Validate each command's type, range, and minimum payload size before
constructing the replay executor.
D3D11DrawContext takes its base context from the deviceContext
argument, and the blend state is bound through the base context, which
accepts an ID3D11BlendState1 as is. ID3D11DeviceContext1 is optional:
RetroArch's threaded video hands v1 cores a deferred-context proxy that
answers ID3D11DeviceContext only, and the core now starts on it.
- Fixes#21391
Makes the hardware backends tessellate Bezier patches and splines with the
GE-exact evaluation the software renderer uses. Fixes the z-fighting glow
under the floor grates in Coded Arms.
AGENTS.md: Put "- Fixes #N" first in commit messages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Depth readback and the GLES stencil readback converted whatever their
buffer last held into PSP memory when the copy failed. The generic stencil
readback asked for depth, which asserts on Vulkan and D3D11. And
ReadFramebufferToMemory clamped the width to the buffer but not the height,
which a block transfer can exceed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The crosshair turns red, and lock-on works, when stencil bit 3 is set at
the centre of the frame, which the game tests by reading the pixel from
VRAM with the CPU (#17936). Hook that and read back the stencil of that
row into the alpha bytes in RAM. The alpha the hardware backends keep
doesn't follow stencil everywhere, so reading color isn't enough.
Adds PerformReadbackStencilToMemory for this (8888 only for now).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Silent Hill: Origins shows each frame one late: the first draw of a frame
copies the previous one into its 565 display buffer. A dump of one frame
then displays a copy of the frame before it, taken from memory, and none
of what it draws. When the display buffer's last write came before most of
the frame's drawing, the recorder now records the next frame too, whose
copy puts the recorded frame on the display.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a hardware renderer, a framebuffer only exists on the GPU, so a dump
copied whatever stale data RAM held when a game sampled a buffer it drew
in an earlier frame. Silent Hill: Origins shows the previous frame by
copying it into its 565 display buffer at the start of the next one, so
its dumps displayed old scratch data. The recorder now reads such buffers
back once before copying them out (not ones drawn during the recording,
which replay draws again).
It asks for the copy without creating a framebuffer: with
BlockTransferAllowCreateFB (which Silent Hill has) a plain readback makes
a new framebuffer at the destination and never writes RAM.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Buzz! Master Quiz converts its question videos (sceMpegBaseCscAvcRange)
straight into the middle of the frame it has drawn, at 84,69. The
framebuffer upload only matched writes starting at a framebuffer's
address, so the hardware backends never showed the video. Pass the
written width along and draw just that rectangle into the framebuffer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The app now always uses the Metal view controller with Vulkan (MoltenVK),
for both the App Store and sideload builds. The GL view controller is gone,
and Common/GPU/OpenGL (except GLFeatures, used by the shader generators) and
GPU/GLES are no longer compiled for it. PPSSPP_API_ANY_GL is now off for
all of iOS except the libretro core, which still uses GLES.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A memcpy of part of one row into a framebuffer was drawn at the buffer's full
width, pulling in whatever followed the source. Everybody's Golf copies its
loading screen spinner back to the screen 64 pixels at a time, so its sprite's
next rows were drawn alongside, as a row of discs across the bottom.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After more than three partial readbacks of one buffer in a frame,
ReadFramebufferToMemory read the whole buffer instead for the rest of the
session, writing rows the game never asked for.
Everybody's Golf 2 reads back four portraits' color and depth per frame, which
turned it on. The whole 480x273 depth buffer then got written to VRAM, and its
last row is where the game keeps its texture heap. A texture header was
overwritten: the portrait showed the scene behind it, and the next time the
portraits were built the game crashed writing through a null pointer.
It was added for speed in Grand Knights History (c42d04829f), which copies many
slivers of a framebuffer, so that game may now do more readbacks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The game builds each 64x64 portrait by memcpying a rect of the framebuffer one
partial row at a time, then setting each texel's alpha bit from the depth
buffer, which it reads directly from VRAM. Neither got read back: the row
copies don't line up with a framebuffer, and depth is never downloaded.
Hook the copy function to read back both, whole rows at a time. That needed two
fixes to requested depth copies: they were dropped when the depth buffer lies
above all color buffers, and only an exact full-buffer size matched.
The portrait texture now matches the software renderer, though it still
doesn't show up on screen with Vulkan.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>