Set by ViewGroup when a view is added, inserted or swapped in. Lets a view
find the scroll view it's in, for example.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- inet_pton's IPv6 half is rewritten as a plain parser of the groups before
and after "::". It no longer rejects a valid trailing "::" after seven
groups or accepts nine groups around a "::", writes bytes in network order
without assuming the host's, and only touches the output on success.
- The IPv4 half rejects empty octets ("1..2.3"), and an unsupported family
returns -1 like the POSIX one, instead of success.
- An empty label ("a..b", ".example.com") ended the encoded name early, so
the direct lookup asked for something else and cached it under the name
that was asked for.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- inet_pton could write outside its output for IPv6 strings with more than
eight colons or two "::". Only reachable through AF_INET today, but it's a
public function. Added a unit test.
- The direct DNS lookup took any datagram that arrived within its timeout as
the answer, and cached it until restart. The socket is now connected, so the
OS drops packets from anyone else, the query has a random ID, and replies
with a different ID, without the response bit, truncated or with an error
code are rejected.
- Packet fields are read and written with memcpy instead of through unaligned
pointers, which can fault on 32-bit ARM.
- The lookup cache has a lock. Only the emu thread uses it so far.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The saved tab was selected before the Search tab was added, so the index
was out of range and no tab was created. Select it after adding Search, and
create the current tab anyway if the saved one no longer exists (Search is
hidden on small screens).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MeasureString was slow mostly because of wrapping: DrawStringRect and
MeasureStringRect re-ran the word wrapper every frame, and it measures
every prefix of every word. Wrapped strings are now cached. The caches
are unordered_maps with an xxhash of the key, and lookups reuse a key
buffer so a hit doesn't allocate. UIContext also skips switching fonts
when measuring in the current style. In a Mac debug build, 200 cached
measures go from 0.26 to 0.06 ms and 10 wrapped paragraphs from 2.1 to
0.01 ms per frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The volume key filter in NativeKey left unmapped volume keys to the OS, so
they never reached the dialog and couldn't be mapped. A new InputMode::AllKeys
lets a screen ask for them; the dialog consumes them so the volume doesn't
change while mapping.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Same as Android: rotation rates go to NativeGyroscope, and SYSPROP_HAS_GYRO shows the "Use gyroscope" option.
Accelerometer data is now scaled from g to m/s^2, like Android's. Tilt normalizes it, but the gyro fusion only trusts the accelerometer when it reads about 9.81, so in g it would never correct drift.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SYSPROP_HAS_ACCELEROMETER was true for every phone and tablet, whether or
not it had the sensor. The activity now tells the native side what the
sensor manager found, which also answers the new SYSPROP_HAS_GYRO. VR
headsets still report neither. Every other platform returns false for the
gyro for now.
The "Use gyroscope" checkbox uses the new property instead of waiting for
gyro data to show up.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tilt used the raw accelerometer as the gravity vector, which jitters with
sensor noise and jumps when the device is shaken. Now, when gyro data is
coming in, a complementary filter integrates the gyro rates and only pulls
the result toward the accelerometer with a 0.5 s time constant, trusting
the accelerometer less the further it reads from 1g. The fused vector goes
through the same ProcessTilt path, so calibration and sensitivity work as
before. Without a gyro, nothing changes.
The fusion lives in TiltEventProcessor behind NativeGyroscope(), so other
sources (DualSense) can feed it too. Android registers TYPE_GYROSCOPE next
to the accelerometer. A "Use gyroscope" checkbox (on by default, per game)
appears in tilt setup once gyro data has been seen.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Find4GBBase found 256 MB of free address space for the views and then
unmapped it, so the gaps between the views went back to the system. On a
32-bit Android device, a running game had malloc heaps between VRAM and RAM
and GPU driver buffers just past RAM, and masked guest addresses (bits 30-31
cleared) reach a whole GB above base, mostly heap. A stray guest access
landed in those silently instead of faulting, and another thread could even
map something where a view was about to go, which the view's MAP_FIXED then
replaced. That fits the 32-bit-only crashes deep in system libraries and in
our own heap structures.
Keep the range reserved as PROT_NONE and map the views over it, putting the
reservation back when a view is released. Mask out bits 28-29 too on 32-bit
(no valid address has them), so accesses stay inside the reservation.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ME sees the main CPU's RAM, but has its own eDRAM at 0 (with
0x80000000 and 0xA0000000 mirrors) where the main CPU has its scratchpad,
its own memory where the main CPU has VRAM, and both CPUs share the boot
page at 0xBFC00000.
- The ME's own memory is views in the main arena flagged MV_ME_ONLY, which
only Memory::CreateMESpace() maps, into a second reservation along with
the shared views at their same arena positions. It gets its own block
shadow.
- The eDRAM (MV_EDRAM) is the ME's half of the eDRAM chip, the GE's VRAM
being the other: 2MB on the PSP-1000 (whose resident ME image says so),
4MB on the others (from mcidclan's libmecore, not hardware-checked).
- At VRAM's addresses, through 0x44000000 and 0xA4000000 alike, the ME
doesn't see VRAM but memory of its own (pspautotest me/vram, on a
PSP-1000): where homebrew keeps the VME's buffers. Its size isn't known;
2MB with VRAM's mirrors here, and no depth layout on 16-bit accesses.
- 0xA0000000 reaches physical memory uncached, so RAM is mapped at
0xA8000000 too.
- The boot page is a view too, which the main CPU reaches through a new
MMIO device (in savestates from MMIO section version 2). The ME runs
code from it, uncached, so the main CPU's writes invalidate code there.
On masked platforms it isn't mapped, and the device uses a plain
buffer.
- AddressSpace has IsValidAddress etc, with the ME's rules when edramSize
is set, and the interpreters and the IR interpreter check and fetch
instructions through their state's space. The IR compiler's checks still
use the main CPU's, until the ME gets a JIT.
- On Darwin, MemArena can hold several view reservations.
Masked platforms (iOS, 32-bit) don't get an ME space yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#22675
Since 3aa4714ce0 desktop Windows draws text with DirectWrite, which looked
up every font in a collection of only our bundled fonts, so an override
like "Font = 微软雅黑" silently fell back to Roboto. Fall back to the
system font collection when the family isn't one of ours.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
PPSSPP has access to the URI, so a frontend or adb can launch the game with it,
which isn't true of a plain path on modern Android.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Texture replacement looks up files from several IO threads at once, while another thread may be
reading from the same archive. libzip isn't thread safe, and a name lookup writes to the archive: it
caches each name's guessed encoding, and allocates a converted copy of CP437 names.
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 case insensitive lookup compares against every name in the zip, while an exact one uses libzip's
hash table. Texture packs can have tens of thousands of files.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The JIT backends' calls out of generated code (the MIPS and IR interpreter
fallbacks, and the bad address report) now take the state the backend was
built for instead of reading currentMIPS. Likewise the interpreter's tick
check, the IR dispatcher's Advance, and a couple of debugger helpers.
MIPSState::InitRegisters() no longer sets currentMIPS; the boot paths set it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A failed final flush (a full disk) or a failed rename into place was
reported as "Saved State". A failed load undo rename still pointed load
undo at the current game, while the file was another game's state.
- Refusing a save or load for being online didn't call the callback,
unlike every other refusal.
- A rewind dropped after its decode started stayed active, and stopped
rewind snapshots until the next clear.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Saving compressed into a ZSTD_compressBound() buffer, about the size of
the whole state, and loading read the whole compressed file before
decompressing it. Now saving compresses into a buffer that grows with its
output and frees the uncompressed state before writing, and loading
decompresses the file in chunks. The file format doesn't change.
Saving also compresses before opening the file, which truncates it, and
drops the unused snappy path. LoadFile and SaveFile are public, for doing
the file part away from the state itself.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
When the file reads fine but a section doesn't, that's almost always a
state from another version, so say that (LoadStateWrongVersion), with the
version that saved it and the failing section on the second line.
LoadPtr() now returns just the section title, for callers to phrase.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Rewind:
- first_ never wrapped, so after about 40 snapshots rewinding past the
oldest state jumped back to the newest ones. Track a count instead.
- States whose base had been replaced stayed in the ring, and each one
failed a rewind with an empty error. Drop them when the base goes.
- Rewinding after pausing for longer than the snapshot interval restored a
snapshot taken just before the rewind ran.
- Rewind snapshots (and verification) counted as saves for IsStale().
Slots:
- Loading an empty slot replaced the load undo backup before failing.
- A refused save (hardcore mode) rotated the slot's screenshots first.
- Refused or dropped operations never called their callback, so auto-load
didn't notice it had failed.
- The slot listing went stale after a save until something rescanned.
Also: Verify always failed (its write pass never set the buffer start),
and corrupt or truncated files were reported as being from an older
version.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The version comes from the file header and goes in the failure message's
metadata line. A state from another version is the usual reason a load
fails. Headless prints it too.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Through Android's storage framework, every exists check, create and open is a round trip to the
storage provider, and writing each file took four of them (a check of its directory, a check of
the file, the create, the open). That made a 6.61 install take around 30 s on a phone, against a
fraction of a second of decrypting and inflating.
The install unpacks into a staging directory it has just cleared, so nothing in it can exist yet.
With the new outputDirIsEmpty option, each directory is created once and each file is created and
opened without checking for it first (File::WriteDataToNewFile), on the IO threads while decoding
goes on. A name that turns up twice still goes the careful way, overwriting the first. The swap
into place at the end is unchanged.
Headless --unpack-updater uses the same path when the output directory doesn't exist yet. Its
output is identical either way. With SIMULATE_SLOW_IO, unpacking the fonts went from 17 s to 7.5 s,
most of what's left being the fixed cost of opening the updater.
The UI's install already runs on a thread pool IO thread, so writes queued on the same pool
could stall behind other long IO tasks, and the pool drops queued tasks unrun at teardown,
which would have left the install waiting forever on its in-flight count. Eight threads owned by
the writer (attached for JNI) avoid both, and headless no longer has to start the pool for it.
Also compare file names case-insensitively when looking for repeats, since the storage usually
is case-insensitive and a second create there makes a renamed copy.
Checked with ThreadSanitizer, and on a read-only destination (every file fails, no hang).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#16823
CLOCK_UPTIME_RAW is mach_absolute_time() in nanoseconds (the same value, not
just the same rate). That's the clock MoltenVK reports VK_GOOGLE_display_timing
present times in, which time_now_raw() is meant to match. Apple's
CLOCK_MONOTONIC counts time asleep, unlike Linux's, so on a Mac that had
slept since boot, from_time_raw() of a present time was off by all of it.
It's also cheaper to read: ~19ns vs ~30ns on M1. mach_absolute_time() itself
is ~8ns, but returns ticks, and the timebase on Apple Silicon is 125/3.
Timing risk: time_now_d() no longer advances while the machine sleeps, so a
sleep/wake shows up as a small delta rather than a huge one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The fallback (Switch, BSDs, anything not Windows/Linux/Android/Apple) mixed
clocks: time_now_d() read the wall clock with gettimeofday, time_now_raw()
and Instant a monotonic one, from_time_raw() didn't subtract the start time,
and time_to_unix_utc() added the timestamp to the current time instead of
the start time.
Now every non-Windows platform shares the same code, built on time_now_raw()
and time_now_unix_utc(). Those two use clock_gettime where we know it's there
and std::chrono's steady_clock/system_clock elsewhere. Instant stores a
single nanosecond timestamp, and time_to_unix_utc() (which was missing on
Linux/Android/Apple) is start time plus timestamp, like on Windows.
The time origin is now set in TimeInit() rather than lazily on the first
time_now_d(), which raced between threads. iOS never called TimeInit(), so
it does now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NativeKey calls KeyEventToFocusMoves directly on the input thread, while
ProcessHeldKeys iterates heldKeys on the UI thread, which tripped an
iterator assert in debug builds. ProcessHeldKeys also dispatched key
events mid-iteration, where a handler calling EnableFocusMovement(false)
would clear the set under it. Now it collects due repeats under the lock
and dispatches them after.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Like the Vulkan backend: a multisampled framebuffer has multisampled
color and depth textures that are drawn to and kept between passes, and
each pass resolves them into the ordinary ones (depth and stencil to
sample 0), which textures, copies and readbacks use.
BindFramebufferSamplesAsTexture binds the samples, for the shared code's
sample 0 copies. Copies between multisampled framebuffers copy the
samples too, both depth and stencil, since Metal can't copy one plane of
a multisampled texture.
Also skips empty draws, which Metal doesn't allow.
With 4x MSAA, 1012 of the 1061 frame dumps are identical to Vulkan's.
Most of the rest differ at edges because programmable blending blends
each sample with its own color. In Resistance: Retribution, the sample 0
copy for a palette lookup matches the render without MSAA here, and
Vulkan's doesn't.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Remove what nothing used: the vertex texture and sampler commands,
the byte-upload setters, and some unused render manager members.
- thin3d pushes its uniform buffer once for both shader stages.
- Build the thin3d depth/stencil cache key from bytes, so memcmp
doesn't compare padding.
- Only create the upload command buffer when there are uploads.
- Compare copy destinations by their base texture when merging passes,
like the textures the passes read.
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>
Headless renders to an offscreen backbuffer that nothing ever draws to,
so the GE debugger's screenshot read uninitialized memory. Fail the
readback instead, like headless Vulkan (which has no backbuffer), so the
debugger falls back to the displayed PSP framebuffer.
All the GE debugger buffers (screenshot, render color/depth/stencil,
texture, CLUT) now read back the same as Vulkan's.
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>
The vertex decoder can write past the end of its output, so a draw
decoded into the last bytes of a push buffer block wrote into unmapped
memory and crashed, now and then, in DrawEngineMetal::Flush. Keep 256
bytes free after each allocation, like the Vulkan backend's push pool.
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>
The render manager now records commands on the emu thread, keeping all
of its decisions there (lazy passes, clear merging, redundant state),
and they're encoded on a render thread, or inline when
bRenderMultiThreading is off, as in the Vulkan backend.
Before encoding, passes to the same framebuffer are merged across
independent work, by the rules of VulkanQueueRunner's render pass merge.
God of War alternates with block transfers to other framebuffers, and
went from about 41 to 18 render passes a frame, which more than halved
its GPU time.
Passes where nothing tests or writes depth or stencil leave the
attachment unloaded and unstored, like the Vulkan backend, which
doesn't attach depth then.
On the eight benchmark states, Metal now uses 4-13% less CPU than
Vulkan, with wall times from 6% faster to 4% slower.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>