Only the start of the vertex range was checked. Nothing is mapped after
the end of RAM or of the VRAM mirrors, so a draw starting near either end
faulted on the first read past it: in TestBoundingBoxFast, the vertex
decoder or the software renderer, depending on the path. 3000 triangles
at 0x047FFF00, or a 4x4 bezier patch at 0x047FFF80, crashed both Vulkan
and the software renderer. Prims, beziers and splines now check the
whole range on both, including in the fast loop and when merging draws.
Indexed draws still only check the index range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A HASH_RECHECK forced a rehash even of the RELIABLE atlas, which hashed
its fake address as if it were in emulated memory: a memory exception
in the middle of the game's next system dialog update. Loading a state
saved after one had drawn text was enough to hit it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The MIPSTracer page's "log file" is the trace file, and some strings had odd
spacing, grammar or capitalization. The auto load savestate toast now uses the
setting's key instead of a duplicate one, and the English fallbacks in code
match en_US.ini.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Metal: Load the shader cache again in DeviceRestore, like Vulkan. DeviceLost
saves it and throws everything away, so the next save (the next app switch,
or quitting) overwrote it with only what had been drawn since.
- Software renderer: Bring the post-shader back after a device loss.
DestroyDeviceObjects asks for it with restorePostShader_, but
CreateDeviceObjects cleared the flag again before BeginFrame could act on
it. The hardware backends recover through renderResized_.
- Forget pointers into the old device's objects in DeviceLost, rather than
relying on BeginFrame to come before the next flush: Vulkan's last pipeline
and bound views, Metal's UBO allocations, the GLES last linked shader and
bound texture.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The iOS simulator reports an Apple GPU but doesn't have the packed 16-bit
formats, so creating one tripped Metal's validation and aborted the app.
thin3d now maps them and only reports them as supported on an Apple GPU outside
the simulator, and GPU_Metal asks GetDataFormatSupport like the Vulkan backend
does. Without them, textures decode to 8888.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An output screenshot reads back the device's own screen, which only has
the touch controls on it then.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The design doc said Cardboard VR turns off on a Game output, but only the
config was checked, so with it on the TV got the split screen too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Post-shader framebuffers (for shaders at output resolution, and those
that keep the previous frame) are sized for the output they were built
for. Moving the game to a TV or back, or the TV changing size, left them
sized for the other one. SetOutputOverride now asks for a rebuild when
the output changes.
Also corrects the design doc: "auto" render resolution follows the
device's screen, not the TV.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ScreenManager keeps a list of secondary outputs, each with its own
swapchain, recreated after a lost device. Platform code adds, resizes and
removes them from any thread through Native_AddOutput() and friends, which
take effect at the start of the next frame.
Each frame, every output is drawn by the topmost screen that claims it
(Screen::renderOutput). EmuScreen claims Game outputs: it presents the
game there, laid out for that output, and leaves it out of the backbuffer,
where the touch controls and the menus stay. Not in non-buffered mode,
which draws straight into the backbuffer.
--game-window opens such an output on Mac, with Metal. It's a smoke test
for multiple swapchains, and a stand-in for an AirPlay display.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The backbuffer is now a Swapchain, from GetPrimarySwapchain(), and
callers bind, measure and read it back through that instead of nullptr.
nullptr still means it too. Backends with one swapchain map the primary
to their old backbuffer path.
On Metal, CreateSwapchain() adds more, each with its own layer, depth
buffer and drawable. Passes merge per swapchain, and Present() presents
every drawable acquired that frame. The backbuffer is just the first
of them. Nothing creates a second one yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
OutputParams describes the surface the game is presented to.
PrimaryOutputParams() builds it from g_display and the system's safe
area insets, as before. SetOutputOverride() lets the game go to another
output, like a TV, which will have its own size, rotation and insets.
No change for the primary output: frametests on Metal and Vulkan match
before and after.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Apply the display layout's scale and offset to the overlay itself instead
of always centering it, so moving/resizing the display in Display Layout
moves the overlay with the game inside it, and the portrait default
(screen high up) is respected.
- Rescanning overlays (done whenever the display layout screen recreates its
views, e.g. after closing the picker popup) no longer unloads the active
overlay unless its definition changed.
- Re-picking the current overlay no longer posts GPU config change
notifications, which caused an audio glitch with a game running behind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBg32EFPsTXjAFu7dnTcz
Draws a user image around or behind the PSP screen, to fill the black bars
on displays whose aspect ratio doesn't match the PSP's (#20325).
- Overlays are PNGs in PSP/OVERLAYS. A plain PNG there just works: it's
drawn in front of the game, and its transparent hole is found by walking
from the image center to each edge until a nearly opaque pixel. Existing
RetroArch overlay images can be dropped in as-is.
- A subfolder with an overlay.ini can define overlays with a Name, Layer
(Foreground/Background) and an explicit Hole=x,y,w,h.
- "Load overlay image..." in the display layout screen imports any PNG
(including Android content URIs) into the overlays folder and selects it.
- The overlay is fit to the screen (aspect preserved, centered). The hole is
mapped with the same scale, and the game is fit into it with the normal
stretch / integer scale / aspect ratio options. Offset and scale don't
apply while an overlay with a hole is active. This goes through
CalculateDisplayOutputRect, so touch mapping and post shaders agree.
- The selection is a per-orientation, per-game DisplayLayout setting.
- Disabled in VR, Cardboard, and with skip buffer effects (except software
rendering), since the presentation path doesn't run there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBg32EFPsTXjAFu7dnTcz
Video textures were clamped to a 480x272 frame on both axes, so filtering
can't pull in whatever lies next to the frame in memory. The VSH's UMD
Video player converts 480 rows, into a 256 or 512-wide texture it uses as
a ring that its strips wrap around: the rows past 272 smeared, and the
wrapped strips were stretched. The clamp now covers the rows written at
the texture, and the columns the strips written into its first row cover.
An axis the frame fills keeps the game's wrap mode. A game's 480x272
frame is clamped as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A replay looks compat.ini up by the ID in the dump's header, so PS1 games'
dumps now get the PS1 emulator's settings when replayed with them
(--dump-compat). The file name keeps the game's own ID.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The PS1 emulator draws sprites with their corners on pixel centers (x.5) and
their texture coordinates on texel centers. At 1x that covers whole pixels,
but above 1x it covers half a pixel more on the right and bottom and reads
the neighbouring texels, which shows as seams between tiles. With compat.ini's
SnapHalfPixelSprites, such sprites and their texture coordinates are moved back
half a pixel, which draws the same at 1x. Texture coordinates are also nudged
a 64th of a texel in, as a scaled sprite can sample right on a texel edge,
where interpolation can fall short of the texel the GE reads (the emulator's
video unpacking does).
Set for PS1 games (POPS00000).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Where the backend has no logic op and no framebuffer fetch, the logic op is
done in the shader against a copy of the framebuffer taken when the batch is
drawn, so draws batched together don't see each other. The PS1 emulator puts
each 24-bit video frame together by ORing passes into the same pixels, so it
lost all but the last. With compat.ini's SeparateLogicOpDraws, such draws are
flushed one at a time, and the rectangles of one draw are split, as they can
overlap too.
It costs a framebuffer copy per draw. Set for PS1 games (POPS00000).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
compat.ini's NativeResolutionFramebuffer (an address) keeps that framebuffer at
1x whatever the render resolution, for one that holds data read back texel by
texel: the PS1 emulator unpacks 24-bit video into one three bytes per pixel.
Set for PS1 games (POPS00000).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A framebuffer the game always keeps at one size, and also reads and writes
outside the area it draws: the PS1 emulator's VRAM, a 1024x512 16-bit surface
it copies between and reads back from the CPU. compat.ini's FixedFramebuffer
("address,width,height") makes it that size instead of one estimated from the
drawing, initializes it from memory even when the first draw doesn't look like
one, and uploads into it without filtering, since what goes in is data that's
read back (#22686 saw FFVI crash on bilinear uploads at 4x).
Set for PS1 games (POPS00000).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
width/height is volatile and influenced by the framebuffer size detection
of the latest draw so chck bufferHeight instead.
The old check broke the homebrew OOT port.
The framebuffer manager only switches between buffered and non-buffered
rendering on a render resize. Turning on Auto FrameSkip clears Skip Buffer
Effects without posting one, so after resuming, EmuScreen ran the buffered
path with a GPU still in non-buffered mode. A frame displayed from RAM was
then drawn straight to output with no render pass bound, crashing in
VulkanRenderManager::SetScissor. Treat a mismatch as a resize in
BeginHostFrame, whatever changed the setting.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ClipTrianglesAgainstNearPlane took limits for the transformed vertices and
the output indices but never checked them, and the caller passed constants
(one of them a typo, 65336). Each partially clipped triangle adds two
vertices, so a large draw with many triangles crossing the near plane ran
off the end of transformed_ and crashed. Pass the real limits, and drop
triangles that don't fit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A patch drawn as lines is a zigzag per quad (gpu/probe exp175), but the GE
draws a quad's right edge only in the patch's last column, since the next
quad's left edge is the same line. Edges between patches are still drawn
twice. Opaque lines can't show the difference; NFS Most Wanted's
alpha-blended spline visualizer (#22104) came out brighter wherever the
edges were drawn twice. gpu/probe exp257 counts the draws per pixel with
additive blending, for Bezier and spline patches; both are now exact, as is
the NFS frame against the PSP.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nothing is evicted until the cache estimate reaches these, and they were tuned when the estimate
counted every texture at its PSP size. Counted at real size, upscaled textures crossed 16MB scale^2
times sooner (and re-scaling an evicted one is slow), and a single 2048x2048 replacement filled the
4MB secondary cache. 128MB covers what upscaling up to about 3x effectively had before. Also remove
TEXTURE_KILL_AGE_LOWMEM, unused since the low memory mode went away.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A texture waiting for its replacement was only polled while the frame's replacement budget lasted,
so if something used the budget up early in every frame, the textures drawn after it kept their
originals until the cache was cleared (like when minimizing on Android). Poll every time, and only
let the rebuild wait for the budget, keeping TO_REPLACE set so it's retried.
Possibly related to #21586.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cache decides when to start evicting textures by their estimated memory use, which only counted
the PSP size. A 4x replacement takes 16 times that and texture packs often go higher, so with a big
pack the cache could hold far more than it meant to before evicting anything. Record the size when
the texture is built. Unscaled textures without replacements get the same estimate as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#18746
Packs are mostly made on Windows, where "portraits/x.png" in textures.ini finds Portraits/x.png. On a
case sensitive file system (Steam Deck, Linux) it didn't, while zipped packs already ignored case.
DirectoryReader gets an opt-in ignoreCase mode, used for texture packs. On the first miss it indexes
the directory by lowercased path and retries there, so packs where every name matches never pay for
it, and the walk happens on the IO thread that loads the texture.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The copies went through the thread pool, where they could wait behind other tasks like pipeline
compiles, and one core already saturates memory bandwidth.
A filename with a parent dir component left the texture in CANCEL_INIT, which the texture cache
keeps polling forever. Treat it like a missing file instead. Also remove an unused lastUsed field.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Saving level 0 marked the whole texture as saved, so the other levels were skipped. Track it per level.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#19623
With a fake mipmap change (Japanese Tactics Ogre), each PSP level is cached and hashed as its own
2D texture, but since 73c06bb776 (v1.14) the replacement and dump code used the PSP level as the
replacement level. Loading a replacement for a level-2 font sheet asked for level 2 of a one-level
replacement and hit the "Invalid miplevel" assert, and dumps were named _2, which the ini then
treats as an ignored texture. Use the created texture's level for both again, as 1.13.2 did.
Also don't force mip filtering from a mipmapped replacement when only one level was uploaded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#22104
NFS Most Wanted's music visualizer is a 32x16 spline of lines at 8x8
tessellation: 144768 indices, which software transform expands to far more
than VERTEX_BUFFER_MAX vertices, so SubmitPrim splits it and flushes between
the parts. The curve's indices sat at the start of decIndex_, where each
flush writes its generated and expanded indices, so the first flush
overwrote most of the later parts and about half the visualizer was lost.
Tessellate the indices into the upper half instead, and don't use the
predecoded output for a curve that will be split, as the flushes decode
over it.
The game also sends an empty PRIM with VADDR 0 every frame, which the
hardware backends logged as a bad vertex address. Skip zero-count PRIMs
before the address check, as the software renderer does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
skipDrawReason was saved whole, including the bit that says the host
window is minimized. A state saved while minimized (a rewind snapshot)
kept it set after loading in a normal window, and the other way round.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Which list slot and id the next sceGeListEnQueue gets wasn't saved, so
after a load it depended on the session before it, and replays and
rewinds diverged. Older states start it at 0, and also get the default
EDRAM translation and visible matrices they don't carry, rather than the
previous session's.
- A frame dump recording during a load replayed the loaded state's
commands on the registers from before it. It now starts over from the
loaded state.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
DestroyAllFBOs (on a savestate load) kept depthShiftedFor_ and
lastRenderAddress_, so a new framebuffer at the same address as the old
one skipped the depth shift for a frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The PSP accepts display strides that run past the end of VRAM, and
negative ones (setframebuf.expected has -64 and 2048), and presenting
straight from memory read all 272 rows of whatever stride was set.
Without a matching framebuffer, that's a read far outside PSP memory.
Now the whole image has to be in memory, or the display shows black, on
the hardware backends and the software renderer.
A savestate's display framebuffer with an address outside RAM and VRAM,
or an invalid format, turns the display off instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A load cleared the texture cache, so every texture the game drew next
was decoded and uploaded again. Now each one is rehashed against its
memory the next time it's used, and only decoded again if it changed. In
the three seconds after a load, Ridge Racer decodes no textures instead
of 109, Force Unleashed none instead of 43. Rendered frames are the same.
Framebuffers are still all destroyed on load, since loading a state is a
known way out of some framebuffer problems.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They get their graphics context from the frontend, so they compiled and
linked the Metal backend without being able to use it. Adds
PPSSPP_API_METAL, like PPSSPP_API_D3D11, for the code that only exists
with it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Runs the same compute shaders as the Vulkan backend, translated to MSL,
in the upload command buffer: levels decode unscaled into the push
buffer and get scaled into a view of the level. Push constants go in a
buffer of their own. Multipass shaders (NNEDI3) use scratch textures
that live for the frame.
Shaders with a constant buffer (only the smiley test shader) aren't
supported.
All the texture shaders give the same result as Vulkan's on LocoRoco
and God of War savestates.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like the Vulkan backend, with "Auto Max Quality" texture filtering give
textures up to 256x256 a full mip chain, generating the levels the game
didn't supply with a blit. Mipmapping is now controlled by the
sampler's LOD clamps alone, as on Vulkan, so the mip filter is always
set.
With max quality on, the frame dumps that change are the same 829 on
both backends, and match Vulkan's to within 41 pixels.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like the Vulkan backend, upload the decoder's 5650, 5551 and 4444
pixels as they are, in B5G6R5Unorm, BGR5A1Unorm and ABGR4Unorm with a
swizzle (Metal names packed channels from the lowest bits up, the PSP
stores red lowest), instead of expanding them to 8888. Halves the
decoding writes and memory for those textures.
It also makes Everybody's Golf's dump identical to Vulkan's, which
already sampled these at 16 bits.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pipeline states are created on compile threads, along with the shader
functions they need, which compile once on first use. Draws record a
handle to the pipeline state, and the render thread waits for it if it
gets there first, as with Vulkan, so the emu thread never waits for a
compile. Translation to MSL stays synchronous, as recording needs the
texture masks it produces.
Each game's shader IDs and pipeline keys are saved to a
<discID>.mtlshadercache, and recreated while the game boots. macOS's own
Metal cache then makes them quick: Wipeout Pure's 36 pipelines took
25 ms, or 260 ms with an empty Metal cache, instead of stalls during
play.
Cold Wipeout Pure, 5 emulated seconds from a savestate: 3.0 s before,
1.6 s with the background compiles alone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On Apple GPUs, the fragment shader reads the framebuffer directly
([[color(0)]]) instead of blending against a copy of it. The Vulkan GLSL
generator path for this was already there apart from the input
declaration: SPIRV-Cross turns the subpassInput into framebuffer fetch.
The Vulkan backend never sets the fetch bit.
Reading the framebuffer as it is also sees earlier triangles of the same
draw, like the PSP does, where the copy only had what was there before
the draw. Of the dumps in ppsspp-framedumps whose output changes, all
ten with a PSP capture got closer to it, Tenchu's from about 10000
differing pixels to 900. Ridge Racer runs about 6% faster.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>