Comment only. Whether the monotonic clock counts time spent asleep differs per
platform, and the names invite exactly the wrong assumption: Apple's
CLOCK_MONOTONIC behaves like Linux's CLOCK_BOOTTIME, not like Linux's
CLOCK_MONOTONIC. We now skip suspended time on Linux/Android and Mac/iOS, but
not on Windows, where QPC is documented to include standby and hibernate.
Writing down why that's deliberate, so the Apple branch doesn't get "fixed"
back to CLOCK_MONOTONIC by someone who reads it as the portable spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
No behavior change - recording what was measured so the next person doesn't
have to rediscover it. Darwin's usleep overshoots by ~25% of the requested
interval, mach_wait_until doesn't improve on it, and the fix that does work
costs CPU every frame.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
CLOCK_MONOTONIC is the expensive clock on Apple - it keeps counting while the
system is asleep, so it can't be a plain counter read. CLOCK_UPTIME_RAW is the
raw counter (the clock_gettime man page notes it's identical to
mach_absolute_time() after the timebase conversion), and the _nsec_np variant
returns nanoseconds directly instead of filling in a timespec we then have to
recombine - which is exactly what time_now_raw() wants.
Measured on an M-series Mac, per time_now_d():
clock_gettime(CLOCK_MONOTONIC) 23-31 ns
clock_gettime_nsec_np(UPTIME_RAW) 14-16 ns
mach_absolute_time + double mult 10.3 ns
mach_absolute_time is a little faster still, but needs mach headers, a cached
timebase and a second time origin; this is a one-function change that keeps
time_now_raw()'s nanosecond contract. Available since macOS 10.12 / iOS 10, and
our deployment targets are 10.13 and 11.0.
Timing risk: this clock stops while the system is asleep, where CLOCK_MONOTONIC
kept running. Deltas across a sleep/wake will now be small rather than huge,
which is the better behavior for frame pacing. time_now_unix_utc() still uses
CLOCK_REALTIME, so wall-clock time is unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
It stored a split seconds/nanoseconds pair on POSIX and hand-rolled the borrow
in ElapsedNanos, duplicating what time_now_raw() already does. Just store the
nanosecond value, so there's one clock read per platform to keep correct.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
time_now_d() lazily initialized g_startTime on its first call, which is a data
race between threads, and left from_time_raw() subtracting zero (returning
seconds since boot) if it happened to run before any time_now_d(). Set it in
TimeInit() instead, matching what the Windows path already does with
frequencyMult.
That only works if TimeInit() is actually called, and iOS was the one entry
point that never did - Windows, UWP, SDL, Qt, Android, libretro, headless and
the unit tests all do it as the first thing in main(). Added it there too.
Timing risk: anything calling time_now_d() before TimeInit() now gets seconds
since boot rather than a value near zero. All entry points call TimeInit()
first, so this only bites code running from a static initializer; deltas
between two timestamps are unaffected either way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
The fallback path (anything that isn't Windows/Linux/Mac/iOS/Android - so
Switch, OpenBSD, FreeBSD) was thoroughly broken:
- time_now_raw() computed a double of *seconds* and returned it as a uint64_t,
where every caller expects nanoseconds. from_time_raw() then scaled it by
1/1e9, so time came out roughly 1e9 times too small.
- Instant() seeded itself from gettimeofday (realtime epoch) while
ElapsedNanos() read CLOCK_MONOTONIC (since boot), so every elapsed span was
the difference between two unrelated clocks - decades, in practice.
- On top of that it mixed units, assigning tv_usec to nsecs_ and subtracting it
from ts.tv_nsec.
Since that code already called clock_gettime(CLOCK_MONOTONIC) itself, any
platform reaching it necessarily has POSIX clocks, so just fold those platforms
into the branch that works instead of fixing three bugs in a duplicate
implementation. Also drops the now-unused "micros" constant.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
It's declared in TimeUtil.h and defined for Windows and for the generic
fallback path, but not in the branch that Linux, Mac, iOS and Android actually
compile - so the first caller on any of those platforms would have failed to
link. Nothing calls it today, which is why nobody noticed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
Adds SceKernelLoadExecVSHParam (matching JPCSP's reference layout - its
first four fields line up with the existing SceKernelLoadExecParam,
which is why the plain sceKernelLoadExec already worked for
sceKernelLoadExecVSHMs2) and fills in the rest of LoadExecForKernel's
NIDs from JPCSP: real implementations for sceKernelExitVSHVSH/Kernel
(mirrors sceKernelExitGame) and sceKernelLoadExecBufferVSHUsbWlan (loads
an exec from an in-RAM buffer instead of a file - the VSH's "push a game
over USB/WLAN" path), plus UNIMPL stubs for everything JPCSP itself only
knows by NID.
sceKernelLoadExecBufferVSHUsbWlan needed __KernelLoadExec split into a
file-reading front end and a shared __KernelLoadExecFromPtr back end
that both it and the new buffer-based path call into - a pure
extract-method refactor of the single most heavily used boot path in the
emulator. Verified no regression: same 11 passed / 9 pre-existing-failed
split on pspautotests/tests/cpu/*, and loader/bss still passes, before
and after this change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSNaZnHCjmryS3ziVN9gZU
- AudioEngine.mm/.h: not in the source list at all, only in the ARC
set_source_files_properties list, and written for manual retain/release
([super dealloc]), so it couldn't have compiled under ARC anyway. The actual
audio path is iOSCoreAudio.mm.
- DisplayManager.mm: empty file, referenced nowhere.
- iOSCoreAudioSetDisplayConnected: never called, so g_displayConnected was
always false and the external-display branch in UpdateSession unreachable.
Also dropped an unused local there.
- AppDelegate: the "screen" property was never assigned, so both view
controllers were setting their frame from [nil bounds], i.e. CGRectZero. The
window sizes the root view anyway. Also removed the stale launchPPSSPP:argv:
declaration (it lives on SceneDelegate now), a forward declaration of a
PPSSPPViewController protocol that doesn't exist, and an unreachable return.
- getView: declared on the base class and implemented identically in both
subclasses, called by nobody.
- bindDefaultFBO: moved the empty implementation to the base class so it isn't
missing there, and dropped the Metal no-op override.
- The unused delta/lastTimestamp computation in displayLinkFired.
Also fixed the viewWillAppear/viewWillDisappear log messages in the GL view
controller, which claimed to resume and pause the display link but don't.
The getView and bindDefaultFBO changes clear three -Wincomplete-implementation
warnings, and the AppDelegate.h one clears a fourth.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
updatedTransactions posted success/failure unconditionally, but StoreKit also
delivers transactions we didn't initiate - unfinished ones at startup, for
instance - when no request is pending. Request IDs start at 10, so that just
produced "Unexpected request ID 0" errors. Guard it like the restore-finished
handler already does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
NSStringEncodingConversionAllowLossy is a conversion option, not an encoding.
Its value is 1, which as an NSStringEncoding means NSASCIIStringEncoding, so any
URL with non-ASCII characters produced a nil string and a nil NSURL. Use UTF-8,
and bail out with a log instead of calling openURL with nil.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
viewDidLoad deleted the graphics context on InitAPI failure but left the member
pointing at the freed object, and then kept going - runVulkanRenderLoop and
shutdown would both use it. Null it out instead; both already check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
prefersStatusBarHidden was dead code - it computed an orientation and a
(commented out) user preference, then unconditionally returned false. So the
status bar was only ever hidden on iPhone in landscape, and only because iOS
does that on its own in compact height.
Now it honors bImmersiveMode from the DisplayLayoutConfig matching the current
orientation, so it also applies in portrait and on iPad. Adds the corresponding
checkbox to the iOS system settings, and updates the status bar on rotation and
when the setting is toggled.
Also fixes a missing break in the ROTATE_UPDATED case in System_Notify, and a
static/non-static mismatch on sceKernelLoadModuleBufferUsbWlan that broke the
build (the header intentionally exposes it for sceVshBridge).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
Captures the msys2/make/cl.exe recipe from libretro/README_WINDOWS.txt plus
two gotchas specific to driving it non-interactively (COMSPEC and
ProgramFiles(x86) not being inherited by a sandboxed agent invocation,
breaking the VSWhere.sh-based VS auto-detection) and the VsInstallRoot
override workaround, so this doesn't need to be rediscovered next time.
libretro/libretro_vulkan.cpp got PPSSPP's libretro core working with
RetroArch's Vulkan integration by globally monkey-patching PPSSPP's Vulkan
loader function pointers (vkCreateInstance, vkCreateDevice,
vkCreateSwapchainKHR, vkAcquireNextImageKHR, vkQueuePresentKHR,
vkQueueSubmit, etc.) so the unmodified VulkanContext class would end up
wrapping RetroArch's already-existing VkInstance/VkDevice instead of
creating its own, and so a fake VkSwapchainKHR (a self-managed array of
images synced against RetroArch's retro_hw_render_interface_vulkan
callbacks) could stand in for the real swapchain that libretro's Vulkan
model doesn't have. Flagged in-code as "a wacky wrapper".
Replaces that with first-class support in VulkanContext for the two things
libretro actually needs:
- Adopting an externally-created instance/device instead of faking
vkCreateInstance/vkCreateDevice: VulkanContext::CreateInstanceExternal()
adopts RetroArch's VkInstance; CreateDevice() gained optional
extraDeviceExtensions/extraRequiredFeatures params so RetroArch's
requirements get merged into a real vkCreateDevice() call;
ownsInstance_/ownsDevice_ flags (the latter set via
SetDeviceExternallyOwned()) mean DestroyInstance()/DestroyDevice() skip
the real vkDestroy* calls when something else owns the object, without
needing to intercept anything. VulkanLoader gained
VulkanLoadFromGetInstanceProcAddr() for bootstrapping from a
host-supplied proc-addr getter instead of dlopen/dlsym-ing the loader
ourselves - vkGetDeviceProcAddr is resolved via the real instance handle
(not NULL), since per the Vulkan spec it's not one of the handful of
commands queryable with a NULL instance.
- A pluggable presentation backend (Common/GPU/Vulkan/VulkanPresentation.h)
for hosts with no real VK_KHR_swapchain, replacing the fake-swapchain-
handle trick. VulkanContext::GetPresentation() is null by default, so
every existing platform's real-swapchain code path is untouched;
libretro/LibretroVulkanPresentation implements this interface directly
against retro_hw_render_interface_vulkan, as real class state instead of
file-scope globals. Several pieces of state that are normally only
populated as a side effect of ReinitSurface()/InitSwapchain() - the
graphics queue/queue family index (ChooseQueue() is entangled with
real-surface presentation-support checks), the swapchain format, and
the available present modes - needed presentation-aware fallbacks since
libretro never calls that real-surface path at all.
libretro/LibretroVulkanContext.cpp now drives VulkanContext's real, public
API directly - no more hijacked function pointers, no more fake surface or
swapchain. libretro/libretro_vulkan.cpp is deleted.
Verified with a full build+run in RetroArch (not just compile-time
checks): the libretro Makefile doesn't track header dependencies
(cl.exe doesn't support -MMD/-MP, and Makefile.common never sets up an
equivalent), so a `make clean` full rebuild is required after any header
change to avoid linking stale object code from before the change - several
of the fixes above were initially masked by exactly that.