- Track up to 4 pad slots in libScePad with per-slot inputs, outputs, sensor fusion, and deadbands
- Support dynamic user login/logout events and multi-user registration in libSceUserService
- Map SDL controllers 0-3 to Players 1-4 in libSceVideoOut while preserving Player 1 keyboard and mouse composition
- Add test coverage for multi-controller pads and multi-user services
sceVideoOutGetOutputStatus derived resolution from the size of the
registered buffers, so a title that reads it right after sceVideoOutOpen
got 1 (1080p) whatever the display. In a payload process on a PS5
(firmware 12.70), with no buffer registered, the field changed with the
console's resolution setting: 2 with it on Automatic on a 4K display, 1
with it set to 1080p. Read it from the display mode SDL reports for the
window's display, next to the refresh rate, and keep it independent of
the registered buffers.
F12 (the new Screenshot input action) requests a capture of the next
presented display buffer. The presenter records it with the existing
APS5_DUMP_FRAMES GPU readback at full resolution, and the frame dump
writer thread encodes it with decoder_png into anyps5-screenshots/
beside the executable, or ANYPS5_SCREENSHOTS.
The controller information reported 1920 by 943, the DualShock 4 height with
the DualSense pixel density, while the host controller's fingers were scaled to
1920 by 942 and the keyboard touch contacts sat on the 942 centre line. A title
that maps touch coordinates through the reported resolution lost the lower part
of the touchpad. The width and height now live beside the pad state and all
three places use them.
When a title has configured 119.88 Hz output (mode 15) and the window moves
to a display below 120 Hz, the presenter fell back to std::terminate via a
require() call. Replace the abort with a graceful fallback: reset outputMode
to mode 1 (59.94 Hz), fire the output-mode event so the title is notified,
and continue presenting.
This matches the HDMI output mode renegotiation a real display change
requires: the system falls back to a compatible mode and notifies the
title, rather than terminating the process.
After the fallback:
- sceVideoOutGetOutputStatus reports refreshRate 3 (59.94 Hz)
- sceVideoOutGetResolutionStatus reports refreshRate 3
- vblanks run at 59.94 Hz (every-other-tick skip resumes)
- sceVideoOutIsOutputSupported(mode=15) returns the unsupported error
- sceVideoOutConfigureOutput(mode=15) succeeds again once the window is
on a 120 Hz display
The console's exact behaviour when a 120 Hz display is lost has not been
measured; this fallback is recorded as unverified in the debt file.
Fixes#7877
Windows.Gaming.Input stays on by default, so racing wheels, arcade and
flight sticks, HID pads without an HIDAPI driver and Xbox One/Series
trigger rumble keep working. Hosts where it faults at
Windows.Gaming.Input.dll+0x10a84 set APS5_NO_WGI to turn it off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
APS5_VIDEOOUT_TELEMETRY_DIR=<directory> appends one JSON object per line to <directory>/videoout.jsonl: a contract line for the run (commit and pid), a pad line whenever the published pad state changes in its buttons, sticks, L2 or R2, a present line after the first present and every 30 after that with the count and rate the window title shows, and a drops line with the number of lines the writer discarded, reported every ten seconds while it grows and once at a normal exit. A bounded queue of 4096 lines and one writer thread batch the file, so neither the present nor the input path waits for it; a pad change that the full queue refused stays pending for a later publish, and the frame count moves from UpdateTitle's function-static to DisplayWindow so the input path can name the present it belongs to.
Without the variable no file is opened and no thread is started: a present or an input update costs one call that checks a guard variable and returns null.
sceVideoOutOpen with bus type 0x20, the PS VR2 output PPSA05666 opens as
SCE_VIDEO_OUT_BUS_TYPE_HMD2_MAIN after sceHmd2ReprojectionEnableVrMode, threw
VIDEO_OUT_ERROR_INVALID_VALUE and aborted the title. The console returns that
code to a process that is not a game, with the headset connected and worn or
not, for users 255 and 0 and for a null or zeroed open parameter, so the bus
is answered that way before the other argument checks and the title carries
on with the error as its handle, as it does on the console without a headset.
The debt entry records the sequence the title follows on that handle
(RegisterBuffers2 with category 16 and 4000x2040 descriptors that point at
Hmd2 per-eye textures, ConfigureOutput and IsOutputSupported with 0x2000F, a
GPU wait on buffer 0), which a VR backend will have to model, and the test
covers the bus and the HMD output modes 0x20001 and 0x2000C to 0x20010, which
the console and this implementation answer with VIDEO_OUT_ERROR_UNKNOWN_OUTPUT_MODE
on the TV bus.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Some titles stop on a prompt to shake the controller, which keyboard players
and controllers without motion sensors cannot answer. While the Shake input is
held (X by default), the composed pad state reports motion with the
acceleration and angular velocity flipping sign every 50 ms; a controller's own
sensor data is used otherwise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The 30 ms limit gated all controller output, so a rumble pulse shorter
than the interval could be lost and a stop could wait up to 30 ms.
Rumble goes out on every output change again, as on main; SDL already
skips repeated values and merges consecutive rumble reports. Only the
light bar is paced: a changed colour is sent at most every 30 ms, always
the latest one, and a colour SDL refuses is retried after the interval.
The trigger effect is cached only when SDL_GameControllerSendEffect
succeeds, so a refused effect is sent again on the next update as it was
before. LightBarPacer and TriggerEffectCache hold these decisions;
pad_output_pacing covers changes, retries, a new controller and a light
bar animated every frame at 120 Hz.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
AudioOut2 turns controller haptic PCM into motor rumble only when no
controller sound card plays it, which is the case for a DualSense over
Bluetooth. PadInput then dropped that rumble for every DualSense, so
titles that vibrate through haptic audio never rumbled one over
Bluetooth. A DualSense now takes it like any other controller; with a
sound card no haptic rumble is submitted, so nothing plays twice.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every change of the pad output state sent a rumble, light bar and trigger
effect update to SDL, and the trigger effect was sent again even when it
had not changed. Titles that animate the light bar or drive rumble from
haptic audio change that state every frame, so at 120 Hz about 110
reports a second were queued while SDL's HIDAPI rumble thread sends at
most one per 10 ms. Different report kinds do not merge in its queue, so
the backlog grew: rumble arrived ever later, and disconnecting the
controller blocked the presenter until the queue drained.
Updates now go out at most every 30 ms with the latest state, and the
trigger effect is sent only when it changes or a controller connects.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceVideoOutSubmitFlip went through queue 0 as a one-packet command
buffer, so every CPU flip waited until the queue worker had translated
all command buffers submitted before it. The PS5 RADV WSI flips from its
own thread once the frame's fence has signaled. vkQuake keeps two frames
in flight, so by then the next frame's command buffer is already queued,
and the flip waited about 10 ms for the worker to translate it. Presents
came in pairs, about 4.7 ms and 29 ms apart. Meanwhile the title waited
in acquire for the flip to be shown, leaving the worker idle for about
7.5 ms per pair.
A CPU flip from a thread that has never submitted queue-0 work now goes
to the presenter at once, unless a command-buffer or EOP flip ahead of it
has not been reached yet. Such a thread can only know that its frame is
done by waiting for it. A flip from a thread that submits its own frames
still waits for the work submitted ahead of it, because a title that
flips without waiting for its frame relies on that order.
APS5_CPU_FLIP_IN_ORDER=1 orders every CPU flip.
vkQuake (PPSA99010) on an RTX 4060: 60 -> 72-74 presents per second,
present-interval standard deviation 12.2 -> 2.1 ms, and intervals over
25 ms 40% -> 1%.
On a Windows 10 host with virtual gamepad buses, SDL's WGI backend
faults at Windows.Gaming.Input.dll+0x10a84 a few seconds into every
run (EVO Player PPSA99039: 26 of 26 runs). XInput and HIDAPI still
cover Xbox and DualSense controllers. Set at default priority, like
DIRECTINPUT, so SDL_JOYSTICK_WGI=1 turns it back on.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A sleep of 1 ms or less returns once the AGC driver sees a GPU batch
complete (a thread waits on the recorder's timeline semaphore), runs a
batch's completion actions, or VideoOut completes a flip. A thread
returns early at most 8 times in a run of back-to-back sleeps; the run
restarts when the thread submits AGC work or did not sleep for the
requested time. APS5_NO_SLEEP_WAKE=1 keeps every sleep in full.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The default bindings map the left and right mouse buttons to Square and
R2, and mouse mode maps motion to the right stick. A title that opens the
mouse with sceMouseOpen therefore gets every click twice: as a mouse
button and as a pad press. PPSA15388 treats the pad press as a switch to
gamepad input, re-centres its cursor and drops the click, so menu items
cannot be clicked.
While libSceMouse has a handle open, KeyboardMouse ignores mouse motion
and mouse-button presses. Releases still pass, so a button held before
the title opened the mouse is let go. Keys and the wheel are unchanged,
as with the IME guard next to it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>