303 Commits
Author SHA1 Message Date
Brian Degenhardt 3e56da7f86 Merge upstream PCSX2 (2026-07-15 .. 2026-08-10)
71 commits from 474ad59818 to 2cf8dabe6b, triaged rather than taken wholesale.

Declined, resolved to ours:

- AGENTS.md: upstream's AI-agent instructions; we carry our own and do not
  want a second, conflicting policy file.
- CI deps bump (setup-node, labeler): both target workflows are absent here,
  and the labeler job is gated on the repository being PCSX2/pcsx2.
- KDDockWidgets 2.4.1: two of the six files do not exist here; we already
  build 2.4.0 against a 2.3.0 floor, so there is nothing to gain.
- The FullscreenUI Achievements-layout realignment: our section already
  carries the same settings, and ours is the branded copy.
- The GS draw/vertex-buffer cluster (7887919e74, b2fa00844e, 99cfbb49c1,
  5c611f85e1, 9945046a49, af48193ebb, d88510e3a6, 8c1bb5742e). Our vertex
  kick is an ARM64 rewrite of the same hot path -- register-resident cursor,
  fused min/max with a rewind watermark, and a scalar cull mirror that
  dual-issues against the NEON parse -- so upstream's generic pointer-logic
  optimisation is a variant of work already banked here, and their growth
  restructure replaces per-buffer capacity with a single global value, which
  the pooled draw-node model cannot express. Two of the four August commits
  in that cluster repair regressions the July rewrite introduced, and the
  third's genuine fix (staging arrays sized from an unrelated buffer) we had
  already made independently.

Taken with adjustment:

- EATAN coefficients (aae9438f98). Upstream relabelled mVU_Globals so the
  names match the powers; we had fixed the same defect by ordering the arm64
  call sites by power instead. Both fixes are correct alone and CANCEL when
  combined, so the arm64 call sites move to plain ascending order in the same
  commit. The values never moved, so this emits an identical instruction
  sequence. Their fix also repairs the x86 mVU we still carry.
- Shader cache version: upstream numbered their tfx.glsl change 109, which is
  below our 110. Taking their value would hand every user a stale blob, so
  this lands as 111.
- FullscreenUI: took the two readback-spin toggles, placed outside our
  non-Apple guard rather than inside upstream's unguarded run.
- Restored tools/generate_fullscreen_ui_translation_strings.py, dropped by
  431ca0c063, and regenerated both string areas. That also registers the Big
  Picture setup-wizard strings, which had never been extractable.

GameDB: the three serials upstream gave gsHWFixes (SLES-53869, PAPX-90020,
SCPS-15064) are absent from the mobile overlay, so no fix is silently erased
on handhelds.
2026-08-10 18:24:24 -07:00
Brandon 1a13086768 iOS: Optimize FPS Caps for Performance, Battery Life, and Thermal Efficiency
# iOS: Optimize FPS Caps for Performance, Battery Life, and Thermal Efficiency

## Summary

This pull request is organized as two incremental commits:

1. **FPS Cap Fixes and Optimizations**
2. **JIT Double Alert and In-Game No-JIT Indicator**

The work is rebased onto `origin/master` at `f4e6452141be556f46bb0f3249d07865df4f64d5`.

This base includes the upstream GS back-queue ownership fix from
`173d10bf0c6cf9e46b2850ac40f9e865a50635d5`. The FPS-cap implementation does
not restore the removed EE-thread back-queue drain or alter that synchronization.

The two source archives mirror that order. The second archive is intended to be applied after the first. Each archive contains complete source files at that commit's state, with repository-relative paths. No patch file is required.

## Features

- Presentation-only FPS targets which preserve 100% game, CPU, and audio timing.
- One shared FPS-target slider design globally and per game, with exact detents at `15`, `23.976`, `29.94`, `30`, `45`, `59.97`, `60`, `90`, and `120` FPS.
- Dedicated per-game FPS overrides with global inheritance.
- Compact FPS labels: integer targets display without `.000`, while broadcast/film cadences retain their meaningful decimals.
- Exact active-cap reporting in the performance OSD.
- Turbo-aware cap suspension and automatic cadence restoration.
- Progressive and interlaced cap-skipped rendering paths.
- Bounded Metal command-buffer batching across eligible skipped frames.
- A two-stage No-JIT boot warning with an explicit destructive confirmation.
- A red in-game **No JIT** indicator beside the Pause/Quick Menu control.

## Fixes

- Selecting 30 FPS no longer halves emulation and audio speed.
- Fractional FPS targets no longer get rounded into a different cadence, while whole targets no longer display unnecessary `.000` suffixes.
- Per-game FPS targets correctly override the global target.
- Legacy per-game and global `NominalScalar` FPS profiles migrate without losing their selected cadence.
- RetroAchievements Hardcore Mode no longer rejects a presentation cap as though it were slow motion.
- Quick Menu normal-speed reporting no longer derives game speed from display FPS.
- Fast Forward no longer races settings reloads or inherits a stale presentation deadline.
- The OSD hides the cap label on the unchanged 60 FPS-or-higher master path.
- Interlaced skipped frames retain required deinterlacing history.
- Screenshots, recording, GS dumps, framebuffer feedback, and software rendering retain complete frames.
- Texture age continues advancing during capped frames without scanning the complete pool every skipped frame.
- Metal skipped-frame batches have bounded frame, encoder, and wall-time limits.
- Normal Metal submissions clear skipped-batch bookkeeping, preventing a stale counter from forcing a later unnecessary flush.
- Cancelling or dismissing either No-JIT warning safely clears the pending game boot.

## Performance, battery, and thermal improvements

- Eligible capped frames omit final display merge/composition and optional output processing.
- FXAA, CAS, MetalFX, ShadeBoost, shader-chain, final-resize, drawable, presentation, and OSD passes can be avoided on those frames.
- Metal work is submitted in bounded batches to reduce driver and tile load/store overhead.
- Texture-pool deletion scans are amortized while preserving texture lifetime and memory bounds.
- Fewer output-sized render passes can reduce GPU occupancy and memory bandwidth.
- Reduced GPU, memory-controller, and driver activity can lower package power and battery drain.
- Lower sustained package power can reduce heating, preserve thermal headroom, and delay throttling.
- Screen recording and other system workloads can receive more GPU and thermal headroom, reducing the chance that recording pushes an already demanding game into thermal slowdown.
- Higher internal/output resolutions can benefit disproportionately because the skipped final effects operate over substantially more pixels at 2× and 3× resolution.
- The native 60 FPS and above-60 FPS paths retain master's original rendering and performance behavior.
- Android retains master's presentation-only cap path and does not opt into the iOS final-render bypass.

## Commit 1 — FPS Cap Fixes and Optimizations

Suggested commit message:

> iOS: make FPS caps presentation-only and optimize skipped frames

### Problem addressed

The old iOS FPS-target implementation encoded the selected target in `Framerate/NominalScalar`. A 30 FPS selection on a roughly 60 FPS game therefore requested approximately 50% emulation speed. CPU/game timing and audio slowed down together with the displayed frame rate.

An FPS cap should instead control how often the finished image is presented while the emulated PlayStation 2 continues running at normal speed.

### Presentation cadence is independent from game speed

The selected target is now stored in the dedicated `ARMSX2iOS/FramePacing/TargetFPS` setting.

- `Framerate/NominalScalar` remains `1.0` whenever the normal limiter is enabled.
- EE, IOP, VU, game logic, input processing, patches, and SPU2 audio remain at 100% timing.
- Turning the limiter off still uses the existing unlimited-speed scalar.
- Fast Forward remains a separate emulation-speed control.
- The Quick Menu reports Normal Speed from the actual nominal-speed scalar, not from the presentation target.
- RetroAchievements Hardcore Mode no longer treats a presentation-only target below 60 as slow-motion.

Legacy global and per-game profiles that encoded an FPS target in `NominalScalar` are migrated to the dedicated cadence setting. Their selected display target is retained, while normal game speed is restored.

### Exact and per-game targets

Frame Pacing now uses one slider with precise detents for:

- `15 FPS`
- `23.976 FPS`
- `29.94 FPS`
- `30 FPS`
- `45 FPS`
- `59.97 FPS`
- `60 FPS`
- `90 FPS`
- `120 FPS`

The per-game Frame Pacing screen uses the same slider, detents, formatting, and presentation-only behavior. A per-game profile can inherit the global target or enable its own override. Whole values render as `15 FPS`, `30 FPS`, and `120 FPS`; exact fractional cadences remain `23.976 FPS`, `29.94 FPS`, and `59.97 FPS`.

This is integrated with master's numeric override-row design rather than restoring the deleted picker lists.

### Native behavior at 60 FPS and above

The custom presentation scheduler is enabled only for positive targets below `60.000 FPS`.

- Exactly 60 FPS follows master's normal render, texture-pool, Metal submission, and presentation behavior.
- Targets above 60 FPS also follow master's normal path.
- `59.97 FPS` remains a real fractional cap because it is below 60.
- The OSD does not show a cap label on the native 60-or-higher path.

This keeps the default configuration and high-refresh targets out of the new cap-specific optimization paths.

### Cadence scheduler

The GS thread uses a deadline accumulator rather than a simple “was the last frame too recent?” check.

- It preserves the requested average rate when the target is not an integer divisor of the game's source cadence.
- It avoids assuming that every game produces exactly 60.000 frames per second.
- It re-synchronizes after a large hitch instead of banking skip credit and presenting a burst afterward.
- Turbo suspends the custom cap and clears its deadline.
- Returning from Turbo re-primes the scheduler from the next normal frame.
- Turbo state and cap suspension are changed together on the CPU thread, avoiding a settings-reload race.

### OSD reporting

When FPS is shown in the OSD, the active target is appended in a readable form, for example:

> FPS: 30 (Cap 30 FPS)

Fractional values are retained instead of rounded away. The label is hidden when no sub-60 cap is active and while Turbo temporarily owns presentation cadence.

### Work omitted on eligible cap-skipped frames

By the time the GS VSync scheduler chooses to skip presentation, correctness-sensitive PS2 draw commands and framebuffer writes have already been processed. On an eligible hardware-renderer frame, ARMSX2 can omit display-only work that cannot affect emulated GS memory.

For a progressive cap-skipped frame, the optimized path can omit:

- final GS display merge/composition;
- ShadeBoost;
- FXAA;
- RetroArch shader chains;
- final output resizing and the bilinear-sharp pass;
- CAS sharpening/upscaling;
- MetalFX spatial upscaling;
- TV/presentation shader work;
- drawable acquisition and final presentation;
- that frame's ImGui/OSD render pass.

This is deliberately not blanket GS draw skipping. Geometry processing, texture uploads, render-target state, framebuffer writes, and other emulation-visible graphics work continue so later frames remain correct.

### Interlaced games

Interlaced cap-skipped frames use a separate path:

- display merge and required interlace/deinterlace history are updated;
- optional visual filters, final output processing, and presentation are omitted.

This preserves temporal history for 480i titles. Because required history work remains, interlaced games generally save less work than progressive games.

### Correctness guards

The aggressive final-composition bypass is disabled when a complete image or emulation-visible output is required, including:

- screenshots;
- video capture;
- GS dumps and configured frame/texture dumps;
- active `EXTWRITE` framebuffer feedback;
- invalid or unavailable current output;
- software-renderer operation;
- non-cap skips such as manual frame skip and the existing duplicate-frame path.

Those situations retain the full rendering path.

### Texture-pool maintenance

Texture lifetime still advances for every emulated frame. Cap-skipped frames preserve deferred-draw ordering but amortize the pool deletion scan:

- cleanup runs no later than every fourth cap-skipped frame;
- normal presented frames keep master's standard pool maintenance;
- native 60+, manual skips, and duplicate-frame skips continue using master's standard `AgePool()` path;
- purging resets the deferred-cleanup counter.

This reduces repeated GS-thread pool scanning without allowing frame age or retained pool memory to stop advancing.

### Bounded Metal batching

Compatible Metal work can remain in one command buffer across cap-skipped frames, reducing repeated submission and tile load/store overhead. Retention is bounded and flushed when any of these limits is reached:

- 4 cap-skipped frames;
- 256 encoders;
- 75 milliseconds.

The bookkeeping resets whenever encoders are flushed, including normal presentation, readback, and upload-only flushes. This prevents a submitted batch's old frame count or start time from causing an unnecessary flush in the next batch.

### Detailed 30 FPS example

Consider a game producing approximately 60 GS VSyncs each second with an active 30 FPS target:

- the PlayStation 2 simulation still processes approximately 60 game-time updates per second;
- audio continues at normal pitch and timing;
- approximately 30 frames are selected for display;
- approximately 30 final presentation opportunities are cap-skipped;
- each displayed frame is spaced at roughly 33.3 ms instead of 16.7 ms.

The cadence is based on wall-clock deadlines, so this should be understood as an average target rather than a hard-coded “always skip every second frame” rule.

#### Battery and power benefits

On eligible skipped frames, the app avoids the final output-sized passes, drawable work, post-processing, and presentation listed above. This can reduce:

- GPU execution time devoted to final composition;
- final render-target reads and writes;
- memory-controller and bandwidth activity;
- Metal command encoding and submission pressure;
- drawable acquisition and compositor-facing work;
- repeated texture-pool cleanup work on the GS thread.

Lower GPU, memory, and driver activity generally lowers package power consumption. Over a long session this can slow battery discharge, especially at 2× or higher internal/output resolutions or when FXAA, CAS, MetalFX, ShadeBoost, or shader chains make the final display pipeline expensive.

#### Heat and sustained-performance benefits

Electrical power consumed by the CPU/GPU package becomes heat. Removing avoidable final-frame work can therefore:

- slow the device's temperature rise;
- provide more thermal headroom;
- delay or reduce iOS CPU/GPU thermal throttling;
- improve consistency in long sessions;
- leave more GPU time for the frames that are actually displayed;
- reduce contention with screen recording and other concurrent system work.

This can be more useful than a short benchmark suggests: preventing an early thermal limit may preserve steadier performance later in a session.

#### A 30 FPS target does not guarantee, but nearlies a 50% reduction in total GPU use, battery drain, or heat.

Only the final-display portion of the workload can approach a 50% reduction when roughly half of 60 presentation opportunities are removed. Correctness-sensitive work still runs, including:

- EE/IOP/VU execution;
- game logic and input;
- SPU2 audio;
- GS command processing;
- geometry and vertex work;
- texture uploads;
- framebuffer and render-target writes that future frames can consume.

The total benefit therefore depends on the bottleneck:

- **Largest expected gain:** GPU/output-bound games, high resolutions, and expensive post-processing.
- **Smaller gain:** EE/VU/geometry-bound games where final presentation is a small part of frame cost.
- **Smaller gain:** interlaced games because history updates remain required.
- **Minimal gain:** games already producing fewer frames than the selected cap.
- **Temporarily disabled optimization:** capture/dump/feedback cases that require complete frames.

No fixed battery percentage, wattage, temperature reduction, or total GPU percentage is claimed without physical-device profiling.

#### Other observable metrics

Potential improvements include lower average GPU occupancy, fewer presented drawables, fewer final post-process passes, reduced memory bandwidth, reduced command-buffer submissions, lower thermal pressure, and improved battery endurance. The tradeoff is visibly less fluid motion and a longer visual update interval, even though simulation and input processing remain at normal speed.

### Why 23.976 FPS is a cinematic option

Theatrical cinema traditionally uses `24.000 FPS`. `23.976 FPS` is the NTSC-compatible fractional form of 24p and is commonly described as `23.98` or simply `24p` in film and video workflows. It preserves the familiar approximately 24-frame cinematic cadence while fitting fractional-rate video systems.

Traditional 24 FPS capture is often associated with a 180-degree shutter, which gives an exposure close to 1/48 second per frame. Motion occurring during that exposure is integrated into each source frame as motion blur. That combination of cadence and source motion blur is part of the familiar film look and can make continuous movement feel less mechanically sharp than high-frame-rate capture.

The ARMSX2 cap does **not** create synthetic motion blur. It controls presentation cadence only. A game's own motion-blur implementation may complement 23.976 FPS; without suitable source blur, fast camera pans can instead show more judder. The option is therefore a visual/performance tradeoff, not a claim that 23.976 FPS is objectively smoother than 60 FPS.

Useful background references:

- [Adobe video delivery specifications](https://www.adobe.com/content/dam/experience-fragments/www/us/en/experience_cloud/avstg/Adobe_Avstg_Cloud-Ad-Specs-12.13.2018.pdf) describe `23.976` as film-look content commonly referred to as 24 FPS.
- [ARRI's shutter white paper](https://www.arri.com/resource/blob/186770/ad23969317dafff402f0902d47bb4ab7/alexa-studio-electronic-and-mirror-shutter-white-paper-data.pdf) explains the 24 FPS, 180-degree-shutter relationship and its motion-blur characteristics.

### Resolution, recording, and contributor-observed result

This optimization is particularly relevant at higher resolutions. Final merge, scaling, sharpening, post-processing, and presentation passes operate over output-sized images. At 3× resolution those passes touch many more pixels than at 1×, so omitting them on eligible non-presented frames can return more GPU time and memory bandwidth than it would at native resolution.

That recovered headroom can be used for a higher resolution, screen recording, or more stable sustained clocks. The contributor reports that on an iPhone 14 Pro Max this branch can run at 3× resolution with a cinematic `23.976 FPS` target while retaining enough headroom to record, whereas ARMSX2 iOS 2.4.1 was usable only at 1× in the same practical comparison. This is a device/workload observation, not a guaranteed result for every title or device; gains depend on whether the title is limited by output rendering, EE/VU work, GS geometry, or another subsystem.

### Why this can improve battery life and heating

Apple's performance guidance connects GPU workload and memory bandwidth with energy consumption and thermal behavior:

- [Measuring the GPU's use of memory bandwidth](https://developer.apple.com/documentation/xcode/measuring-the-gpus-use-of-memory-bandwidth) notes that reducing bandwidth saves energy and leaves memory-system capacity available to other processors.
- [Analyzing your app's battery use](https://developer.apple.com/documentation/xcode/analyzing-your-app-s-battery-use) covers CPU/GPU activity, power consumption, and device temperature as related diagnostic signals.
- Apple's [`ProcessInfo.ThermalState.serious`](https://developer.apple.com/documentation/foundation/processinfo/thermalstate-swift.enum/serious) guidance explicitly gives reducing a target frame rate from 60 FPS to 30 FPS as an example response to elevated thermal pressure.
- Apple's [Energy Efficiency Guide for iOS Apps](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/index.html) explains why eliminating unnecessary work improves energy use and sustained behavior.

For this implementation, the benefit comes from avoiding eligible final-display work—not from slowing the PlayStation 2 simulation. Less output-side GPU work can reduce instantaneous GPU occupancy, memory traffic, command processing, and compositor interaction. Lower sustained power produces less heat, which can delay thermal throttling and preserve performance during longer play or simultaneous screen recording. The amount saved is title-, resolution-, effect-, and device-dependent.

### Commit 1 files

- `pcsx2/GS/GS.cpp`
- `pcsx2/GS/GS.h`
- `pcsx2/GS/Renderers/Common/GSDevice.cpp`
- `pcsx2/GS/Renderers/Common/GSDevice.h`
- `pcsx2/GS/Renderers/Common/GSRenderer.cpp`
- `pcsx2/GS/Renderers/Common/GSRenderer.h`
- `pcsx2/GS/Renderers/Metal/GSDeviceMTL.h`
- `pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm`
- `pcsx2/ImGui/ImGuiOverlays.cpp`
- `platforms/ios/app/src/main/cpp/ARMSX2Bridge.h`
- `platforms/ios/app/src/main/cpp/ARMSX2Bridge.mm`
- `platforms/ios/app/src/main/cpp/IOS/HostImpls.mm`
- `platforms/ios/app/src/main/cpp/IOS/IOSRuntime.h`
- `platforms/ios/app/src/main/swift/Models/AppLanguage+UISupplementTranslations.swift`
- `platforms/ios/app/src/main/swift/Models/SettingsStore.swift`
- `platforms/ios/app/src/main/swift/Views/GameScreenView.swift`
- `platforms/ios/app/src/main/swift/Views/HelpView.swift`
- `platforms/ios/app/src/main/swift/Views/PerGameSettingsPanel.swift`
- `platforms/ios/app/src/main/swift/Views/Settings/EmulatorSettingsView.swift`
- `platforms/ios/app/src/main/swift/Views/Settings/FramePacingSettingsView.swift`
- `platforms/ios/app/src/main/swift/Views/Settings/NumberRow.swift`
- `platforms/ios/app/src/main/swift/Views/Settings/PerGame/FramePacingTab.swift`
- `platforms/ios/app/src/main/swift/Views/Settings/PerGame/NumberOverrideRow.swift`

## Commit 2 — JIT Double Alert and In-Game No-JIT Indicator

Suggested commit message:

> iOS: add two-stage No-JIT warning and runtime indicator

### Two-stage warning before interpreter fallback

When a game is started without detected JIT access, the existing warning remains the first step:

> JIT access is not available. Match the StikDebug script to the JIT Script setting in Emulator settings.

Cancel abandons the pending boot. Continue is now destructive and opens a second confirmation rather than immediately starting the game:

> Are you sure you want to continue?  
> JIT access is required for 60 FPS.
>
> EXPECT TOO LOW PERFORMANCE.  
> PROCEED AT YOUR OWN RISK!

The second alert uses the same warning title and icon. Its red Continue action is the only route that calls the existing interpreter-fallback boot continuation. Dismissing or cancelling either stage clears the pending boot safely.

### Red in-game No JIT status

While the VM reports interpreter fallback as active, a compact red **No JIT** capsule appears immediately to the left of the Pause/Quick Menu button in portrait and landscape gameplay layouts.

- It uses the existing native fallback-status bridge already present in master.
- It updates through the existing runtime menu-state refresh points.
- It adds no emulator-core or JIT implementation changes.
- It adds no independent timer, polling service, or per-frame native work.
- It disappears when fallback mode is no longer active or emulation ends.
- It includes an accessibility label.

### Commit 2 files

- `platforms/ios/app/src/main/swift/Views/GameScreenView.swift`
- `platforms/ios/app/src/main/swift/Views/RootView.swift`

## Scope boundaries

This PR intentionally does not modify JIT allocation, validation, keep-alive, worker lifecycle, recompilers, interpreter implementation, or No-JIT performance behavior from master. Commit 2 is UI and boot-confirmation behavior only.

Unrelated existing workspace changes—such as simulator generation, Swift compilation-memory settings, third-party notices, and Dynamic Background shader files—are not included in either PR archive.

## Validation

- Rebased onto `origin/master` at `f4e6452141be556f46bb0f3249d07865df4f64d5`.
- Includes upstream GS back-queue fix `173d10bf0c6cf9e46b2850ac40f9e865a50635d5` through the new master base.
- Both requested changes were reconstructed as dependency-ordered commits in a clean worktree.
- Commit 1 contains 23 source files and no No-JIT UI hunks.
- Commit 2 contains only `RootView.swift` and the No-JIT portions of `GameScreenView.swift`.
- `git diff --check` passes.
- Full unsigned IPA build completed successfully with `platforms/ios/scripts/build-ios-ipa.sh`.
- Generated IPA: `platforms/ios/build-ios-xcode/ARMSX2-iOS-unsigned.ipa`.
2026-08-05 00:33:19 +02:00
SternXD 624717dfad GS: Fix status bar GPU % when OSD GPU usage is off 2026-08-04 23:01:34 +02:00
Brian Degenhardt 8b31dbce6c GS: let the pipelined split run with asynchronous HW downloads
The front-object split was refused whenever the EE thread services the readback
itself, which covers both Unsynchronized and Asynchronous. That groups the modes
by which thread reads, when the question is what it reads.

Unsynchronized takes GS local memory directly, no lock and no drain, so a queued
back thread leaves it arbitrarily far behind what the EE expects. Asynchronous
does not read local memory at all: it takes the CPU shadow under
m_async_readback_mutex, and the mutex is the synchronization point. The shadow
moves only when the GS thread publishes a completed GPU download, never when a
record is queued or executed, so queue depth cannot change what the EE sees.
Every shadow accessor already routes through m_mem_target, so a front object
reaches the back's authoritative copy - the plumbing was in place, only the gate
was wrong. The refusal was Unsynchronized-only when the split landed; the
asynchronous readback import widened it to the shared predicate.

Keep lockstep for the one case that does read live memory under Asynchronous: a
shadow that never came up sends ReadLocalMemoryUnsync down the fallback path.
The renderer is constructed before this decision, so ask it directly.

Skip the shadow allocation on the front object. The base constructor could not
tell it was building one - m_mem_target still points at itself there - so it
allocated and seeded a full GS-memory-sized copy that nothing can ever read once
the derived constructor repoints it. UpdateSettings runs on both halves and had
the same problem, re-seeding that dead copy on every settings change.

Measured on the SD865, which ships this exact configuration (HWDownloadMode 5,
GSBackThreadMode 3) and was therefore never pipelining at all. Both arms come
from one binary: the back object always constructs lockstep, so -backthread 2 is
precisely what -backthread 3 did before this change. Fan pinned, 3 runs per arm,
-loop 40, medians, ranges disjoint in both titles:

              lockstep    pipelined
  OutRun      12.05 ms     7.75 ms   -36%   83 -> 129 fps
  Rogue Gal   18.97 ms    12.87 ms   -32%   53 ->  78 fps

Rogue Galaxy is the title that just took Asynchronous by GameDB, and it crosses
60 fps on this device as a result.

Correctness: frames are byte-identical across back-thread modes Off,
InlineRecords, Lockstep and Pipelined under Asynchronous, on both the M2 and the
SD865, against a same-binary control run first to confirm the dumps reproduce.
40-loop runs of the previously deadlocking combination complete cleanly with
readbacks exercised. On the M2 frame time is flat at ~6.6 ms - that replay is not
GS-CPU-bound there - though the GS thread still drops 6.17/6.55 ms to 4.06/4.26.

Default configuration is untouched: the back thread is off by default, and only
Asynchronous plus Pipelined changes behaviour.
2026-08-02 19:19:31 -07:00
TJnotJT 5793dbc1ef GS:HW: Only enable GPU timing/stats in backend if the option is enabled.
Helps prevent a crash in Mesa3D Windows drivers.
2026-07-31 17:28:57 +02:00
Brian Degenhardt 17b2be058a GS: record a complete dump under the pipelined back-thread split
The dump's transfer and ReadFIFO hooks sit on the parse path, and its initial
state came from Freeze() on the renderer. Under GSBackThreadMode=Pipelined the
parse path belongs to the front object, so both were reading the wrong object:
the front's transfers never reached the dump at all. A Rogue Galaxy capture that
should be 39.4 MB of packets came out with 90 KB -- 0.2% of the stream, the
ReadFIFO and VSync packets alone -- and replayed as nothing. GSQueueSnapshot
warned about it rather than fixing it (GV7-2).

The dump stays owned by the renderer, which opens and closes it on the present
path; the parse side reaches it through GetDumpSink(), which routes via
m_mem_target, and the initial freeze goes through a new m_parse_target, the
inverse pointer. Both paths run on the MTGS thread -- the front's runahead is
over the back thread, not over the thread handling vsync -- so the front writes
straight into the back's dump with no synchronisation. m_parse_target->Freeze()
is the same call GSfreeze makes for a savestate, which already drains and
already takes registers from the front and local memory from the back.

Verified on a Rogue Galaxy savestate, frame-stepped over PINE so both arms start
from the identical state: the mode 3 dump is byte-identical to the mode 0 dump,
4.2 MB of initial state and 39.4 MB of packets, and it replays in gsrunner to
ten frames identical under both modes. Two mode 0 runs are likewise identical,
so the harness has no slack. Reverting just the transfer sink reproduces the
90 KB dump, so the comparison has teeth.

Two bytes of bookkeeping ride along: GSQueueSnapshot loses the warning, and the
MsgGSDump reply loses pipelined_incomplete, which now has nothing to report.
Whether the split engaged is a genuine question, so it moves to the stats reply
as gs_front_parser, next to gs_back_thread_pct where it belongs.
2026-07-30 21:55:58 -07:00
Brian Degenhardt 975e408ed5 PINE: add a GS-dump opcode so a script can capture without a hotkey
MsgGSDump (0x14, ARMSX2-local) queues a GS dump of the next N frames:
[u32 frames][u32 path_len][path bytes], where frames == 0 stops a recording
dump and UINT32_MAX records until stopped -- the same press/release pair the
GSDumpMultiFrame hotkey binds. The reply is JSON carrying the resolved dump
path, so a client knows the file to wait for instead of guessing at the
snapshots folder's auto-naming.

Three things the naive version of this gets wrong, all found by testing it
against a live Dragon Quest VIII:

QueueSnapshot honours a caller-supplied path only when it ends in .png, and
silently substitutes an auto-named file otherwise -- a scripted client would
write somewhere it never looks. Normalise the path up front instead, dropping
a .gs/.gs.xz/.gs.zst/.png suffix if the caller spelled one out so that naming
the file you want does not earn a doubled extension.

A request that arrives while a dump is already recording creates no second
dump: the VSync handler only opens one when none exists. It writes a stray
screenshot, and worse, overwrites the running dump's remaining frame count and
cuts it short. The first version of this replied with a path for a file that
was never created and truncated the recording that was. Refuse instead, with
reason "already recording"; the caller can stop the running dump first. The
same defect reachable via the Screenshot hotkey is left alone here -- it is a
renderer behaviour change and belongs in its own commit.

The PINE thread cannot push MTGS packets: the ring is single-producer and that
producer is the EE thread. Take the same two-hop route BuildStatsJson already
documents -- Host::RunOnCPUThread, then RunOnGSThread -- and read GSConfig's
compression method on the GS thread, since it decides the extension.

QueueSnapshot and GSQueueSnapshot now return whether they took the request;
existing callers ignore it. GSIsDumpRecording and GSHasFrontParser expose the
two pieces of GS-thread state the reply needs. pipelined_incomplete surfaces
the known GV7-2 gap rather than letting a script collect corrupt dumps.

Verified live: every promised path was written, refusals produced no files,
and all three dump shapes replay in gsrunner -- single-frame as 4 (2) frames,
a stopped multi-frame recording as 186 (91).
2026-07-30 21:55:58 -07:00
J1coding 3b38bd2605 GS: purge the texture cache when a geometry hack changes
Turning Align Sprite off and watching nothing happen is the report that keeps
coming back. The setting does reach the renderer now, but the pixels it already
moved are sitting in a cached target, and nothing invalidates them.

These hacks rewrite vertex positions or texture coordinates on the way into a
draw, so what they change ends up baked into whatever target that draw landed
on. Games that redraw a target every frame hide this, because the next frame
paints over it with the new setting. Games that build a background or a HUD
layer once and keep sampling it do not: that target keeps the shifted pixels
until something else happens to invalidate it, which can be a whole scene later.

From the player's side that reads as a setting that will not switch off, and
then as ghosting, which is exactly how it was reported.

Native scaling was already in the purge list for the same reason and its comment
claimed it was the only geometry hack that outlived the draw. It is not, it is
just the most obvious one, because it swaps the texture outright rather than
nudging what gets drawn into it. Align sprite, merge sprite, round sprite, half
pixel offset, wild arms, native palette draw, bilinear and the texture offsets
all have the same problem.

Costs a frame when you toggle one, same as every other entry in that list.
2026-07-29 20:18:58 +02:00
jpolo1224 b53ba9a2a3 GS: drain the back queue before presenting
Pausing with the GS back thread enabled crashed the app, reported for a long
time as "crashes when I pause to change settings". The settings were never
involved: the emulog shows the apply completing, the VM resuming, running for
a second, and dying on the NEXT pause. Looking at the settings path found
nothing wrong with it because nothing is.

GSPresentCurrentFrame was the one device-mutating entry point that did not
drain. GSreopen, GSResizeDisplayWindow, GSUpdateDisplayWindow and
SetVSyncMode all do. It reads like a query rather than a mutation, but
presenting records into the command buffer and begins a render pass.

That only bites while paused, which is the one time anything calls it in a
loop: MTGS's idle branch re-presents the frame whenever the VM is not Running
so the window is not left black. The back thread can still be working through
queued draws at that moment, so both threads call vkCmdBeginRenderPass on the
same VkCommandBuffer -- which Vulkan requires the caller to externally
synchronize. Adreno's driver faults inside vkCmdBeginRenderPass rather than
reporting an error, and both threads abort; the log prints "Aborting
application." twice, once per thread.

Reproduced and verified fixed on an Adreno 740 (driver 512.676.53) over
repeated pause/resume cycles with the back thread on.

Diagnosed with jpolo1224 from an on-device tombstone.
2026-07-28 01:38:34 -04:00
Brian Degenhardt 2c02dc8b96 GS: drain the back queue in GSreopen before shredding its textures
GSreopen opens with GSParseTarget()->Flush(GSREOPEN), which flushes FRONT parse
state and *queues* the resulting draw -- GSState::Flush does not drain. Both arms
below it then hand the back thread's textures to the shredder: the device-loss
arm (recreate_device && !recreate_renderer) calls PurgeTextureCache, ClearCurrent
and PurgePool, and the other arm reads the texture cache back. Same class as the
three window/vsync seams fixed in 578cb3a83a, which is where this was found and
deliberately left alone pending the safety question.

That question resolves in favour of draining. The worry was that on device loss
the back thread could be wedged in the driver and waiting on it would hang
recovery instead of recovering. It cannot: BeginPresent only reports DeviceLost
off m_last_submit_failed, so the driver has already declared the loss by the time
we get here, and post-loss calls return VK_ERROR_DEVICE_LOST rather than
blocking. There is no backlog to chew through either -- SubmitVsync drains before
ExecVsyncRecord and present never queues, so the queue is empty on entry and the
Flush above is the only producer.

Note this is NOT the Android suspend/resume path. Backgrounding kills the
surface, not the device: BeginPresent returns FrameSkipped and resume comes back
through onNativeSurfaceChanged -> MTGS::UpdateDisplayWindow, which 578cb3a83a
already drains. The trigger here is genuine device loss, which the tree documents
twice -- the Mali r44p1 blob that returns VK_ERROR_DEVICE_LOST on every game, and
Rogue Galaxy hitting it at vkWaitForFences.

DrainBackQueueBeforeDeviceMutation moves above GSreopen unchanged so it can be
called from there. No test: this seam class has no runtime test surface, same as
578cb3a83a.
2026-07-26 15:01:08 -07:00
Brian Degenhardt 2e40d9e799 GS: drain the back queue before the three window/vsync device seams
GSResizeDisplayWindow, GSUpdateDisplayWindow and GSSetVSyncMode all reach
g_gs_device from the MTGS thread -- swapchain resize, window recreate, vsync
change -- while the back thread is executing draws against that same device.
Every sibling seam of this class drains first; GSUpdateConfig does, and its
comment names this exact hazard. 5aeb3dd8bc added drains to the class and
missed these three.

The drain is sound for the same reason it is in GSUpdateConfig: the front only
parses on the MTGS thread, so one drain up front quiesces the back thread for
the whole call. It costs nothing when the back thread is off (the default) --
DrainBackQueue early-outs on consumer_running -- and the null check is real,
since g_gs_device is created before g_gs_renderer.

Re-sweeping the rest of GS.cpp for the same class: CloseGSDevice is safe by
ordering (always after CloseGSRenderer, and ~GSFrontState drains), and the
device reads (GetRenderAPI, stats, GetWindowInfo) are not hazards. GSreopen's
device-loss branch does purge the texture cache and the device pool with draws
possibly still in flight, but the drain is NOT obviously safe there -- if the
device is lost the back thread may be wedged in the driver, and waiting on it
would hang recovery. Filed separately rather than fixed blind.
2026-07-26 15:01:08 -07:00
J1coding 538421f65b iOS: fix graphics settings that never reach the running game
Align Sprite and Merge Sprite look stuck: turn one on and it stays on after you
turn it off. The INI write is fine in both directions -- nothing ever tells the
VM about it.

A graphics setting only live-applies if its Setting<T> declares an onSet closure
calling requestGraphicsApplyGuarded(), and most declare nothing. The C++ fallback
in setINIBool/setINIInt covers a hardcoded allow-list with none of the user hacks
on it. 33 keys are in neither and only take effect once some unrelated setting
forces a VMManager::ApplySettings, or on reboot.

The allow-listed ones were not really working either. The poke writes GSConfig
from the UI thread, so when GSUpdateConfig runs it takes old_config from that
already-updated GSConfig and every new != old guard is false: hw_mipmap and
texture_preloading skip the texture-cache purge they are listed for,
LoadTextureReplacements skips UpdateConfig and the hash purge, OsdShowGPU never
reaches SetGPUTimingEnabled.

So make it structural rather than extend the list, since the list is what rotted.
Setting<T> now gives every EmuCore/GS key the hook by default and the 23
hand-written copies are gone; both poke helpers go with them, leaving one apply
per key and nothing writing GS state off the CPU thread. Fourteen keys are not
Setting<T> instances -- five plain didSets, and the nine homogeneous hardware
fixes that live in a [String: Bool] dictionary -- so those call it from their
write funnel, and resetGraphicsDefaults goes through that funnel instead of
writing the INI beside it.

Renderer keeps no hook: it is a restart option, so applying it live tears the
Metal device down under the running game. Native scaling joins the texture-cache
purge, being the one geometry hack that outlives the draw -- it swaps a target's
texture for a downscaled one and pins m_scale to 1, so those targets stay
downscaled after it is off.

Also: MaskUpscalingHacks zeroes the sprite hacks below 2x while the UI only gated
them on Manual Advanced Hacks, so at 1x they read on and did nothing. Say so. The
Skipdraw footer claimed its changes need a reset; they do not.
2026-07-26 15:47:05 +02:00
jpolo1224 069f8a44f3 Android 2.6.5.1: Local Link LAN play, async GS readback, pause/rotation/settings fixes
Crash and correctness
- Fix a crash when backgrounding the app mid-game: onPause flushed the Vulkan
  pipeline cache from the UI thread while the GS thread was creating pipelines
  into the same VkPipelineCache. Vulkan requires that handle to be externally
  synchronised, so this was a driver-level data race and crashed on Adreno and
  Xclipse alike. The flush now runs on the GS thread, posted via the CPU thread
  so it does not race the EE-owned MTGS ring.
- Fix an unbounded out-of-bounds vertex read in the GSRendererHW sprite-merge
  paving path: the inner loop advanced i instead of j, so j stayed loop-invariant
  and the scan walked past m_vertex->tail.
- Fix per-game settings being silently ignored: gamesettings/<serial>_<CRC>.ini
  loads into a higher-priority layer than anything the app writes, and saves made
  from the library never regenerated it, so any key already in that file
  overrode the user permanently. Only the category-Reset path rewrote it, which
  is why Reset appeared to be the only thing that worked.
- Fix screen rotation: the BIOS followed the launcher rotation instead of the
  renderer's (it has no GameInfo, and the tier was keyed on that), and the
  launcher stayed locked in a game's orientation after exit because the cleanup
  lived only inside stop()'s vmRunLoopActive-guarded branch, which loses a race
  against the VM thread's own finally. Rotation tier is now an explicit flag and
  the cleanup runs on every terminal path.
- Discard the Vulkan pipeline blob whenever the SPIR-V cache is discarded. It was
  validated only against the device header (vendor/device/pipelineCacheUUID),
  which is identical across an app update, so a SHADER_CACHE_VERSION bump kept
  every pipeline built from the old shaders and nothing pruned it.
- Make eeRecExitRequested atomic: it was a plain bool written from the JNI thread
  and read on the CPU thread.
- OpenGL: restore GL_PACK_ALIGNMENT after readback, add the missing memory
  barrier after the CAS dispatch, and initialise GLState::depth_mask to GL's
  actual default.
- DEV9: log the GetNetAdapter default: bail and the InitNet skip. Both returned
  silently, so a settings mistake surfaced as missing hardware three layers away.

Local Link (new)
- New DEV9 backend bridging emulated PS2 Ethernet between devices over
  authenticated local UDP, so games with a built-in LAN / System Link mode can
  play together. Ported from EmuCoreX (sashkinbro) with the wire format
  unchanged, so peers remain compatible across both forks.
- Network mode picker (Online / Host / Join), host address readout, auto-derived
  peer ids, generated room codes, hostname support alongside numeric IPv4, and a
  link to the supported-games list. Fully controller-navigable.

Performance
- Asynchronous hardware download mode (experimental, opt-in): non-blocking
  GPU->CPU readback so the EE thread no longer waits on the GS thread. Ported
  from EmuCoreX. Appending Asynchronous to GSHardwareDownloadMode makes the enum
  non-ordered, so the relational comparisons on it are replaced with
  IsHardwareDownloadReadbackEnabled / IsHardwareDownloadEEThreadRead.
- Affinity Control Mode (experimental, opt-in): EE/VU/GS priority orders plus a
  Performance Cores mode. Android otherwise leaves these threads unpinned.
- Raise the texture-replacement cache ceiling from 6 to 16 GB; RAM/2 remains the
  real limiter, so this only binds at 12 GB RAM and up.
- Low Latency frame pacing is no longer the default, with a one-time migration
  for installs that took the earlier flip.

Features
- Auto renderer resolves to Vulkan HW on Adreno.
- Auto Progressive Scan (per-game): holds Triangle+Cross through boot.
- OLED black as a modifier over any accent colour, including Custom and RGB.
- Optional system keyboard instead of the built-in on-screen one.

Game compatibility
- Everybody's Golf 4 / Hot Shots Golf Fore! hwDownloadMode across all regions
  (PR #421, XDarkFallenX).
- Delta Force: Black Hawk Down (PR #401, XDarkFallenX).
- Reduced input latency and input handling improvements (PR #403, Splaser).

RetroAchievements
- Inject the client version from a build-time secret kept out of public source,
  with a stock-PCSX2 fallback for secret-less builds, so third parties cannot
  copy the client identity. Covers the iOS token too.
2026-07-25 00:48:56 -04:00
Brian Degenhardt fca9018dce GS: per-draw ledger (GSDrawLog)
We could see that a frame issued 900 draws and 40 barriers, but not which draws
were expensive or what PS2 state made them so. The existing per-draw facility,
GSHWDrawConfig::DumpConfig driven by SaveHWConfig, writes one text file per
draw: right for inspecting a single suspicious draw, wrong for profiling a
scene, since a heavy frame produces hundreds of files.

Records one append-only table instead, so a whole scene can be sorted by cost,
grouped by pixel format, or scanned for draws that forced a barrier, in one pass
with shell tools.

A row is assembled from two points in the draw, because the field sets do not
overlap. The PS2 view is live at the top of GSRendererHW::Draw -- primitive type
and count, FRAME/ZBUF/TEX0 addresses, formats and buffer widths, FBMSK, blend
equation, alpha test and DATE. The backend view only exists at submit, and
reuses the fields DumpConfig already knows: topology, barrier requirement,
tex_hazard, destination_alpha, colormask and drawarea. Draws that return before
submit still get a row, marked unsubmitted, since "which draws were skipped" is
itself a signal.

I/O discipline is the design. At ~900 draws/frame and 60 fps this is ~54k
rows/sec; formatting a row costs microseconds and writing it costs bandwidth,
and both would land on the GS thread -- the thread under investigation. So
capture stores a packed POD into a preallocated arena, with no formatting and no
I/O, and serialisation happens once afterwards. The arena is bounded to ~64
heavy frames, so a long session yields a contiguous prefix rather than an
unusable file; truncation is reported rather than silent.

Activity tracks GSConfig.DumpDrawLog directly rather than a config edge, so
recording works when the setting is already true at GS open. GSUpdateConfig
writes the CSV on the true->false edge, making a live capture "turn it on, play
the slow bit, turn it off" -- both edges drivable over PINE. gsrunner gets
-drawlog <path.csv>.

The stringifiers GetTopologyName/GetTexHazardName/GetDestinationAlphaModeName
were TU-local statics in GSDevice.cpp; exposed as GSGet* so the ledger names
enum fields instead of duplicating the tables.

Verified on a dump replay: row count matches @HWSTAT@ Draws exactly (277/277),
and with the frame limiter off, median-of-5 p50 frame time is unchanged (+0.00%,
distributions overlapping). Note the test scene is ~70 draws/frame rather than
the ~900 the arena is sized for.

This is an attribution tool, never a comparison tool: an A/B with the ledger
enabled on one arm is invalid.
2026-07-22 21:16:45 -07:00
jpolo1224 90daa091db Android: audio backend options, setting descriptions, RA/haptics polish, and ported GS fixes
Audio
- Optional OpenSL ES output backend for devices where the default AAudio path
  crackles, glitches or won't initialise (Settings -> Audio), plus a lightweight
  SPU2 mode that skips the reverb pipeline to free CPU on low-end devices.
- Keep the audio device alive across the in-game menu pause so Android no longer
  reclaims the idle stream and drops sound after the menu sits open (#333).

Settings
- Restored the per-setting descriptions under every GameDB Fix and Advanced
  Speedhack toggle (lost in the settings redesign).
- Per-game Reset now clears the native per-game INI, so it truly reverts to the
  global values instead of the game keeping stale overrides.
- On-screen display now defaults off; Custom stats appear on boot without a
  reset (#385).

Controls / RetroAchievements
- Vibration Strength slider scaling all rumble and touch haptics 0-200%.
- Achievement Sound Volume slider; points now show in the menu before a game
  loads; unlock sounds play with Do Not Disturb enabled.

Misc
- Drop the compiled GS shader/pipeline cache automatically on app update to
  avoid post-update graphical corruption.
- Animated XMB library-background fallback for GPUs without float-texture
  filtering.

GS correctness (ported from sashkinbro/EmuCoreX)
- Reset per-game hardware-hack HLE state on game change (Burnout bloom,
  IRem/GT channel-shuffle) so it no longer leaks across in-app game switches.
- Fix a non-strict-weak-ordering comparator in SortMultiStretchRects.
- Free the leaked m_expand_vao on the OpenGL device teardown path.
2026-07-21 20:42:35 -04:00
Brian Degenhardt 616e5900f3 Merge yaps2/main: GV7 GS front/back thread split (17 commits)
Brings the complete GV7 campaign: GSBackQueue SPSC record ring, draw/
transfer/PCRTC/vsync records with inline executors, the GSFrontState
two-object front split, lockstep + pipelined back-thread modes (default
Off), mid-frame MTGS-thread drain seams, back-thread affinity fix, and
the Qt/Big Picture GSBackThreadMode settings UI.

Conflict resolutions:
- pcsx2/GS/GS.cpp: adjacent additions unioned (Android Tekken 5 Mali
  override + GV7 g_gs_front/GSParseTarget). GSAllocateWrappedMemory
  unioned semantically: keeps the iOS-safe HostSys::CreateSharedMemory
  routing AND drops the static fd so two wrapped allocations coexist
  (the GV7 two-object split runs two GSStates, each with a wrapped vm);
  CreateSharedMemory already unlinks/memfds the name and the iOS
  file-backed fallback O_EXCL-retries per-attempt paths, so coexisting
  allocations cannot collide.
- GraphicsAdvancedSettingsTab.ui: take gsBackThreadMode tabstop; drop
  yaps2's stale "rov" tabstop (no such widget in either tree).
- FullscreenUI_Settings.cpp: GS Back Thread setting inserted outside the
  ARMSX2 !__APPLE__ guard around exclusive fullscreen.

Audit: ARMSX2 one-liners in GV7-rewritten files survived
(GSClut CreateFeedbackTarget, GSState SaveTransferImages); ARMSX2's
Merge()-path additions (RetroArch shader chain, FastMAD fallback) run
post-drain on the vsync path, so no unaudited device seams.

Gates on merged tree: recompiler_tests 1359/1359, gs_vertex_tests 21/21
(incl. new gs_backqueue suite), full build incl. armsx2-qt.
2026-07-19 16:19:06 -07:00
Brian DegenhardtandClaude 5aeb3dd8bc GV7-2: drain the back queue at mid-frame MTGS-thread device seams
Sync-point audit fixes for pipelined mode: GSUpdateConfig's non-reopen
branches, SaveSnapshotToMemory, and capture begin/end all touch renderer
or GSDevice state from the MTGS thread while the back thread may be
mid-draw on the same device. Each now drains queued records first (the
front only parses on the MTGS thread, so nothing new queues during the
operation). DrainBackQueue becomes public for the GS.cpp seam.

Audit conclusions (no code needed): WaitGS callers never touch back-owned
state EE-side (state access travels through MTGS ring packets into already
drained seams); SIGNAL/FINISH are GIFRegHandlerNull in GSState — CSR
semantics are entirely EE-side; a back-thread assert failure aborts the
process on Linux, so it cannot deadlock the front.

Gates: gs_vertex_tests 21/21; gsrunner PNG hashes identical to GV-0
baselines, modes 0 and 3, vk and sw.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-19 14:58:49 -07:00
Brian DegenhardtandClaude 5e3819a087 GV7-1d-ii-c: flip mode 3 to true pipelining
The front parser object no longer drains after each push — the record ring
and pool backpressure bound the runahead, and the back thread executes
draws while the front parses ahead. Every cross-boundary read is either
record-carried, behind an explicit drain, or per-object:

- Serial counters s_n / s_transfer_n / s_last_transfer_draw_n become
  per-object members (were process statics): the front assigns order and
  carries serials in records, the draw executor installs s_n from the
  record, and the transfer executor counts its own slice stream — so TC
  timestamps and age heuristics on the back thread see the executing
  draw's serial, not the front's runahead position. Qualified static refs
  in the TC/MultiISA/OGL-debug/SW-dump paths now go through the renderer
  object. The front re-syncs s_n and the scanmask after each (drained)
  vsync.
- Kick-time IsCoverageAlphaSupported drains before reading last-flushed-
  draw state (exact AND deterministic: post-drain state is a function of
  the record stream, not thread timing; the alpha clause can read CLUT
  bytes so it is not front-computable). Memoized per (draw serial, live
  ALPHA) => at most one drain per AA1 draw.
- The HOST->LOCAL exec cursor stays back-side: the front skips the inline
  m_tr.x/y mirror, Freeze adopts the drained back cursor before
  serializing, and Defrost seeds it back.
- GSreset resets the front first (flushing pending draws as records) so
  the back's drain executes them before memory/TC reset, like serial
  pre-reset draws.
- Pipelined mode is refused (falls back to single-object lockstep) when
  HWDownloadMode is Unsynchronized: that path reads local memory from the
  EE thread with no drain.

Gates: gs_vertex_tests 21/21; gsrunner PNG hashes bit-identical to GV-0
baselines for modes 0/1/2/3 x vk+sw over all 10 dumps, and mode 3 repeated
3x with identical hashes (pipelining is deterministic by construction).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-19 14:33:20 -07:00
Brian DegenhardtandClaude 84a1de62b5 GV7-1d-ii-b: two-object front split (GSFrontState + entry-point routing)
Instantiate a GSFrontState parser object under GSBackThreadMode::Pipelined
(SEAM-AUDIT.md $7): it owns all parse state and emits records into the back
renderer's channel; the back object executes them, installing record state
into its own members so the HW look-ahead heuristics read the same names
they always did. Mode 3 still drains per record (lockstep) — the pipelined
flip is the next commit.

- GS.cpp routes GIF transfers, SoftReset, CSR, readbacks, savestates, and
  the vsync PCRTC digestion to the front; present/TC/settings stay on the
  renderer. The front is created only when the back thread engaged, and is
  destroyed first (it drains the shared channel the back owns).
- Drained seams reach authoritative memory through m_mem_target: readback
  ReadImageX/SaveBMP, InvalidateLocalMem, savestate vm8 serialize/restore,
  TC readback/purge (now draining), plus back-side Reset/CLUT-reset and a
  PCRTC re-sync on Defrost.
- The draw executor on a split back aims m_draw_env/PRIM/m_context around
  the tail exactly as FlushDraw does on the front, and restores after.
- m_channel_shuffle_finish is written on both sides; the front's ApplyTEX0
  set becomes a one-shot edge OR-ed into the back-owned flag (a level
  install clobbered the draw path's own sets/clears — FlatOut 2 lost its
  channel-shuffle skip, caught by the vk hash gate).
- Kick-time IsCoverageAlphaSupported reproduces single-object mixed
  semantics: live PRIM/ALPHA from the front, last-executed-draw primclass/
  cached-ctx/alpha-minmax from the back (IsRTWrittenLive split).
- GSAllocateWrappedMemory drops its process-global singleton (close the
  fd/handle once the views are mapped) so two GSStates can each own a
  wrapped vm; also fixes a handle leak in the Windows free path.
- s_transfer_n moves to the submit side: transfer serials are
  front-assigned, and the vsync idle-frame check reads them on the MTGS
  thread.

Gates: gs_vertex_tests 21/21; gsrunner PNG hashes bit-identical to GV-0
baselines for modes 0/1/2/3 x vk+sw over all 10 dumps (mode 3 exercises
Defrost + readback seams through the front object).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-19 14:20:49 -07:00
Brian DegenhardtandClaude 7e612f4750 GV7-0e: PCRTC_SYNC + VSYNC records with inline executors
PCRTCDisplays is not vsync-only state — the HW Draw() heuristics read it
per draw — so under the split it is duplicated front/back and refreshed
by a PCRTC_SYNC record carrying the whole digested GSPCRTCRegs plus the
pre-decrement scanmask counter. GSvsync now digests (unchanged), submits
the PCRTC record, flushes, then submits a VSYNC record carrying
field/registers_written/idle_frame; the executor runs the whole VSync()
body (Merge, present, capture). Record order reproduces today's
semantics: vsync-flushed draws see the fresh display state, mid-frame
draws the previous frame's. Merge's scanmask decrement stays back-side;
the front mirrors it at enqueue once the copies are distinct (GV7-1).

GSPCRTCRegs hoists to GSBackQueue.h as the record payload type; GSState
keeps an alias.

Gate: gs_vertex_tests 17/17; gsrunner PNG hashes bit-identical to the
GV-0 baselines on all dumps, Vulkan and SW.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-19 11:53:04 -07:00
Brian Degenhardt 3e077eff9b Merge yaps2: arm64 JIT transplant + test/perf/libretro infrastructure
Merges yaps2/main (github.com/yaps2/yaps2, c16b88cb7) into ARMSX2,
replacing the arm64 recompiler family with the yaps2 JITs and importing
the yaps2 testing, perf, and libretro infrastructure. Common ancestor is
upstream PCSX2 342db5152 (2026-06-19); git auto-merged all but 38 files.

Replaced (deleted in this merge, recoverable from history):
- arm64/aR5900*, aR3000A*, aVU* -> arm64/iR5900*/iR3000A*/microVU*-arm64:
  EE static-pin register file with lazy dirty tracking, dual-residence
  allocator, IOP block linking, native COP2 macro ops, inline unaligned
  fastmem, persisted VU program cache, call-ret shadow ring, VU0 spin
  fast-forward.
- MVU_DIFF shadow-run hooks in shared VU interpreter TUs (superseded by
  the offline vurunner JIT-vs-interp oracle).

Imported from yaps2:
- tests/ctest/core/recompilers: ~80 gtest suites (EE/IOP/VU differential
  harnesses, fuzzers, ABI digest tripwire, capture format pins) plus the
  gs_vertex_tests kernel oracle.
- pcsx2-vurunner / pcsx2-eerunner headless capture-replay runners.
- tools/perf counter-based A/B rigs, perf jitdump productionization,
  PmuCounters, clang-perf/clang-handheld presets.
- pcsx2-libretro core (ENABLE_LIBRETRO, default OFF; rename pending).
- GS vertex-kick fast path (GV series): TBL-based packed parse,
  register-resident kick, scalar-outcode cull, fused draw-rect/FindMinMax.
- Null renderer, VK_KHR_display direct WSI, swapchain PresentStats.
- SPU2 NEON mixer vectorization, EE timer read clamp (NFL 2K5 hang),
  IOP ioman signed-compare fix, assorted UB fixes.

Kept from ARMSX2 in the both-touched files:
- iOS dual-map W^X and fastmem-unavailable resilience (Memory, HostSys,
  vtlb). The split data/code area model is retained; both areas now take
  fixed VA hints so cached VU JIT code stays deterministic on Linux.
- Android thread-affinity model, VMState shutdown early-outs, all
  platform frontends, branding, CI, RetroAchievements identity/policy.
- GSDeviceVK: ARMSX2's push-descriptor decision logic (Mali crash gate,
  proprietary-vs-turnip Adreno split) merged with yaps2's descriptor-pool
  exhaustion recovery (flush + render-pass restart instead of dropped
  binds). Vendor feature policy is the union: Mali fbfetch policy with
  MediaTek/G57/Xclipse gates from ARMSX2; Adreno stencil/ROV/
  test-and-sample-depth hang avoidance and no_ps2_z_quantization from
  yaps2.

Build-system notes:
- The Qt debugger is now gated behind ENABLE_QT_DEBUGGER (default off on
  arm64) so handheld builds drop the KDDockWidgets dependency.
- GSDeviceNone and remaining yaps2 GS code were ported to the newer
  upstream GSTexture Usage-flags API.

The replaced backend's interpreter-fallback glue (intExecuteOneInst,
AndroidEEOpHist) and the EEDiffVerify runtime differ are retained for
now; dead pieces will be removed in a follow-up commit.
2026-07-19 10:24:29 -07:00
J1coding 445d7aee1b iOS: Use sandbox-safe shared memory for GS
GSAllocateWrappedMemory's POSIX branch called shm_open("/GS.mem",
O_RDWR | O_CREAT | O_EXCL, 0600) directly. The iOS application
sandbox rejects named POSIX shared memory in the system-wide
namespace, so shm_open returned -1, the function returned nullptr,
and GSLocalMemory::GSLocalMemory() aborted via
pxFailRel("Failed to allocate GS memory storage."). On hosts that
re-enter the CPU thread across launches, this surfaced as a game-
launch abort on the GS thread.

The rest of the codebase already routes shared-memory creation
through HostSys::CreateSharedMemory (pcsx2/Memory.cpp uses it for
EE/IOP RAM), whose Linux/Apple branch selects memfd_create on
Android, shm_open on desktop POSIX, and a file-backed TMPDIR
fallback on iOS so the same call works under the sandbox.
GSAllocateWrappedMemory was the only production caller bypassing
the helper.

Delegate fd creation to HostSys::CreateSharedMemory, drop the
Android-only memfd_create special-case and the redundant ftruncate
(both are handled inside the helper), and use
HostSys::GetFileMappingName so the name is PID-qualified instead of
the fixed "/GS.mem". The MAP_SHARED repeat-mirroring mmap loop is
preserved unchanged so the 4 MB GS VRAM still appears `repeat` times
at contiguous virtual addresses for the PS2 GS address-wrap
behaviour. GSFreeWrappedMemory is updated symmetrically to call
HostSys::DestroySharedMemory.

The Windows branch is untouched, and the caller contract (return
nullptr on failure; the caller's pxFailRel handles the abort) is
preserved.

Verified with the iOS build and repeated game launch/exit/relaunch
cycles: GSLocalMemory construction no longer aborts and the wrapped
memory layout is unchanged.
2026-07-18 17:45:36 +02:00
jpolo1224 9e0dc436f7 Android 2.6.0: ANGLE toggle, gyroscope, cheats/disc-swap, GameDB + fixes
- OpenGL-via-ANGLE renderer toggle for broken native GLES drivers, with a
  driver-keyed GL shader cache so switching drivers recompiles instead of
  feeding foreign program binaries to glProgramBinary (fixes Mali-G77 crash)
- Gyroscope input (aim/steering modes, sensitivity, smoothing, invert) shared
  between the Pad settings tab and the in-game Controls tab
- Re-add disc swap without closing the game, and per-cheat PNACH enable
- Xclipse GPU profile + Mali-G615 freeze gate; MediaTek Tekken 5 override
- GameDB: KH2, Tekken 5, Rumble Racing, MK Shaolin, Avatar
- Folder-reuse settings recovery (reverse-map INI + config mirror)
- In-game pause menu rail icons; make new settings searchable
- Resume/auto-load: wait for the renderer to present before restoring state
  and force a present after load (reduces black screen on resume)
- Boot crash guards (pad state before VM); FXAA + CAS sharpening
2026-07-14 02:34:14 -04:00
Brian DegenhardtandClaude Fable 5 5682ed9951 GS: add GSDeviceNone — deviceless host device for the Null renderer
GSRendererType::Null previously still required a real graphics device:
GetAPIForRenderer() had no Null case and fell through to the host's
preferred API, so headless runs needed a working Vulkan/GL context. On
boxes where no API works from a scripted session (mq65: turnip reports
Vulkan 1.0 over ssh — cause unfound; EGL can't create a surfaceless
context) the VM failed to initialize at GS open, blocking eerunner A/Bs.

GSDeviceNone touches no graphics API: BeginPresent() always reports
FrameSkipped so the present/ImGui path never draws, textures are
RAM-backed stubs so map/update callers stay memory-safe, and the Null
renderer pairs with it via a new RenderAPI::None case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 15:55:15 -07:00
Jeen 431ca0c063 iOS: port JIT and W^X foundation, refresh SwiftUI frontend, fix critical boot and display bugs
Port the complete JIT and write-xor-execute infrastructure to DarwinMisc with four JitModes (Simulator, Legacy, LuckTXM, LuckNoTXM), dual-mapping via vm_remap for writable code aliases, the csops CS_DEBUGGED probe for JIT availability detection, brk assembly helpers for the TXM protocol, and the W^X toggle functions. Connect the JIT foundation to the code emitters through AsmHelpers dual-map bridge, Memory.cpp MmapCodeDualMap allocation, and the aR5900 LegacyEnsureExecutable path. Refresh the iOS SwiftUI frontend from the iOS-refresh branch, bringing in 11 missing and 20 drifted Swift files plus ios_main.mm integration. Switch the CI to a real device build using the iphoneos SDK. Fix the Achievements crash by gracefully degrading when no HTTPDownloader is available (no CURL on iOS). Fix the Metal surface to reuse the UIView's existing CAMetalLayer instead of an orphaned allocation that caused half-screen crops. Fix GS memory allocation by using mmap and vm_remap instead of shm_open which is blocked by the iOS sandbox. Suppress the false positive Graphics not Automatic OSD warning. Merge upstream master and resolve all resulting compile errors.
2026-07-12 16:39:22 +02:00