mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
dd5b6616eb315260b235fee36083e1ba45fc36a0
24687
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dd5b6616eb | Merge remote-tracking branch 'origin/master' into jit-android-catchup-gv7 2.6.6.4 | ||
|
|
785e4f5343 |
Android: let the resolved config decide fast boot; park the extra button above the D-pad
runVMThread forced boot_params.fast_boot from a raw base-layer read whose fallback was FALSE, while Settings.kt's enableFastBoot and VMManager::SetDefaultSettings both default it TRUE. Whenever the key was not yet in settings.ini -- notably right after an update, before the Kotlin settings have been pushed down -- the app showed "Skip BIOS: on" and full-booted into the BIOS browser anyway; toggling the switch off and on wrote the key and appeared to fix it. Reading only the base layer also discarded any per-game Skip BIOS override. Leave fast_boot unset instead. VMManager::Initialize already falls back to EmuConfig.EnableFastBoot, which it reads late and deliberately so a game can override it, and ApplySettings has run by then. Log the resolved value. The extra analog button defaulted above the left stick; above the D-pad is where it is actually wanted, in the gap between the shoulder column and the D-pad. Portrait cannot mirror that -- the stick sits directly above the D-pad there and the whole left column is taken -- so it parks just clear of the D-pad's right edge. |
||
|
|
014676f3d5 |
Android: ignore the AAB dual-core staging artifact
build-play-aab.sh stages the 16k core into src/main/jniLibs so AGP merges it with the CMake-built 4k one. It is a build intermediate, but an interrupted build leaves it behind, where AGP happily packages it into the next APK and a blanket 'git add' commits 22MB of it as source. |
||
|
|
570ca35d77 |
Android: draw the RetroArch overlay only when a game is on screen
Window() is the one surface every frontend passes through -- the game library, the pause menu and the in-game manager screens are all inside its Box -- so drawing the overlay artwork there unconditionally painted the bezel over the library itself. Gate it on the same RUNNING || PAUSED test the touch controls use, and suppress it while the library is pulled up over a running game. |
||
|
|
c88b18eb74 |
Android: make the stick's extra button a real layout widget; autosave BIOS NVRAM
The extra analog button (sprint/jump) was a satellite of the left stick: drawn inside StickWidget, positioned only by distance/angle sliders in Pad settings. The touch layout editor never knew it existed, so it had no selection box and could not be dragged -- which is what everyone means by "adjustable". Promote it to TouchButtonId.ANALOG_EXTRA (Kind.ANALOGEXTRA) with entries in both default layouts, so it drags and resizes like any other control and splices into saved layouts. The stick keeps the glide hit test -- a finger locked onto the stick is the only pointer that reaches that handler after it leaves the stick -- but now tests the button's OWN circle wherever the user dragged it, lifting the local pointer into overlay space via OverlayDims (which now carries density). Two gestures can hold the button at once, so holders are counted and the keycode is emitted only on 0<->1 transitions. Distance/angle sliders drop out; dragging replaces them. BIOS NVRAM reached disk only on a clean VMManager::Shutdown, and the pause JNI's flush sat in the Running branch, so backgrounding while already paused skipped it. That is exactly how you leave a game that booted into the BIOS browser -- and on a never-configured console the BIOS runs its setup and then parks in the browser, wanting a power cycle before it will boot the disc, as real hardware does. With the config never written, every full boot was the first one: "Skip BIOS off opens the memory card manager instead of the game", permanently. Flush on the paused branch too, and autosave properly -- cdvdWriteNVM sets a dirty flag, cdvdVsync flushes it once a second. The compare-before-write in cdvdSaveNVRAM keeps that cheap. Log the boot shape (fast_boot, CDVD source, disc type, .nvm presence) once per boot so the next report of this names which of the four it is. |
||
|
|
cdb7310a74 | Merge branch 'pr539' into jit-android-catchup-gv7 | ||
|
|
7c39516f6a |
Android: replace shader preset accordions with folder browser
Show only the current shader directory's immediate folders and presets, with an Up row for parent navigation. Promote the redundant shaders_slang wrapper contents into the browser root while preserving other installed packs and user presets. Add active-path markers, stable controller focus and scroll restoration for full settings and in-game menus, plus a vector folder icon. |
||
|
|
7eb414260a |
Android: community batch — stick sprint button, overlays, second screen, save-state delete
Pressure modifier now applies to buttons that are ALREADY held: the range was only read when a press was emitted, so the gesture these games actually use — hold the button, then ease off — did nothing (MGS2 cancels a shot on a half-pressed Square). Macro turbo holds each state for at least 24ms. The pad is sampled on the VM's own schedule, so the fastest frequencies were emitting presses that fell between two samples and never registered, which read as the turbo being dead. Extra button on the on-screen left stick, for sprint/jump. The stick locks the gesture onto the pointer that started on it, so a separate widget could never be reached by a finger gliding up off the stick; the stick hit-tests the zone itself and keeps emitting deflection, making run-and-sprint one thumb motion. Landscape render position (Center/Top), for foldables and clamshell controllers whose screens open downward. Reuses the vertical-align switch that was gated to portrait. Custom internal resolution as a percentage of native, for steps the presets miss. A value matching no preset also stops displaying as "0.25x" while the GS runs something else. RetroArch overlay artwork: import a pack and draw it between the game frame and the touch controls, so it layers with a shader preset and never covers a button. Second-display panel (Ayn Thor, Retroid dual screen): FPS, battery, clock and buttons for save/load state, fast-forward, pause and screenshot. Battery low and temperature warnings, off the sticky battery broadcast. Delete a save state from the in-game picker by long-pressing its slot. Point the PCSX2 CheatDB source at its current home; the old address is gone. |
||
|
|
0bfc42d2a4 | Two-stage No-JIT warning and red in-game indicator. | ||
|
|
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`. |
||
|
|
38ba87f359 |
Test: pin the multiplier deficit in the upper binade
The Booth predicate is keyed to fixed bit positions of ft, but the truncation column moves one bit between the binades -- and every zero-tail row I measured sat in the lower binade, at fs = 2^23. So the region where the decrement fires with the product in the upper binade had never been observed on silicon. The fpmul3 capture already had it. The counts and the row provenance are at the tables. One dead end worth recording, since the data invites it: do not split this by binade, split it by T. fs = 0xFFFFFF looks like the predicate carries no information there (0.1907% low with Booth on, 0.1976% with it off), but it has exactly one T == 0 row, so that is measuring the unmodelled 0 < tail < deficit class instead. At fs = 0x800001, lower binade and also almost entirely that class, Booth is strictly necessary: 13,248/13,248 on, 0/262,144 off. |
||
|
|
e1f3048cc4 |
Optimization: pin the multiplier's predicate mask
emitDefectiveFmul materialised the 0x2AA Booth-digit mask on every
mode-3 multiply (mov + fmov). It is now placed into d10 for the whole
JIT session by _DynGen_EnterRecompiledCode, next to the s8/s9 clamp
scalars and under the same AAPCS64 argument: the low 64 bits of d8-d15
are callee-saved, so a pinned constant survives every C call with no
compile-time tracking. Six instructions become four:
cmtst d30, d11, d10 ; d11 = narrow guest ft, d10 = the parked mask
fmul d0, d0, d1
fcmeq d31, d0, #0.0
bic v30.8b, v30.8b, v31.8b
add d0, d0, d30
Why a pinned callee-saved register beats a caller-saved one behind a
liveness flag, and why q10 specifically, is on NEON_RESERVED_FPU_MULMASK
in iCore-arm64.h. It costs one of the six call-surviving FPR homes.
|
||
|
|
bfc99f2bf3 |
Accuracy: give clamp mode 3 the EE multiplier's one-ULP deficit
The console's multiply array does not round correctly. When the exact product has nothing below the single's ULP to absorb it, the result comes back exactly one step closer to zero -- and whether it does is decided by ft's mantissa alone, so mul.s is not commutative. iFPUd, the eeClampMode:3 codegen, computed the IEEE product; the interpreter has modelled the law since the previous commit. Six instructions per multiply, at both emit sites: recMULop, and recMaddsub's multiply stage, which round through different helpers. The mechanism, the FPCR dependency it rests on, and the one term of the measured predicate it knowingly drops are documented at emitDefectiveFmul. kGuardMaskWitnesses moved on 35 of its 72 rows -- fs is 1.0 throughout, so every product there is zero-tailed. Each was re-derived against the measured law rather than re-pinned against the emitter that moved it; the one row where the dropped boundary term decides is called out at the table. |
||
|
|
9d7d11cd0e |
Fix: model the EE multiplier's one-ULP deficit in the interpreter
The console's multiply array is not a correctly-rounding multiplier: it comes back exactly one step closer to zero on a large fraction of operands, and which operands depends on operand order. Upstream states the rule in a comment (x86/iFPU.cpp) and never tests it; FpuMulHack is a one-point sample of it. What is modelled, what deliberately is not, and where the rule was measured are at eeMulDefectiveFt. All six multiply forms route their product through the new eeMulProduct, which also localises the fpuDouble() calls the sites used to make inline. MADDA/MSUBA gain a temp for it; the accumulate itself is unchanged, and still does not round-trip the product through fpuDouble the way MADD/MSUB do. MulSFpuMulHackOffGivesNativeProduct asserted the opposite -- that the IEEE product is the console's. Silicon returns 0x3F490FDA for those operands and 0x3F490FDB reversed, so the gamefix matched hardware in both orders all along. The deficit only reaches what fpuDouble() hands it. An exponent-0xff operand is clamped to +/-Fmax before the multiply and the product saturates at FLT_MAX, so this engine still has no EE top binade; that gap is fpuDouble's and is untouched here. |
||
|
|
8e91a17905 |
Optimization: widen straight out of the operand register, dropping the copy
Every full-mode widening site paid an Fmov to copy an allocator-resident operand into a temp before ToDouble, for one reason: ToDouble's exponent-0xff has to place the exponent-lowered single somewhere before Fcvt, and it parked it in the register it was reading. ToDoubleFrom(dst, src) puts it in the destination's S lane -- a temp the caller already owns -- so it never writes the source. ToDouble(idx) is now ToDoubleFrom(idx, idx) and emits the same instructions it always did. Instructions removed on the common path: 2 per MUL.S/MULA.S, 3 per MADD/MSUB/MADDA/MSUBA (both operands plus the ACC), 2 per C.EQ/C.LT/C.LE, 2 per DIV.S. recDIVhelper1 now takes the operand registers separately from its two write-only temps; its zero-divisor block reads fs/ft in place. |
||
|
|
b8b5c176aa |
Optimization: keep the MADD product wide between its roundings
recMaddsub rounded the product to a PS2 single with ToPS2FPU_Full and then widened it straight back with ToDouble thirteen instructions later. Nothing observed the narrow form in between except the guard mask, which only reads exponent fields -- and those are as extractable from bits 52..62 of a double as from bits 23..30 of a single. Two new emitters make the round trip unnecessary: ToPS2FPU_Wide rounds to EE precision without changing format, and FPU_ADD_SUB_D is the guard mask on doubles. Shaves off about 5 executed instructions per MADD/MSUB/MADDA/MSUBA, not the 19 the round trip costs: the multiply stage still needs its flag branches, because the O flag is what recMaddsub branches on. Output is unchanged. |
||
|
|
ebf7124f25 |
Test: pin the MADD family's guard mask and rounding
Two new tests exist so that the format-churn work that follows has something to fail against. |
||
|
|
f4e6452141 |
iOS: enforce the two ranges that were only named
vsyncQueueRange and casSharpnessRange are used by the stepper and by the per-game panel, but neither was on its global descriptor's codec, so the global path was the odd one out. The core does not clamp either: VsyncQueueSize is read with a plain SettingsWrapEntry and used raw in MTGS. Only reachable by hand editing the INI, so this is insurance rather than a fix. Checked it anyway: wrote VsyncQueueSize = 99 and CASSharpness = 500 into the file, launched, and the rows read 16 and 100%. The file still says 99 and 500 afterwards, because nothing rewrites it on launch, so someone's INI is only corrected once they change something themselves. Both ini gates stay empty, since 8 and 50 are already inside their ranges and every frame pacing preset writes 2, 4 or 8. The comment calling this a gap goes with it. |
||
|
|
0bdacf3bdb |
iOS: tell people GS Back Thread needs a restart
The core counts GSBackThreadMode in RestartOptionsAreEqual, so a change while a VM is live goes down GSreopen and tears the Metal device down under the running game. That is the same reason the renderer is boot only, and the renderer says so in its picker. This one said nothing, so you change it, nothing happens, and there is nothing on screen telling you why. Nobody can reach the teardown today, because the graphics apply is a no op without a live VM and the only way out of a game is Stop Emulation. bootOnly closes it anyway and costs a line. The setting still writes to the INI exactly as before. bootOnly only takes away the nudge to the running VM. Checked on the simulator: picked Pipelined, the key landed, quit, relaunched, and the picker came back on Pipelined. No translations. The whole section falls back to English already, picker label and all four options included, and translating one footer while the label above it stays English reads worse. |
||
|
|
72f95b7534 |
iOS: keep the next setting from drifting the way the last ones did
Regex over the source, same shape as the other two tests in here. No build impact, nothing to wire up. It is the only thing in this branch that constrains the next setting anyone adds. Twelve checks, and they earned their keep straight away. Two descriptors disagreed with the value their own property starts at: the OSD position declared a named constant then started at a bare 3, and the JIT protocol declared the by-version default then started at .legacy regardless. Swift will not let the initializer say _xConfig.defaultValue, so the duplication has to stay and the test is what keeps it honest. It also found that a descriptor could inline its own read and write pair straight into SettingCodec, which is the exact asymmetry the type exists to prevent, and that four exemptions could rot without anyone noticing. Four settings load by hand on purpose and are listed as such, so adding to that list is a decision rather than something a new setting inherits by sitting next to one. Five migration reads are listed the same way: a migration wants the value as it is on disk before anything loads, sentinel and all. I broke the tree twelve ways to watch each check fail, including renaming the _xConfig convention, which used to make the whole suite pass on an empty set in three milliseconds. The reset functions still read fxaa = false rather than spelling the descriptor out. The literal is easier to read and the test is what stops it drifting. Also wrote down what init() actually does, because the comment above it said the opposite. Assignments there do not fire their didSet, so nothing writes back while the INI loads. That matters most if you are about to tidy init() into per section helpers, where they would fire, and every non suppressible setting would start writing itself to disk on every launch. Measured with a probe inside commit rather than read off the language reference: zero calls across a launch, one call from one toggle in the same run with the same probe. The two dozen widest setter lines wrap now. commit(_xConfig, x) names the setting three times and didSet gives you no newValue to shorten it with, so the longest ran to 177 characters. |
||
|
|
0da4a18a65 |
iOS: let the JIT protocol descriptor pick by iOS version like everything else
Both reset paths use JITScriptProtocol.defaultValue, which answers universal on iOS 26 and up and legacy below it. The descriptor said plain .legacy. Nothing reads it today, because init() maps the older spellings by hand instead of loading. It would have started mattering the moment anyone pointed that line at load(), and a fresh install on iOS 26 would have quietly come up legacy. |
||
|
|
62450cc55a |
iOS: describe each setting once instead of five times
A setting's section, key and default were written out up to five times: the descriptor, the read in init(), the read in reload(), a bare literal in the reset functions, and for two dozen of them the UI descriptor as well. Nothing made them agree. They agreed by discipline, and that is exactly how a setting quietly stops persisting. SettingCodec holds the read and the write together, so the pair cannot drift apart the way two hand written halves do. A descriptor names the one it wants: .bool, .int, .int(in:), .rawInt, and a small number that spell their values out because the core does. Aspect ratio is a menu index here and a name in the file. Audio time stretch is a switch here and the word TimeStretch there. commit() says the three lines every setter was repeating. 115 settings are one line now. The 20 that really do something extra keep it on the line below where you can see it. init() loads through the descriptor that saves it, so there is one place left to get a key wrong. Five stay hand written because they are genuinely not one key to one property, and each says why: the renderer has to correct a desktop value on disk, LastActiveOsdPreset uses -1 to mean never set, the OSD position drops values this build no longer offers, and the JIT protocol maps names older builds wrote. osdShowDeviceStats disagrees with its descriptor on purpose, so it says load(default:) rather than looking like every other line and behaving differently. onSet was only ever asking "is this a graphics key", so it is a plain Bool now and the store calls its own method instead of reaching through SettingsStore.shared from inside a closure. Checked by diffing PCSX2-iOS.ini before and after on the same container, once with settings already in it and once with none at all. Both empty. |
||
|
|
421ca0ac73 |
iOS: clear out the settings code nothing calls
reload() was 218 lines with no callers anywhere in the history, and it had already drifted: two settings were missing from it, so wiring it up would have quietly reverted the frame pacing preset and adaptive resolution on every VM start. frameLimiterDisabledForFastForward went with it, set false twice and never read. The adaptive resolution setter no longer reaches back into init to start its controller. Nothing hangs today, because upscaleMultiplier happens to be loaded further up, but that is luck rather than design and the comment claimed a guard that was not there. applyFramePacingPreset was restating the same 24 numbers that SettingsStore+FramePacing already holds and PerGameSettingsPanel already trusts. It reads them from the table now. Still six explicit assignments rather than a loop, because the order is the point. |
||
|
|
fd4fcfac9d |
iOS: translate the new setting names
CAS Sharpness, the four Shade Boost components and the three CPU sprite render level options were falling back to English everywhere. |
||
|
|
22192ddcd2 |
iOS: give the background art rows the step they print
These stay continuous on purpose. They are live preview controls where every value in the range is a legitimate one, so stops would only get in the way. What they were missing is a step. A row could read 50% while quietly storing 0.4973: the readout rounded, the stored value did not, and the reset arrow was comparing against the hidden one. They snap to the precision they print now. |