ResizeFramebufFBO now asks the framebuffer whether it has a depth/stencil
buffer instead of guessing from the z address and stride.
Vulkan CopyFramebuffer gets the debug asserts and bail-out BlitFramebuffer
already had, and GL copies and blits get the same, so a copy out of a
framebuffer without depth/stencil asserts where it's queued instead of in
the queue runner.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A color framebuffer made by CreateRAMFramebuffer has no depth/stencil
buffer. When a game later draws to it with depth, it's recreated through
ResizeFramebufFBO, and the stencil copy added for Need for Speed Shift
copied from the missing buffer, asserting in VulkanQueueRunner::PerformCopy.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#21739
With the DarkStalkers hack, SoftGPU skips the game's stretch blit and
stretches the source image to the screen itself. Libretro always presents
software rendering at 480x272, so that stretch happened at 1x and the
frontend then scaled up a blurry image.
Now libretro gets the 384x224 source as is, with the aspect the game would
have stretched it to, and switches back to the full frame when the game
stops using the stretch. GL and D3D11 read the frame from a corner of their
larger framebuffer. RetroArch's Vulkan driver samples the whole image the
core hands it, so the Vulkan presentation images are resized to the frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#21636
The Win32 window drew with a fixed 12px font and hardcoded pixel offsets, so at
high DPI the grid ran under the (dialog-unit sized) tab bar. It now uses the
DPI-scaled debugger font, derives the layout from its metrics and the tab bar's
position, and sizes the window to fit. The HalfFloat, Bytes, Shorts and Ints
tabs, which used to show floats, now work.
The ImDebugger VFPU window, which was a TODO, shows the same grid and modes,
with changed values highlighted.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
While loading, the game presents every frame but also pushes its
"next draw" time 35-50ms ahead per frame, so by the end of loading
it's about 7 seconds in the future. If loading finishes while the main
thread is in the middle of a present, that present resets the time and
nothing goes wrong. If it finishes anywhere else, the game stops
presenting for those 7 seconds but keeps drawing every frame into the
same 128KB display list, which overruns into the game's own globals
within a second and crashes.
Which way it goes depends on small timing differences.
A hook on the draw function does what the winning side of the race
does: once loading has completed, it pulls the next draw time back if
it's more than a second ahead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Syphon Filter reads its depth buffer back on the CPU for the light flares as soon as a list has
ended, without a DrawSync, and saw it before the worker had drawn the walls, so flares behind them
flickered. Now returning to the CPU after an END waits for the batches already submitted. Returning
at a stall doesn't wait, as Wipeout does that about 300 times a frame.
Wipeout on a Galaxy S8: 11% less wall time than before the worker (16% without the wait).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MoltenVK serializes the MSL compiles of pipelines that share a
VkPipelineCache, and we create every pipeline with the same one, so our
compile threads mostly waited on each other. On MoltenVK the in-memory
cache only saves the SPIR-V to MSL conversion, under a millisecond.
Wipeout Pure from a savestate on an M1, with no shader caches (Metal's
own cleared too): rendering blocked on pipelines for 1.2s instead of
2.9s, longest single stall 0.3s instead of 1s.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Turns off loading and saving the per-game shader cache for one run, so a
run can time shader and pipeline compiles like a first boot.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add https support to FreeBSD builds (which also unbreaks builds where curl is installed and detected by find_package). Also, explicitly include <clocale> to unbreak with newer clang
The hardware backends discard antialiased lines drawn with REPLACE (the
heuristic from #6483 and #12588) by returning from Execute_Prim before the
vertex or index pointer moves past them, and without taking their time. A
game that draws on from where they end then reads the wrong vertices. Skip
them like a culled draw instead, which does both, as the software renderer
and the null GPU do.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GPU_Null derives straight from GPUCommon and has no thin3d context, framebuffer
manager, texture cache or shaders. It runs display lists with the flow control,
bounding boxes, matrix loads (the hardware backends'), immediate vertices and
block transfers of the others, moves the vertex and index pointers past each
draw and curve, and charges the per-vertex and fill cost the software renderer
does. A DrawEngineCommon that queues nothing provides the vertex decoders.
It's for measuring the CPU emulation. From savestates, God of War, Tekken 6,
Wipeout Pure and LocoRoco flip the same frames in 10 emulated seconds as with
Vulkan and software, in 44-77% fewer instructions than Vulkan.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The IR frontend compiles on past a conditional branch that isn't taken,
so the IR cores don't check downcount there, while the interpreter
ended its block at every branch. Events then landed on different
instructions, and a game's trace left the interpreter's within a few
hundred syscalls.
The interpreter now applies the same rule at run time: not likely, the
operands not decided by their registers, no branch in the delay slot,
no prefix pending after the delay slot, and the same instruction and
branch limits, counted the same way. It also goes on past a beql/bnel
over a break, which the IR frontend drops. The limits move to IRInst.h
so both use them.
With it, the interpreter, IR interpreter and ARM64 JIT produce the same
trace of syscalls and thread switches over 5 emulated seconds of God of
War, Tekken 6 and Daxter.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cores charged cycles differently and checked downcount at different
places, so interrupts and thread switches landed on different
instructions, and a game could take a different path on each core.
- IR: j, jal, bc1t and bc1f now charge their delay slot.
- IR: a syscall sees every earlier instruction charged, including a
branch it's the delay slot of, and is charged once it returns. It used
to charge itself first, or, in a delay slot, never.
- Interpreter: a skipped likely delay slot costs what the instruction
would, as in the IR cores, instead of 1.
- Interpreter: both run loops go on to the end of what the IR cores run
as a block before checking downcount, and charge each instruction as
it runs.
The CPUEquivalence fuzzer now compares the cycles up to its terminating
syscall. With continueBranches off, the three cores now produce the same
trace of syscalls and thread switches, ticks included, over several
emulated seconds of commercial games. With it on, the IR cores leave the
interpreter within a few hundred syscalls, as it moves block ends.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The steps skipped bones with a zero weight. With weights that vary from vertex to vertex that
branch mispredicts a lot, and it saves only a few multiplies. Now every weight is applied, with
fused multiply-adds, as the vertex JIT does. Without the vertex JIT (as on iOS): Tekken 4.5% fewer
cycles, God Eater 2.6%, God of War 1.7%.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
vfpu_multi_dot ran the exact dot four times, each summing across the lanes. Now each lane works
out one output, a term at a time, the same steps as vfpu_dot_neon, so nothing crosses lanes until
the rounding at the end. Inf and NaN still take the old way. The IR interpreter (no JIT, as on
iOS) runs every multi-dot through it: GTA 3% fewer cycles, Tekken 2%.
The VFPUDot test checks it against the reference too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each block was a call into IRInterpret from RunBlocks: register save and restore, the first
dispatch through the shared switch, then the shadow lookup in RunBlocks. Now the exits look up the
next block themselves and keep dispatching while downcount lasts, the same check RunBlocks makes.
Anything else (a hook, an uncompiled block, an invalid address, downcount gone) still returns.
Without the vertex JIT, as on iOS: 4.5-10% fewer instructions and 1-4% fewer cycles in the games
measured.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
EmuScreen only draws the crash info when an exception was recorded, and
showed just the Resume/Reset/Back buttons otherwise. Several ways of
stopping the core with CORE_RUNTIME_ERROR recorded nothing:
- A bad memory access in JIT code that MemFault can't skip, either
because it can't decode the host instruction or because ignoring is
only configured through the exception action, went to the crash
handler, while Core_MemoryException returned early under the default
Ignore action. A halting access is now always recorded.
- HLEReturnFromMipsCall on a corrupt stack, and a failed LoadExec, set
the state directly. They now go through Core_HLEError, which records a
new HLE exception type with a message that the crash screen shows.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The depth draws were queued and rasterized on the emu thread at each render target change, in
Wipeout about 40 small batches a frame, and around 40% of that thread's time. Now a render target
change hands the batch to a worker thread and carries on. Waiting for it is left to whatever reads
the depth buffers: DrawSync and ListSync, block transfers, framebuffer copies, showing a frame,
savestates and shutdown, and the texture cache when the texture or CLUT it reads overlaps a depth
buffer still being written.
Batches grow as needed instead of being preallocated at full size. A full batch is now handed over
and a new one started, where the draw used to be dropped.
Wipeout headless, output unchanged: 16% less wall time on a Galaxy S8 (8.2 s to 6.9 s for 10
emulated seconds). On an M1, where headless waits on the GPU, the emu thread uses 38% less CPU time.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
saveIndexed = true in [options] saves the CLUT4/CLUT8 textures listed in
[indexed] as palette PNGs, saveIndexed = all saves every one of them.
The file is named <hash>_indexed.png, with the CLUT hash zeroed so one
file serves every palette of the texture. The other textures are dumped
as before.
The indices are decoded from PSP memory and the palette comes from the
current CLUT, so the dump looks like the game's texture. Add the file to
[hashes] and [indexed] to use it as a replacement.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The download (a DRM BB install package) is installed by the game's boot program only when it's
started with -i, which nothing does any more. PPSSPP now installs it itself when the game boots, the
way psheet does. Unpacking the package needs only known keys: a fixed one from psheet, and the
secure install ID, which comes out of the package's MAC.
The module inside is a type 3 PRX whose first step is encrypted for KIRK command 2, with a key
that isn't known. Command 2's result is the same on every PSP, though, so it is now stored for the
EU and US modules (taken on a PSP through sceMesgIns_driver_D062B635, the call the install makes,
and cross-checked against an install). The type 3 decrypt uses it in place of that step, so the
installed module needs no Fuse ID.
--re-decrypt also takes such a package now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both now list their routines (GetFixedCodeLabels), so the disassembly
names jump targets and --jit-profile charges routine samples to the
calling block. The profiler samples them on Linux, and its report
disassembles their host code per instruction (RISC-V's in 2- and 4-byte
steps).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With LSX, the Vec2 pack and unpack ops went to the interpreter, as they
addressed the lanes of a pair one by one. They're now an interleave or a
pick of halfwords in the pair's register.
That makes pairs, so mapping a quad whose halves are mapped pairs (two
vs2i.s feeding a vi2f.q) joins them with VILVL_D rather than storing both
and loading the quad back, like the ARM64 JIT's ZIP1 (3d20d51c70).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ops from 108e66f2d1, so both now get moreVec4Ops. With LSX they're
vector code: vfpu_minmax_key() of each lane picked by VBITSEL, a compare
ANDed with 1.0, and VFFINT with a scale. RISC-V and LoongArch without LSX
do them lane by lane, vslt and vsge as a compare into a GPR rather than
through fpcond.
Vec4VfpuMultiDot stays ARM64-only, so the frontend still doesn't emit it
for these.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like the ARM64 JIT (e33f38afa6), each jr and jalr does the dispatcher's
lookup itself, for an indirect branch of its own, and goes to the
dispatcher when out of downcount or with no block yet.
A bad target now faults in the block rather than at dispatchFetch, so the
fault handler takes any fault in the block shadow as a bad jump, with the
masked target from the address. That covers ARM64's too, which was handled
as a bad memory access.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>