Commit Graph
2103 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 1d5e2689b5 JIT: Add --jit-profile, a sampling profiler for the CPU cores
The native IR JITs get the emulator thread's host PC sampled every 50 us (a
signal on macOS and Linux, SuspendThread on Windows), mapped to blocks, the
routines they call and host functions. The report has each hot block's MIPS,
IR and host code with the samples per host instruction. The IR interpreter's
block timing, previously behind IR_PROFILING, now runs on the same switch.
Both cost nothing when off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 11:01:52 +02:00
Henrik RydgårdandClaude Opus 5.5 0edfd93a3a Depth raster: Stop a row once it has left the triangle
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>
2026-10-15 10:14:54 +02:00
Henrik RydgårdandClaude Opus 5.5 83639a73e8 GPU: Cull-test draws four vertices at a time
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>
2026-10-15 10:14:51 +02:00
Henrik RydgårdandClaude Opus 5.5 f3c69fc5d1 Remove FakeJit and the pre-IR JitBlockCache
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>
2026-10-15 02:24:25 +02:00
Henrik RydgårdandClaude Opus 5.5 74703b031e Remove the legacy ARM32 JIT
The IR-based one replaces it. With that, every native JIT is IR-based, which the JIT viewer and
the settings' comments now assume. The ARM emitter test loses its part that drove the old FPU
register cache.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 17:32:53 +02:00
Henrik RydgårdandClaude Opus 5.5 6d105ae35d ARM32: Start an IR-based JIT
The backend follows the ARM64 one: a dispatcher, a register cache on top of the IR one, and
native exits, syscalls, breakpoints and transfers. Everything else goes to the IR interpreter
for now. Floats are mapped one lane per S register (no SIMD mapping yet), and addresses are
masked, as on every 32-bit build.

The JIT core now uses it on ARM32 in place of the old ArmJit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 17:32:53 +02:00
Henrik RydgårdandClaude Opus 5.5 66d7975e7f Remove the legacy (non-IR) x86-64 and ARM64 JITs
Their IR-based replacements stay. The JIT core now creates the IR JIT on both, so "JIT using
IR" is the same as "JIT" on every architecture (ARM32 still uses its legacy JIT for both). The
settings no longer offer it, a saved JIT_IR loads as JIT, and the JIT harness and CPUEquivalence
fuzzer drop the duplicate run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 14:39:45 +02:00
Henrik RydgårdandClaude Opus 5.5 f7db56628c ARM disassembler: Rewrite, cover everything the emitter can emit, add a test
The old disarm handled the integer instructions but only a fraction of VFP
and NEON, and got several of those wrong. The new one decodes ARMv7-A ARM
state with VFPv4 and NEON, printing UAL with upper case mnemonics.

The new ArmDisasm test runs every ARM emitter function through it, two
examples each. Tools/arm_disasm_check.py compares the test's output with
llvm-mc; they agree on all of it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 22:23:14 +02:00
Henrik RydgårdandClaude Opus 5.5 4163737bbc ARM64 disassembler: Cover everything the emitter can emit, add a test
Rewrites most of DisArm64.cpp. Previously missing: test-and-branch,
exceptions other than brk, hints and barriers, adc/sbc, conditional
compare, extr, literal loads, exclusives, unscaled and FP register
offset loads, ldpsw, SIMD structure loads/stores, scalar pairwise,
fixed-point conversions, fccmp, most two-reg misc ops, modified
immediates, smov/umov/dup from GPR, and the scalar shift forms.

Fixed: sp printed as x31, unsigned load offsets sign-extended, ldrsb and
friends shown as stores, adrp ignoring the page, double precision
1-source ops shown as single, fcmpe shown as fcmp, smulh/umulh, the
bitmask decode for 64-bit elements, and fpcr shown as fpsr. Common
aliases are used now (cset, mul, tst, cmn, lsl/asr/ubfx, sxtw...).

TestArm64Disasm runs two examples of every emitter function through it.
The expected strings were verified with Tools/arm64_disasm_check.py,
which compares all 833 instruction words against llvm-mc.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 20:59:51 +02:00
Henrik RydgårdandClaude Opus 5.5 c7d60a5cfb Headless: Vulkan and OpenGL ES on Android, and frametests.py --adb
The Android headless build only did software rendering. Vulkan takes the
offscreen path desktop headless already uses, and OpenGL ES renders into an
EGL pbuffer, so both run from adb shell with no window. Assets are found next
to the executable, like on desktop, instead of in an APK path that no longer
exists.

frametests.py --adb pushes the binary, assets and dumps to a device, runs each
dump there with Vulkan and GLES, and compares the screenshots with the host's
Vulkan render using Tools/image_compare.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:44:34 +02:00
Henrik RydgårdandClaude Opus 5.5 6a15aff789 Android.mk: Don't link the adrenotools hook libraries into ppsspp_common
They are meant to be separate shared libraries that driver.cpp loads into the
driver's namespace by name (ext/libadrenotools builds them that way, and so
does the gradle build). Linked into an executable, file_redirect_hook.c's
global fopen() takes over every fopen in the process, including the one in
libc's own startup, and hook_fopen() calls fopen() again forever. That hung
ppsspp_headless (and the unit test executable) before main().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:44:34 +02:00
Henrik RydgårdandClaude Opus 5.5 3afb17f7d4 Android: Don't leave room for system bars we hid
Before Android 11, decorFitsSystemWindows(false) sets LAYOUT_STABLE, and
the system bar insets then keep the space of hidden bars. So in
landscape, where the status bar is always hidden, the UI had an empty
band at the top (seen on a Galaxy S8 and a Pocophone F1). Count only
the bars we show, and reapply the insets after changing that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:59:06 +02:00
Henrik RydgårdandClaude Opus 5.5 6d9a54d60d Android: Don't restart the microphone on resume after it was stopped while paused
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:41:19 +02:00
Henrik RydgårdandClaude Opus 5.5 56580581e0 Android: Check for Java exceptions after content URI calls
ContentUri catches Exception, but an Error got through, and none of the
native wrappers checked. A thrown int call returned 0, which reads as
SUCCESS, and the exception stayed pending, so the next JNI call aborted.
copyDocument/moveDocument are API 24/25 while the folder browser is
offered from 21, so those threw NoSuchMethodError there - now they return
a failure so the callers fall back.

Also convert non-URI names to Java's modified UTF-8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:41:19 +02:00
Henrik RydgårdandClaude Opus 5.5 bfa0ef9d1f Android: Small crash fixes in the activity and shortcut creation
- Catch takePersistableUriPermission failing in ShortcutActivity, as
  DocumentResultProxyActivity already does. Some pickers don't grant it.
- Treat a cancelled permission request (empty results) as denied.
- Handle getCacheDir() returning null, like getFilesDir().
- Don't let the input device debug string, read from the render thread,
  throw ConcurrentModificationException into native code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:41:19 +02:00
Henrik RydgårdandClaude Opus 5.5 51a799623c Android: Fix GPS and infrared crashes on devices without them
requestLocationUpdates() throws IllegalArgumentException for a provider
that doesn't exist (WiFi-only tablets, TVs, Chromebooks), which crashed
any game opening the GPS. Register each provider separately.

Without an IR blaster, the IR service is null, and InfraredHelper was
created anyway, so a sceSircs call NPE'd. Also catch transmit() failing,
like the SecurityException on API 21/22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:41:19 +02:00
Henrik Rydgård 8d8009840e Merge pull request #22511 from hrydgard/android-inputbox-fixes
Claude code review: Android inputbox fixes
2026-10-12 13:20:25 +02:00
Henrik RydgårdandClaude Opus 5.5 7678336e28 Android/VR input: Lock the VR key state, fix device tracking and release
pspKeys and pspAxis were written from the input thread and the render
thread without a lock. Entering VR camera adjust "released" held keys by
sending PSP button codes as keycodes, which released nothing; use
ControlMapper::ReleaseAll instead.

Input devices are now matched by ID. InputDevice objects are replaced after
device changes, so reference comparison registered the same device again.
Removing a device now releases what it was holding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:56:32 +02:00
Henrik RydgårdandClaude Opus 5.5 468b83a738 Android: Fix JNI string encoding, thread safety, password in debug log
GetJavaString() returned Java's modified UTF-8, so input like emoji
reached C++ as invalid UTF-8. Commands to Java now get converted the other
way. Request IDs are made on several threads, and the permissions map was
written on the Java UI thread and read on the render thread. The frame
command debug log no longer prints params, which could hold a password.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:37:19 +02:00
Henrik RydgårdandClaude Opus 5.5 2aa3e8d6d3 Android: Always answer native requests
An input box whose show() threw, a second input box open at onDestroy, and
browse commands dropped for a destroyed activity or failing in
processCommand were never answered, leaving the native side waiting (a
game's OSK forever). The image proxy also crashed the app on a picker
result without a URI, a SecurityException or an OOM.

Input boxes now answer exactly once, instead of a success followed by an
ignored failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:37:19 +02:00
Henrik RydgårdandClaude Opus 5.5 6071983094 Android: Mask password input boxes, don't log entered text
The passwordMasking flag of System_InputBoxGetString() never reached Java,
and both sides logged the entered text, including RetroAchievements passwords.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:29:47 +02:00
Henrik RydgårdandClaude Opus 5.5 6cf4872e78 Android: Hand microphone input over on the CPU thread, like the other hosts
The Oboe input callback used to call Microphone::addAudioData directly,
writing PSP memory from the audio thread. Now it only pushes samples
into a SampleRing, and pollRecording drains it on the CPU thread.
Starting and stopping still go through Java, which asks for permission.

Every host with a microphone polls now, so sceUsbMic drops what only the
push path needed: QueueBuf's recursive mutex, restarting the mic on a
device change, and the startMic parameter nobody read. Also removes the
unused System_AudioRecordingState and Microphone::isNeedInput.

The Java microphone_command branch didn't return true, so every command
logged "processCommand failed".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 18:49:21 +02:00
Henrik Rydgård 7c780088ac Merge pull request #22488 from hrydgard/ios-cleanup
iOS: Platform layer cleanup
2026-10-10 17:18:48 +02:00
Henrik Rydgård bd3e55a104 Merge pull request #22487 from hrydgard/android-lifecycle-fixes
Android lifecycle fixes, file sharing support (unused)
2026-10-10 16:43:58 +02:00
Henrik RydgårdandClaude Opus 5.5 81f77f5156 Move Android's command line splitter to CmdLine, as SplitCommandLine
So iOS can split its restart arguments the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:28:27 +02:00
Henrik RydgårdandClaude Opus 5.5 b9a706432a Add ShareFilesAsZip, for sharing several files as one zip
Zips the given files (content:// paths included) into a folder of their own
in the app's cache and offers the zip through System_ShareFile. The receiving
app reads it whenever it likes, so it can't be deleted right away: each share
deletes the folders of earlier shares once they're an hour old.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 16:06:55 +02:00
Henrik RydgårdandClaude Opus 5.5 77a6abfe6e Add System_ShareFile, for handing a file to another app
Offers a file through the system share sheet, for things like sending a
zipped savestate to a friend. Android for now (SYSPROP_SUPPORTS_SHARE_FILE):
content:// paths from a picked folder are shared as they are, and plain paths
through a FileProvider. Either way the receiving app can read just that file.
With no MIME type given, it's guessed from the extension.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 15:59:42 +02:00
Henrik RydgårdandClaude Opus 5.5 d35cdf8fcf Android: Track the activity that owns the native side by identity, and log changes
Replaces the instance counter with a weak reference to the current activity,
and says in the log when an activity takes over, and when a replaced one skips
something because the native side isn't its anymore.

The render loop state is static now, with the activity whose surface it runs
on: there's one render thread. An activity only stops the loop if it runs on
its own surface, and one that finds another activity's loop still running
logs an error and stops it before starting its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 15:59:16 +02:00
Henrik Rydgård 6779143827 Merge pull request #22486 from hrydgard/softgpu-x86-opt
SoftGPU: Do an optimization pass on x86-64 too
2026-10-10 15:48:04 +02:00
Henrik RydgårdandClaude Opus 5.5 34da53de5a Android GL: Support PresentMode::IMMEDIATE with eglSwapInterval
Now that EGL is ours rather than GLSurfaceView's, the swap interval can be set,
so turning vsync off (and fast-forward without skipping flips) works on Android
GL too. Not in VR, where OpenXR presents, and not with a config whose minimum
swap interval is 1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 15:45:27 +02:00
Henrik RydgårdandClaude Opus 5.5 2123e7fdac Android: Tie emulation and audio to visibility, not focus
onPause used to join the render thread and stop the audio, so anything taking
focus while the game stayed on screen (a permission dialog, a share sheet, the
other app of a split screen before Android 10) tore down the whole graphics
context and silenced the game. Emulation, audio, sensors and input listeners
now follow onStart/onStop. The render loop itself already follows the surface;
onStop joins it too, for devices that keep a stopped activity's surface.
The camera stays on onPause/onResume, as it goes to the focused app.

A finishing activity lets go of the render thread in onPause, since a quick
relaunch creates the next activity before this one's onStop, and that onStop
then leaves the audio and location updates alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 15:45:27 +02:00
Henrik RydgårdandClaude Opus 5.5 87353cfd01 Android: Fix activity state lost on recreate, and a destroyed activity kept alive
- Initialize() runs for the first activity only, but set up per-activity
  fields: after any recreate, audioManager was null, so audio focus was
  never taken or given up again, and sustained performance mode was off.
- The static camera helper kept the first activity's display. It now gets
  the application context.
- An activity backed out of (or destroyed by the system) with the native
  side left running stayed referenced by it until the next one registered,
  and native code could still post commands to it. It now lets go, and a
  command arriving at a destroyed activity is dropped (an input box request
  answered as cancelled).
- An open input box is dismissed in onDestroy, instead of its window
  leaking and its request never being answered.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 15:31:24 +02:00
Henrik RydgårdandClaude Opus 5.5 a89fdb2c9a SoftGPU: Remove the pixel and sampler JITs
The C++ paths draw and sample four pixels at a time in vector lanes,
while a JIT function only does one, and with a JIT function in use the
span and quad paths were skipped. On x86 the JITs had become slower:
Wipeout took 13% more wall time and 25% more CPU with them, God of War
3% more. They were also a second implementation of every GE rule, and
ARM never had them.

The SoftwareRendererJit setting goes with them. RasterizerRegCache.h
keeps only the types the pixel and sampler functions take, as
RasterizerTypes.h.

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 834c3f00ee Android: Replace OpenSL ES audio with Oboe
Oboe uses AAudio on Android 8.1+ and falls back to OpenSL ES on older
devices. Output and microphone input both go through it. The mic now gets
resampled by Oboe from the device's rate to whatever the game asks for.

New: when a stream is disconnected (headphones unplugged, Bluetooth device
going away), it's reopened on the new default device instead of going
silent.

Oboe 1.11.0 is added as a submodule, built through ext/oboe-build for CMake
and as a module in Android.mk.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 12:13:58 +02:00
Henrik RydgårdandClaude Opus 5 a2869a47cd Android: Re-report the surface when the display density changes
Now that density is in configChanges, a density change no longer recreates
the activity - but it also doesn't resize the surface, so no surfaceChanged
follows it. The DPI is only recalculated in backbufferResize, so the UI kept
the old scale until the next real resize or a trip through the background.

Re-report the surface we already have when the density has moved. The native
side needs no change: Recalculate() already returns true when the dp size
changes, which a density change does on its own.

Also make the log line say which size changed, since the pixel size now stays
put in this case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 21:51:35 +02:00
Henrik RydgårdandClaude Fable 5.1 114512774d Android: Keep the native smallestScreenWidthDp current across config changes
It was passed once at init, and decides whether we can restrict the
orientation on Android 17+ large screens. Now that a fold or unfold no
longer recreates the activity, forward the new value from
onConfigurationChanged. Untested on a large screen device.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 21:17:49 +02:00
Henrik RydgårdandClaude Fable 5.1 be6ff0bc9b Android: Trim SizeManager down to the surface callback, rename to NativeSurfaceManager
Now that rotation doesn't recreate the activity, the class is the
SurfaceHolder.Callback plus the hardware scaling logic, and the rest had
outlived its purpose:

- The deferred-surface-while-paused workaround. Also what started the
  render thread twice on resume.
- The "bad orientation" workaround, which called recreate().
- The deprecated system UI visibility listener, which only chose between
  getMetrics and getRealMetrics, and the view size overrode that whenever
  it mattered anyway.
- The coalescing of display measurement updates, and updateDpi, which
  updateDisplayMeasurements does itself.

Before the first layout the display size now comes from getRealMetrics.
It only feeds the first-run config defaults.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 21:17:49 +02:00
Henrik RydgårdandClaude Fable 5.1 d0e45b94d9 Android: Handle rotation and window resizing without recreating the activity
Every rotation, fold or window resize destroyed the activity and with it
all of graphics, then built it all up again. Declare those config changes
handled, so they arrive as a surfaceChanged and go through the same resize
path as a desktop window resize.

That path needed three things it didn't have on Android:

- The GPU_DISPLAY_RESIZED message, left out for Android since 2020 when
  DeviceRestore covered every size change.
- A fixed (hardware scaled) surface size is now recomputed on each in-place
  surface change, since surfaceCreated no longer runs on a rotation.
- A 180 degree flip keeps the size but changes the surface transform, so
  the display rotation is passed along and a change in it is a resize too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 21:17:49 +02:00
Henrik RydgårdandClaude Opus 5 afedbab578 Android: Remove LifeCycle.java, keep the logging
It was a debug aid that asserted the activity callbacks arrive in the order
Android promises. That has served its purpose - nothing has tripped it in a long
time, and the lifecycle work this sits on top of has been exercised hard.

What it also did, though, was log every callback, and that is worth keeping: the
begin/end pairs are what makes a bad startup or a surprise recreate legible in a
logcat. So the tracker goes and the Log.i calls stay, at the same points, under
this class's own tag instead of LifeCycle's.

onStart and onStop now only log, so they lose the no-native-binary guard. There
is nothing left in them to guard, and if the library is missing is exactly when
you want to see the callbacks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01152igCdjTNhT29qvphPyPC
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 937022aa8e Android: Don't log every mouse movement
Moving a mouse logged two lines from Java and one at INFO level from C++
for every motion event.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 726c98198d Android: Mark the last two JNI-called Java methods @Keep
renderText and getNativeCrashHistory are only called from native code,
like their neighbours that do have the annotation. Harmless today since
minification is off, but it would strip or rename them the day it's on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 4a3ba23820 Android: Forget a key when it's released
pressedKeys is there so Disconnect() can release what's held, but keys
were only ever added to it, so it listed every key pressed so far.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 a413b88cab Android: Make renderer_inited atomic
It's written by the thread that brings graphics up and down, and read by
every input function on the UI thread. The comment also claimed it was
only used with OpenGL, but the Vulkan path sets it too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 a77eba1554 Android: Create the TextRenderer paint only once
TextRenderer.init runs in every onCreate and replaced the static Paint. On
the OpenGL path the emu thread survives the activity and can be in the
middle of measuring or drawing text with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 26d533904e Android: Hand out the same font ID for the same font file
Each TextDrawerAndroid allocates its fonts afresh, and one is created
every time graphics come up. allocFont gave every request a new ID and map
entry, and nothing ever calls freeAllFonts, so the map grew by a few
entries per resume.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 b16693cf48 Android: Survive long enough to show the missing native library message
When the library fails to load, onCreate shows a dialog and returns before
setting anything up. But onResume, onWindowFocusChanged and the rest still
ran, dereferencing the missing SizeManager or calling into the missing
library, so the app crashed before anyone could read the dialog. Make them
all check.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 d887e4a8ae Android: Release the camera if starting it fails
If startCamera threw after Camera.open (no usable preview size, say),
mIsCameraRunning stayed false, and pause() only releases a running camera.
So it was held until the process died, locked for every other app. Release
it on failure, and before opening it again.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 ea9be99149 Android: Parse a launch parameter sent to the running app as a command line
An intent arriving while we're running was passed on as a game path,
whole. That was right for a shortcut or a file association, but the Args
extra carries a command line, so its options got booted as a file.

Run it through the same parsing as at startup. The options can't be
applied anymore and aren't, but parsing them is what separates them (and
their arguments) from the game to boot. If there is none, do nothing.

This also unescapes a quoted path the same way NativeApp.init does, where
before only the quotes were stripped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 105bf522d7 Android: Declare the main activity resizeable explicitly
We never said either way, so the system treats it as resizeable only by
virtue of the target SDK (RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION). Samsung
DeX wants it spelled out before it offers free resizing and maximize, and
a user reports being stuck in a small fixed window there. Not verified on
DeX.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00
Henrik RydgårdandClaude Fable 5.1 cec72d46c5 Android: Stop location updates while paused
Nothing turned the GPS off when the app went to the background, so a game
that had opened it kept it running until the process died. Pause and
resume it like the camera. A start request that arrives while paused (as
the one from the permission dialog does) is deferred to resume.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 20:11:23 +02:00