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
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.
It was removed during https://github.com/hrydgard/ppsspp/pull/21982
On Linux Wayland however this can be observed without:
34:25:550 Vulkan/VulkanContext.cpp:1401 I[G3D]: surfCapabilities_.current: -1x-1
34:25:550 Vulkan/VulkanContext.cpp:1406 I[G3D]: surfCapabilities_.current after clamp: -1x-1 min: 1x1 max: 32768x32768 computed: 1x1 cbdraw
- InitContextFromTrackInfo now rejects files where sampleSize (from
blockAlign) exceeds the buffer size, instead of only clamping later in
DecodeForSas. Keep the DecodeForSas check as defense-in-depth since a
large buffer could still allow a crafted packet to overflow the fixed
assembly buffer.
- CChunkFileReader::Verify now returns ERROR_BROKEN_STATE if bounds
checking fails, so modified savestates are rejected here too.
PointerWrap tracked no end-of-buffer, so DoState() implementations could
read past the end of a crafted or truncated savestate via DoVoid's
unchecked memcpy, and DoVector could resize to an attacker-controlled
size before reading.
- PointerWrap now tracks a read end; DoVoid/ExpectVoid fail (MODE_NOOP)
before reading out of bounds.
- String reads are bounds-checked for the whole string including NUL.
- DoVector rejects sizes that can't fit in the remaining buffer.
- LoadPtr takes the buffer size and sets the read end.
- Capping the decompression buffer allocation in LoadFile.
pngLoadPtr allocated the decoded buffer directly from attacker-controlled
PNG IHDR dimensions with no upper bound, so browsing a crafted game icon
or savedata could trigger a multi-gigabyte allocation.
- Add maxWidth/maxHeight parameters to pngLoadPtr (default 8192x8192)
and reject images larger than the limits.
- Thread the limits through LoadTextureLevelsFromFileData,
CreateTextureFromFileData, and CreateTextureFromFile.
- Limit game icons to 256x128 in GameInfoCache and IconCache.
FixedSizeQueue::DoState serializes the entire fixed backing store. For
the sceAudio channel queues that is 512KB per channel (32768*8 s16
samples), or ~4.6MB of mostly dead bytes in every savestate across the
nine channels - the live sample count at any moment is normally a few
KB. This addresses the existing TODO in DoState.
Add DoStateCompact(), which stores only the live [head, head+count)
region and restores it linearized at the front of storage. A wrapped
live region is written as its two pieces in pop order; since the POD
DoArray path writes raw bytes with no per-element or per-call header,
the single linear read on load consumes them identically. The count is
validated on load and a bad value fails the load cleanly via
p.SetError.
AudioChannel bumps its section to v3 to use the compact form; old
states still load through the unchanged full-storage path. This shrinks
every savestate by several MB uncompressed and cuts the copy/compress
cost of each save, including the rewind feature's periodic states.