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>
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>
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.
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>
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>
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>
Loading trusted whatever was in the file, apart from a few sanity
checks, and a damaged entry stayed in the cache for good, since loading
it put it back in the pipeline map and every save wrote it out again.
A Syphon Filter: Logan's Shadow cache had pipeline keys with garbage in
useHWTransform, which MoltenVK refused to create ("Vertex attribute
texcoord(1) is missing from the vertex descriptor").
Now the files end in an XXH3 checksum of everything before it, checked
before anything is read, and a cache that fails it is deleted and
rebuilt.
Bumps both cache versions, since the files have a new layout. Old files
fail the checksum anyway.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Smaller framebuffers are mostly effects (blur, bloom), and MSAA changes
how those come out, like a higher resolution would. Syphon Filter:
Logan's Shadow grew a glow around its lit floor panels that the PSP
doesn't have. Effects in Call of Duty: Road to Victory and Assassin's
Creed: Bloodlines shifted too.
A framebuffer that grows to full size gets MSAA from then on. The
temporary buffer that shifts depth for offset rendering now takes the
samples of the framebuffer it shifts, since they can differ.
Over the frame dumps at 2x and 4x, the differences away from edges
compared to no MSAA drop by a fifth and 53 dumps get closer. The one
that moved away, Silent Hill: Origins' flashlight shadow, now matches
the PSP; without MSAA it shows a known replay artifact there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A draw with vertices beyond the near or far plane went through the software
transform unless the GPU had both clip and cull distances. When the viewport's
near and far planes both fall on min/max Z, and those are inside 0..65535,
the min/max clip planes clip exactly where the GE does, and also take away
what's beyond the far plane: no need for cull distances or the CPU.
On Mac Vulkan, Metal Gear Solid: Peace Walker's six dumps go from 15.3k px
off the PSP to 10.4k, and Call of Duty: Roads to Victory from 10.0k to 8.9k.
viewportNearPlaneMatchesOutput was never set back to false.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hardware transform does the matrices on the GPU, software transform on the CPU,
and their depths differ by a few ulps on 40-90% of pixels. That's enough to
fail an equal-depth GEQUAL test where a frame mixes the two, which happens to
draws that cross the near plane on GPUs without cull distances: DBZ Tenkaichi
Tag Team's outlines, Naruto Ultimate Ninja Impact's grass over the ground.
Both paths end in the same vertex shader code, so snapping there makes them
agree: screen Z to 1/8 (whole units break a decal in Star Wars Lethal
Alliance) and W to 16 significant bits. W is left alone without integer ops.
On the framedump set with Mac Vulkan: DBZ 3198 -> 77 px off the PSP, Naruto
3243 -> 906, Monster Hunter 792 -> 21, FF Type-0 559 -> 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With depth clip off, the software transform dropped a triangle when any vertex
was beyond the near or far plane, which is the viewport's own Z range. The GE
only drops it when a vertex's screen Z is outside 0..65535 or it's behind the
camera, as the software renderer does. Yu-Gi-Oh GX Tag Force's backdrop has a
10000..50000 viewport and vertices past it: with hardware transform off, parts
of it were missing (4192 px off the PSP, now 64 like with hardware transform).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
For where there's no vertex JIT. It skins the same way as the steps:
the skin matrix is 0 + bone * weight, kept as the columns that
Vec3ByMatrix43() and Norm3ByMatrix43() multiply and add unfused in the
same order (by lane on ARM64, like those), and only rebuilt when the
weight changes. 2.2 ns a vertex in God of War against the JIT's 1.95
and the steps' 12-18.
Without the vertex JIT, God of War's instructions drop by 6.8% per 10
emulated seconds.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The decoders clear gstate_c.vertexFullAlpha when a vertex has less than
full alpha, and a batch decodes each of its draws in turn. The God of War
and GTA decoders assigned it instead, so a draw with full alpha set it
back after an earlier one had cleared it. God of War's also read the
alpha from the normal: its struct had color and normal swapped (the
output came out right, as each went to the matching slot), so the flag
never got set for its most used format.
The vertex decoder test now decodes a vertex with full alpha after a
draw that cleared the flag and after one that didn't.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
For where there's no vertex JIT (iOS), as they're a few percent slower
than the JIT's but four to eleven times faster than the steps. The
formats carry most of the vertices of ATV Offroad Fury: Blazin' Trails,
Wipeout Pure and Killzone: Liberation, and a lot of Gods Eater Burst's.
They convert like the steps (PrescaleUV moves to the header for that),
and the vertex decoder test now also compares each format's decoder
without the JIT against the steps.
Without the vertex JIT, 10 emulated seconds on an M1, instructions:
ATV -9.9%, Wipeout -6.0%, Killzone -5.4%, Gods Eater -2.2%.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
They were only used for UV from texture coordinates. With the texture
matrix or environment map, UV isn't prescaled, which is the same as a
scale of 1 and no offset, so NoPrescale versions pass that. God of War
draws 5 million of its vertices per 10 seconds this way, which without
the vertex JIT took 9.6 ns each through the steps and now 2.4.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An [indexed] entry set to gpu uploads the indices as an R8 image, and
the CLUT8_INDEXED depal shader applies the palette. A texture whose
palette changes a lot then costs no CPU time per palette, and keeps one
texture instead of one per palette.
Once such a replacement is found, the texture is looked up without the
CLUT hash, like with the TextureCLUTInShader compat flag. Only the first
mip level is used, and the replacer's filtering overrides are skipped
since filtering indices mixes up palette entries. Without shader bitwise
ops it falls back to coloring on the CPU.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
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>