Commit Graph
5847 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 f02936575d SoftGPU: Multiply small lane values with 16-bit multiplies
SSE2 has no 32-bit lane multiply, so Vec4S32::Mul is emulated with two
pmuludq and shuffles, and the bilinear sampler spent a tenth of its time
there. The texel row offsets, the texture functions, fog and blending
only multiply values that fit 16 bits, which Mul16 does in one pmaddwd.
The bilinear lerp of two channels packed as 16-bit halves uses the new
MulHalves (pmullw), which multiplies both at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:44:48 +02:00
Henrik RydgårdandClaude Opus 5.5 ce47944216 SoftGPU: Gather a span's UV plane values in registers
GEUVSpan stored the four 64-bit plane values, and the reciprocal table's
entries, to arrays one by one and loaded them as vectors, a store
forwarding stall each time, which took most of its time on x86.
CrossSIMD gets LoadS64Low (the low halves of four 64-bit values) and
LoadPairs (four pairs of 32-bit values split into two vectors) to do it
without memory. Wipeout runs about 2% faster.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:44:48 +02:00
Henrik RydgårdandClaude Opus 5.5 1752a2e53b SoftGPU: Transform a vertex's screen position in vector lanes, without stalls
ReadVertex took the GE's viewport arithmetic one coordinate at a time,
with a reciprocal of w for each, and stored the position, the UVs and
the whole vertex in pieces that were then reloaded with wider loads,
each a store forwarding stall. x, y and z now go through one GERecip and
the four-lane multiply and add (the scalar path stays for non-finite
input and zero or denormal w), the position is loaded as a vector, and
the vertex is written straight into its destination.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:44:48 +02:00
Henrik RydgårdandClaude Opus 5.5 1fe8071875 Vulkan: Apply the texture LOD bias in the shader where samplers ignore it
MoltenVK doesn't support a sampler mipLodBias (the portability subset's
samplerMipLodBias is false), so the PSP's LOD bias did nothing on Mac and
iOS, and biased textures came out a mip level blurrier than on OpenGL and
the PSP: MLB's signs and crowd, Burnout Legends, MediEvil, Ridge Racer 2.
Query the portability subset, and where the bias is missing, pass the auto
mode bias to texture() from a uniform instead.

VK_ENABLE_BETA_EXTENSIONS, which declares the portability subset, is now
defined for App Store iOS builds too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:04:23 +02:00
Henrik Rydgård 0ac523c4b5 Merge pull request #22475 from hrydgard/cars-brightness
Tantalus games: Fix brightness, fix vertical stripes
2026-10-09 19:22:00 +02:00
Henrik Rydgård 324e6c3954 Merge pull request #22476 from hrydgard/community-security-fixes
Two security fixes by acts-1631, and more found by Claude
2026-10-09 18:55:57 +02:00
Henrik RydgårdandClaude Opus 5.5 e705d62f0b GL: Only set GL_DEPTH_STENCIL_TEXTURE_MODE where stencil texturing exists
It needs GL 4.3 or GLES 3.1. On Apple's GL 4.1 every depth texture bind (Cars
Race-O-Rama's depal from depth, for one) raised GL_INVALID_ENUM, which then
showed up at whatever checked glGetError next. Without the mode a
depth-stencil texture samples depth, so nothing else changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:11:59 +02:00
Henrik RydgårdandClaude Opus 5.5 4be43ce23a GL: Forget the cached framebuffer bindings after deleting framebuffers
GLRFramebuffer's destructor binds the default framebuffer, while the queue
runner's cache still had the previous one as bound. A following readback from
that framebuffer skipped the bind and read the default framebuffer instead.
Cars Race-O-Rama and MX vs ATV Reflex recreate framebuffers every few frames,
so their luminance readback came back as zeros on about half the frames and
their brightness adaptation went wrong on GL. Now GL reads the same values as
Vulkan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:22:53 +02:00
Henrik RydgårdandClaude Opus 5.5 4202368b1e Vulkan: Look up multisampled framebuffers through a CLUT by their first sample
With MSAA, a framebuffer textured as CLUT indices was read resolved, so the
indices along edges were averages of the samples and looked up wrong colors.
Dragon Ball Z: Tenkaichi Tag Team draws its outlines from a CLUT32 lookup of
its framebuffer, and they came out noisy and miscolored. Copy each pixel's
first sample with texelFetch into a temporary buffer and look up that.

This needs the multisampled color images to be sampled, and transitioned
back to attachment layout before rendering to them again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:49:15 +02:00
Henrik RydgårdandClaude Opus 5.5 6e0d6f4357 Savestates: Terminate fixed-size name strings on load
Add DoCString() for standalone fixed-size strings, and
TerminateLoadedCString() for names inside structs that are loaded whole.
These all get used as C strings (GetName(), logs, the debugger), and are
always terminated when created. Module names are copied from the ELF with
strncpy, so terminate those on creation too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 15:32:06 +02:00
Henrik Rydgård 831acb099e Allow specifying a cap on the number of threads used in ParallelLoop 2026-10-09 14:33:58 +02:00
Henrik Rydgård bdf05ae874 Merge pull request #22464 from hrydgard/softgpu-spans
SoftGPU: Render in 4x1 spans instead of 2x2 quads, like the PSP. Optimize span functions with templating
2026-10-09 11:38:52 +02:00
Henrik RydgårdandClaude Opus 5.5 d9b36b7e46 CrossSIMD: Keep the C++ fallback's integer ops defined, like the SIMD ones
The fallback (which RISC-V uses) did its integer arithmetic in plain int,
where signed overflow and left shifts of negative values are undefined,
and converted out of range floats with a cast, which is undefined too:
RISC-V gives INT_MAX for NaN where SSE gives INT_MIN and NEON 0. With GCC
on RISC-V that broke GEUVSpanCore in the GEMath unit test. Now the
arithmetic wraps through uint32_t, like the SIMD instructions, and an out
of range or NaN conversion gives INT_MIN, like SSE.

Also skip GERowSum's zero terms before computing their shift, as their
exponent can be INT_MIN. Found with -fsanitize=undefined on the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:12:48 +02:00
Henrik Rydgård bd8f37bf0f Merge pull request #22465 from hrydgard/hw-ge-blends
HW backends: Improve accuracy of blending
2026-10-09 11:05:28 +02:00
Henrik RydgårdandClaude Opus 5.5 770dc0fe43 SoftGPU: Early depth test of staged spans in vector lanes
CheckDepthTestPassed4 only has an SSE path, so on ARM it compared lane by
lane. Adds Vec4S32::LoadU16 for the depth values.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 10:34:39 +02:00
Henrik RydgårdandClaude Opus 5.5 d24d420057 SoftGPU: Sample a span's four pixels in vector lanes
When the four pixels share their mip level (always, for a triangle's
span), their texel coordinates, clamping or wrapping and texel offsets
are computed in vector lanes, and only the texel and palette reads go
one by one. Adds Vec4S32::Min.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 10:34:39 +02:00
Henrik RydgårdandClaude Opus 5.5 d0baab5614 Compute FIXA/FIXB blends like the GE
The GE's blend terms are floor((2c+1)(2f+1)/1024), which truncates, while the
hardware multiplies by f/255 and rounds, so every pass of a fixed color blend
chain came out up to a level brighter. God Eater Burst's bloom is many such
passes and ended up far too bright.

For additive FIXA/FIXB blends that aren't just 0 and 0xFFFFFF, the shader
now computes the src term exactly and lowers it by 0.5 - (2B+1)/1024, and the
blend constant is the GE's dst factor (2B+1)/512, so the hardware's rounding
of the sum lands on the GE's floor (except where the src term would go below
zero). Blend constants are passed as floats to make that possible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:09:36 +02:00
Henrik RydgårdandClaude Opus 5.5 49674c3f25 GEMath: Add GEDot3 and GENormalize4 on Vec4F32
GEDot3 forms one term per lane and sums across the lanes, about 1.5-2x
as fast as the scalar GEDot. Adds Vec4S32::GetLane.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 09:07:12 +02:00
Henrik RydgårdandClaude Opus 5.5 a9d8b6d3c5 GEMath: GERowSum4 takes its left operands as one vector
They're truncated to float24 with one AND, and their exponents, fractions
and signs worked out for all terms at once, each term then broadcasting its
lane (new CrossSIMD Vec4S32::SplatLane). The term count becomes a template
parameter so the lanes are constants. About 75-83 M transforms/s on an M1,
from 68; results unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 09:07:12 +02:00
Henrik RydgårdandClaude Opus 5.5 c4ce2e3635 SoftGPU: Use GERowSum4 for the transform's row sums
The four clip space rows of a vertex, the texture matrix's three, the world
normal's three, the vector to a light, and each row of the combined matrix
now take one GERowSum4 call. Same results (pspautotests, frametests and
the probe regression set unchanged).

GERowSum4 scales its result by adding to the exponent instead of
multiplying, and CrossSIMD gets a Vec4S32::Max for the largest lsb. About
68 M transforms/s against 26 for four scalar GERowSums on an M1 (a pure
NEON version with per-lane shifts did about 79); replaying the frametest
dumps gets only about 1% faster, up to 11% on Taiko no Tatsujin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 02:57:09 +02:00
Henrik Rydgård c7a2cb6f63 Merge pull request #22458 from hrydgard/android-small-fixes
Android: Small fixes from a Claude code review
2026-10-08 21:17:00 +02:00
Henrik Rydgård f0e0368a77 Merge pull request #22456 from hrydgard/spline-simd
SIMD-optimize splines and beziers
2026-10-08 20:27:34 +02:00
Henrik RydgårdandClaude Fable 5.1 ce19265949 Don't save log levels set from the command line
--loglevel, -d and -v are applied to the channels directly, and the next
config save wrote them to [Log], permanently replacing the user's levels.
The other command line options avoid this with DoNotSaveSetting.

The debugger had the same problem and a flag for it. Generalize that one
(NotifyChannelsChangedByDebugger -> DoNotSaveChannels) and set it here too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik Rydgård 0e3dfcef9d Merge pull request #22454 from hrydgard/android-activity-lifecycle
More Android activity lifecycle fixes
2026-10-08 20:10:14 +02:00
Henrik RydgårdandClaude Opus 5.5 08c1a6907a Don't chmod existing parent directories in CreateFullPath
CreateDir's "repair" of existing directories ran for every component
CreateFullPath walked through, so creating any directory under the
user's home made the home directory itself group-writable (775). That
breaks zsh's compinit ("insecure directories") among other things.

Only repair the permissions of the target directory itself, when it
already exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 19:13:18 +02:00
Henrik RydgårdandClaude Opus 5.5 802b52d875 CrossSIMD: Add an arithmetic Vec4S32::Shr and Vec4S32FromBits
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 19:00:19 +02:00
Henrik RydgårdandClaude Fable 5.1 8b829b3097 Android: Share one TextRenderer class ref between TextDrawers
Each TextDrawerAndroid took a global ref to the TextRenderer class and
never released it (the release had been commented out as crashing), plus
leaked the local ref it was made from. A TextDrawer is created every time
graphics come up, so that was one of each per resume.

Look the class up once and keep it. Also initialize the method IDs, which
IsReady() read uninitialized if the class was missing, and don't walk into
a null array if renderText throws.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 18:54:46 +02:00
Henrik RydgårdandClaude Fable 5.1 2f6bb8d55e Android: Don't leak the activity on recreate, and hand it over safely
registerCallbacks took a new global ref to the activity on every onCreate
without releasing the previous one, and unregisterCallbacks only runs on a
real shutdown. Since rotation and resizing recreate the activity, each of
those leaked the old one, view hierarchy and all.

Releasing it means other threads can no longer use the raw global ref,
which had been copied into AndroidStorage and TextDrawerAndroid and was
read from the emu thread and IO threads. They now take a local ref under a
mutex (Android_GetActivity, and the equivalent inside AndroidStorage for
the shortcut activity's sake), so a call in flight keeps its activity
alive while the UI thread swaps in the new one.

The "no activity" bail is a null check on that local ref rather than a
separate HasActivity() test beforehand. Between such a test and the call
the UI thread can swap the activity out, and we'd hand null to a Java
method that wants a Context. The one remaining HasActivity() caller never
passes the activity on, so it has nothing to race with.

The ContentUri class ref and method IDs are now only looked up once,
instead of leaking a class ref per activity.

Headless and the unit test executable link Common but not app-android.cpp, so
they get a stub for this the same way they already do for getEnv and findClass.
Without it the Android CI build fails at link time, where a normal gradle build
is perfectly happy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 18:54:46 +02:00
Henrik RydgårdandClaude Fable 5.1 35c586b27b Android: Stop leaking JNI local references on native threads
Local refs are only freed automatically when a native method returns to
Java. The content URI functions mostly run on threads we attached
ourselves, where nothing ever freed the strings they created - two per
file open, one per exists check and so on. From Android 8 that's a slow
leak; before it the table has 512 slots and overflowing it aborts.

Add JNILocalFrame (PushLocalFrame/PopLocalFrame) and use it in all of
AndroidStorage.cpp, in the camera device list, and free the name string
in findClass.

Also: Android_GetFileInfo never released its GetStringUTFChars buffer, a
native leak on every call, and Android_ListContentUri called
GetArrayLength on a null array if the Java side returned none.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 18:54:46 +02:00
Henrik RydgårdandClaude Opus 5.5 84b674f4d8 CrossSIMD: Add MulAdd and Vec4S32FromF32Round
MulAdd is fused on ARM64 and LoongArch, which always have it. Rounding to
nearest is a single instruction on every ISA except ARM32.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:12:10 +02:00
Henrik RydgårdandClaude Opus 5.5 c23cd3c64f CrossSIMD: Add RecipSqrt, Select and Vec4F32FromBits
RecipSqrt is exact (sqrt and divide) except on ARM32, which has no vector
divide and refines vrsqrte with two Newton steps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:11:07 +02:00
Henrik RydgårdandClaude Fable 5.1 7f751c7ba1 Vulkan: Don't recreate the swapchain over a pre-transform we chose ourselves
InitSwapchain deliberately refuses the 270 degree pre-transform. The driver
then reports VK_SUBOPTIMAL_KHR on every present, which we counted towards
recreating the swapchain - and the new one got the same treatment, so in
reverse landscape we rebuilt it every single frame.

Remember that the mismatch is ours and stop treating SUBOPTIMAL as a reason
to recreate when it is. Any other cause still counts.

Split out of the Android rotation work, where it was found: it's a
platform-independent Vulkan fix and stands on its own.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 17:15:09 +02:00
Henrik RydgårdandClaude Opus 5 812d8f4c09 Android GL: Delete the JavaGL path, leaving only native EGL
The EGL path from the previous commit is now the only one, so GLSurfaceView
and everything that existed to serve it are gone:

- NativeRenderer.java and NativeGLSurfaceView.java, deleted.
- The javaGL branches in PpssppActivity. GL and Vulkan now share one
  lifecycle: a plain SurfaceView, startRenderLoopThread() when the surface
  arrives, joinRenderLoopThread() when it goes away.
- The "androidJavaGL" queryConfig, and the AndroidNativeEGL setting and its
  developer-tools checkbox.
- displayInit/displayRender, and with them the "Second time, joining the
  emuthread and starting it up again" dance - restarting the emu thread from
  inside a JNI callback because a lost context was only observable as a
  second onSurfaceCreated.
- GraphicsContext::NotifyContextLost, which existed solely because that join
  drained the render queue on a thread with no current context. The render
  thread now joins the emu thread with the context still current, so the
  drain is real work and nothing has to be skipped.
- g_emuThread, now that nothing starts an emu thread outside the render loop.

Three things displayInit/displayRender did that nothing else did, moved
rather than dropped:

- EnterVR and the per-frame StartVRRender/UpdateVRInput/FinishVRRender hooks
  now live in RenderLoopThread's GL branch. Java's GL thread could simply
  return early and wait for the next vsync when the VR session was inactive;
  this loop drives itself, so that case sleeps 16ms instead of spinning.
  Not tested - no headset here.
- RECREATE_VIEWS after the emu thread starts, as before. Vulkan never did
  this, so it's a candidate for removal when the two paths converge.
- The thin3d error callback, which surfaces GL errors as OSD messages. That
  one moved into AndroidEGLGraphicsContext::InitSurface, next to the rest of
  the context's own setup, matching WindowsGLContext.

Tested on a nubia Z2464N (Unisoc UMS9632, Mali-G57, Android 15): cold start,
UI, booting a homebrew, backgrounding and resuming both in the menu and
in-game, and a density change, which destroys and recreates the activity the
way a rotation does. Every cycle tears the EGL context down and builds a new
one, and the renderer comes back with FBOs and textures intact. The config
ladder's first choice, RGBA8888 D24S8, is what the Java chooser landed on
here too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 15:22:25 +02:00
Henrik RydgårdandClaude Opus 5 f868976af2 Android GL: Add a native EGL path, behind a dev toggle
First step towards removing JavaGL. The point isn't EGL itself - it's who
owns the render thread. With GLSurfaceView, Java creates the thread and the
context and calls us back, so a lost context is only observable after the
fact as a second onSurfaceCreated. That's what forces displayInit to tear
down and restart the emu thread from inside a JNI callback, and to call
NotifyContextLost first because the queue is about to be drained on a thread
with no context. None of that is needed when we own EGL: the render thread
joins the emu thread itself, with the context still current, so the drain is
real work rather than something to skip.

AndroidEGLGraphicsContext subclasses OpenGLGraphicsContext (whose own
InitSurface assumes someone else already made a context current) and adds
display/config/surface/context plus a swap function. With it, GL reuses the
Vulkan lifecycle wholesale: queryConfig("androidJavaGL") returns false, so
Java builds a plain SurfaceView and starts the native render loop when a
surface arrives - including all of the surface/pause ordering fixes from the
previous branch. runVulkanRenderLoop/requestExitVulkanRenderLoop are now
runRenderLoop/requestExitRenderLoop and serve both backends; the thread they
spawn branches on NeedsSeparateEmuThread, because the two backends split the
work in opposite directions (Vulkan: this thread is the main loop and the
render manager spawns the render thread; GL: this thread *is* the render
thread and spawns the emu thread).

Deliberately a port, not an improvement:

- GLES client version still follows NativeRenderer - 3 for VR, 2 otherwise.
  Not a cap; GLES3 is detected from GL_VERSION at runtime.
- No swap-interval function. GLSurfaceView gave us no way to set one, so
  PresentMode::IMMEDIATE has always been ignored here, and honoring it now
  would be a behavior change. Worth revisiting once this path is the only one.

The config ladder does take the user's word that Mali 400 and the Xperia Play
are no longer a factor: it asks for RGBA8888/D24S8 and falls back twice,
rather than reproducing GLSurfaceView's ask-for-nothing default that the
comments in PpssppActivity.onCreate exist to explain. ANativeWindow's format
is set from EGL_NATIVE_VISUAL_ID, which is the proper fix for the EGL_BAD_MATCH
those comments describe.

Off by default, behind Developer tools / Graphics / "Use native EGL instead
of GLSurfaceView" (OpenGL backend, Android only). Needs an app restart, since
the context is chosen in NativeApp.init.

Builds for arm64-v8a and on Windows; NOT yet run on a device.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 15:22:25 +02:00
Henrik Rydgård d74f16ef51 Merge pull request #22330 from hrydgard/android-render-thread-lifecycle
Claude code review: Android render thread lifecycle fixes
2026-10-08 15:17:30 +02:00
Henrik RydgårdandClaude Opus 5.5 c2ce8d1f2e OpenGL: No hardware logic ops on Apple GPUs
Apple's OpenGL runs on Metal, which has no logic ops, and ignores glLogicOp.
Emulate them in the shader like MoltenVK does, which fixes OutRun's water
(without the BlueToAlpha compat flag) on macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:36:18 +02:00
Henrik RydgårdandClaude Opus 5.5 e30d67946a OpenGL: Don't scissor framebuffer blits
glBlitFramebuffer obeys the scissor test, which stays enabled between render
passes with the last pass's rectangle. Resizing a framebuffer after drawing to
a small one copied only that corner, so FF Type-0's battle screen turned into
the red overlay. Disable it around the blit and restore it.

Also fixes Assassin's Creed Bloodlines, Split/Second, Super Stardust's bloom
and MotorStorm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:36:18 +02:00
Henrik RydgårdandClaude Opus 5.5 a66e94c015 Vulkan: Don't merge an empty clear pass past a copy into the framebuffer
PreprocessSteps moved the clear of an empty clear pass into the next render
pass on the same framebuffer, stopping only at copies out of it. Resizing a
framebuffer clears the new one, copies the old contents in, then renders, so
the clear landed after the copy and wiped it. Stop at any copy, blit or
readback that touches the framebuffer.

Fixes black or missing layers in Asphalt Urban GT 2, FF Type-0, Street Riders,
Kurohyou, Split/Second and Super Stardust.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:36:18 +02:00
Henrik RydgårdandClaude Fable 5.1 3cb6ada66a Android GL: Don't run stale GL commands when joining the emu thread
EmuThread_Join drains the render queue on the calling thread. On Android
that is either the UI thread with no GL context (NativeApp.shutdown), or
the GL thread with a brand new context after the old one was lost
(displayInit the second time). The skip flag was only set once the emu
thread reached NativeShutdownGraphics, so everything queued before that
ran for real, against nothing or against the wrong context.

Add GraphicsContext::NotifyContextLost() and call it before the join in
both places. skipGLCalls_ is now atomic, since it's set from one thread
and read from another.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 00:09:09 +02:00
Henrik RydgårdandClaude Fable 5.1 2c68ce0001 Android: Recover from a Vulkan render thread that exited by itself
If InitSurface failed, the thread returned but stayed joinable, so the next
surface was refused as "Already running" until the next pause. Tell a live
thread from a finished one with renderLoopRunning, and reap the latter.

Also make VulkanGraphicsContext::InitSurface clean up when it fails after
creating the draw context. Callers don't call ShutdownSurface on failure,
so draw_ (with its render manager) and the surface leaked, and the next
attempt overwrote the pointer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 00:09:08 +02:00
thatsprettygood 79f83dd279 Add dbghelp to Windows target link libraries 2026-10-04 15:34:56 +00:00
Henrik RydgårdandClaude Opus 5.5 932d1e3b36 IconCache: Expire only avatars after a day, other icons after 30 days
Achievement badges and game icons don't change, so re-downloading them
daily was wasted. InsertIcon now takes a max age, and each entry saves
its expiry time. Leaderboard avatars get a day, everything else 30 days.
Bumps the cache version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 9cb8f54a4e IconCache: Expire saved icons after 24 hours
Icons were kept in icon.cache indefinitely, so one that changes on the
server (a RetroAchievements user avatar, keyed by username) never
refreshed. The insertion timestamp was process-relative, so it couldn't
be compared across sessions. Store wall-clock time, and skip entries
older than a day when loading. Bumps the cache version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 36bd2e9249 IconCache: Know an icon's size before its texture exists
GetDimensions only answered once a texture had been created, so the first
MeasureNotice for a new icon (and after the texture was released for being
unused) measured without it, and the icon was then drawn over the text.
Read the size from the PNG header when the icon is inserted or loaded.

Also remove a width adjustment in MeasureNotice that was overwritten
before use.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 2d6045fa4b IconCache: Bound failed_, back off failed uploads, fix the disk layout
- Prune expired failed_ entries, which otherwise grew for the whole
  session.
- When CreateTexture fails, wait 5 seconds before decoding and uploading
  again, instead of retrying every frame.
- Bind a newly created texture. Callers draw right after BindIconTexture,
  and got whatever was bound before for the first frame.
- Make DiskCacheEntry's padding explicit. 32-bit x86 Linux aligns double
  to 4, so its icon.cache didn't match other builds. The layout is
  unchanged elsewhere.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 8e92fef527 HTTP: Publish request completion with release/acquire ordering
HTTPRequest::completed_ was a plain bool set on the download thread, and
naett's res->complete a plain int set on its transfer thread. On ARM, the
thread polling Done() could see the flag before the result code or body
written ahead of it, and report a stale code or a partial body.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 f87179d0c1 HTTP: Keep abandoned naett requests' sinks and POST bodies alive
Join() deliberately leaks the naett objects of a request still running at
shutdown and parks its sink, so a late chunk lands somewhere valid. But
HTTPSShutdown, which runs right after CancelAll, deleted those sinks, and
the POST body naett reads in place was owned by the request being
destroyed. On Android, where NativeShutdown isn't process exit, a late
WinHTTP/JNI callback could then write to or read from freed memory.

Only free the list when it's empty (that still fixes the leak the delete
was added for), and keep the POST body in the sink.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 c5ef99ad3f IconCache: Check entry lengths before allocating when loading the cache
The key was resized before its length was checked, and the data length
wasn't checked at all, so a corrupt icon.cache could make a multi-GB
allocation and crash every launch. The cache is only saved at a clean
exit, so a bad file stayed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 6b45c4f3e2 IconCache: Drop icons that fail to decode instead of keeping them forever
Any 200 response went into the cache, and one that didn't decode (a
captive portal's HTML, or a PNG over the 256x128 limit) was marked bad,
saved to icon.cache and never downloaded again. Remove it and mark it
failed, so MarkPending allows another download after the retry delay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00
Henrik RydgårdandClaude Opus 5.5 00208d3d80 Achievements: Enable HTTPS on UWP, retry failed icon downloads
UWP compiled naett with the WinHTTP backend but never reported
SYSPROP_SUPPORTS_HTTPS, so achievement traffic and badge icons went over
plain http. When the media server redirected to https, icons failed.

A failed icon download also left the URL marked pending, so it was never
retried for the rest of the session. Mark it failed instead, and let
IconCache retry it after 30 seconds.

The set-completed badge also skipped RemoveHttpsIfNeeded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 10:26:00 -06:00