A repeat the core sends as a NULL frame is left unpresented, so the
display receives the content's own frame rate and frame generation or
interpolation tools see the real cadence. It stays out of the way where
the presents are the effect (Black Frame Insertion, shader sub-frames,
Threaded Video Frame Repeat, a headset) and with the menu open,
fast-forward or a cached frame shown again.
While repeats are being skipped, the runloop holds the loop to the
content's period with its timer wherever only VSync or the threaded
hold would have, since neither can block on a frame that is never
presented, and the driver's release stands aside. Lanes in
samples/runloop/pacing and samples/gfx/threaded_video pin both halves.
Idea from beetle-psx-libretro#1013 by Razor221.
Every driver but Android's now publishes its mice at its poll and says
nothing about them from input_state, but the capture of a bind still
asked input_state alone, so no mouse button could be bound with any
input driver or Mouse Index. It asks the published frame first, as
input_driver_device_state() does.
The harness's bind mouse lane had a driver that answers for its mice,
the shape none of the desktop drivers has now; it also runs with mice
published by port and by Mouse Index. Those two fail without this.
"poll stamp: not taken where the poll is" failed with the stamp moving
284 times over 300 frames under normal polling, one short of the 95%
it was held to. The stamp is in microseconds and a null-driver frame
takes well under one, so two polls a frame apart landed on the same
value often enough to cross the margin. run_sited_frames() now waits
for the clock to tick before each frame, so every poll has a
microsecond of its own and the stamp moves once a frame.
To tell a playlist, where the right stick cycles thumbnails, from any
other list, the menu's stick handling built a full menu_entry_t for the
list's first entry - six string buffers, over 4 KiB on the stack, and
every label and value callback run - every frame the right stick was
held, only to read its type. The type is read from the list instead.
input_driver_collect_system_input()'s frame goes from 4304 to 576
bytes, back under the 4 KiB budget.
frontend_overhead_test gains a lane: in a playlist the right stick is
its own bind, elsewhere the D-pad.
* OpenXR: the controllers' slots are not read from the driver's view
A joypad driver that keeps its pads' state lends it to the snapshot
bridge through view(), and the bridge reads that in place of a copy
(48f9503c16). The headset's joypad wrapper is made from the driver it
wraps and kept that driver's view(), so under udev or winraw the
bridge read the controllers' slots from the driver's own pads there,
which are empty: the OpenXR controllers gave no input at all.
The wrapper of a driver that lends now has a view() of its own. It
lends none for the controllers' slots, which the bridge then copies
through the wrapper's button and axis calls, and the driver's for
every other slot.
openxr_input wraps a driver that lends all-zero views, and reads the
controllers' slot and one of the driver's the way the bridge does.
* Menu: bind capture goes to the bound user, not the controller slot
A bind capture knows the user whose binds it sets and the slot of the
controller it listens to, that user's Device Index. Three of its steps
used the user whose number is the slot instead: Bind All set every
bind after the first in that user's binds, each capture took the key
it replaced, whose mapping bit it clears, from that user's bind, and a
mouse bind listened to that user's mouse.
So with user 1's Device Index set by hand to the controller in slot 3,
Bind All for user 1 wrote all but its first bind over user 3's, which
were saved as such, and cleared the bit of a key user 3 still had
bound. A mouse bind for user 1 was never heard with one mouse, and
took user 3's click with a mouse for each user, though at run time
user 1's binds read user 1's mouse. All three now go to the bound
user.
frontend_overhead runs Bind All for user 1 on slot 3, and clicks each
user's mouse while binding user 1's B there.
* samples: hotkey_menu reads its sources as UTF-8
extract.py read input/input_driver.c in the locale's encoding, cp1252
for a native Windows Python, which fails on that file's UTF-8, so the
harness did not build there without PYTHONUTF8=1. It reads and writes
UTF-8 now; on Linux its output is unchanged.
* Input: a core port's rumble reaches every user mapped to it
A core port's rumble now goes to every user mapped to that port, the
users its input is read from, rather than to the user with the port's
number. Its motor answers true when any of theirs does; a port no user
is mapped to rumbles nothing and answers false. With every user on its
own port nothing changes.
* Input: Tap Delay holds Hotkey Enable back from its first frame
The hotkey pass reads the controllers as the last poll left them, and
the frame's own poll comes after it, under early, normal and late
polling alike. So with Hotkey Enable Tap Delay on, each press of the
Enable button reached the core for one frame before the tap saw it
and began holding it back.
While the tap is idle, a core read that finds the Enable's game button
down now asks whether the Enable is held in the frame's poll, and if
so holds the button back. The same button from another controller or
key, with the Enable up, is still the game's. Other reads cost one
test, and with Tap Delay at 0 nothing changes.
* Input: users sharing a core port drive the menu and fire profile hotkeys
With the menu up, user 1 and every user mapped to its core port drive
it. The other users on the hotkey user's core port fire hotkeys from
their own controllers in the one hotkey pass; with Hotkeys Follow
Player 1 those are core port 1's other users. With user 1 on None, the
users on core port 1 do both, and user 1 keeps them. With every user
on its own port nothing changes.
A sharing controller fires the hotkeys its profile binds, never the
hand-set ones, which stay the hotkey user's pad's: a hand-set button
names a button of one controller, and on a pad of another model it is
some other button. Its Hotkey Enable is its profile's too. While the
hotkey user has a Hotkey Enable, a sharer's hotkeys need the sharer's
own or the keyboard's, and a sharer holding its own opens only its own
hotkeys, not the hotkey user's; held on any of them, it holds game
input back. With none for the hotkey user, sharers fire freely. The
table of a sharer's profile hotkeys is made when a bind or its profile
changes, not each frame.
With Hotkey Enable Tap Delay each controller taps on its own: a tap
holds back and replays that controller's own Enable button. A hotkey
swallows the tap of the controller that fired it, and the keyboard's
tap when the keyboard's Enable opened it. Any controller's Enable held
short of the delay holds game input back, another controller's Enable
held past it included. Enable presses released while the delay is 0
are not played once it is turned on, user 1's included.
* Input: Plays As, a controller's own player, places it on connect
Sixteen entries, input_plays_as_device_N and input_plays_as_player_N,
name controllers as a reservation does, vid:pid then name, each with a
player (0 Auto). When they are read, each connected controller, lowest
slot first, takes the first entry naming it that no other has taken; a
0000:0000 vid:pid matches by the name after it, not every controller
without one. Giving a controller a player makes an entry for it,
refused when all sixteen are in use; set back to Auto, its entry is
freed.
The entries are read with the binds and written with the input
entries, a free entry's keys taken out. Two of a model are told apart
only by order, so a model's entries are written at their own places
in the order a start hands them out: lowest slot first, a gone one's
in its place, and one on Auto ahead of another of its model as an
Auto entry of its own. Once the main configuration is saved, the
entries in memory are laid out the same way, so reading it again in
the session gives each its own too. Saving an override puts them back
after its comparison load, as it does the binds. The reservations'
vid:pid parse is shared with them, unchanged.
On connect, a controller set to player N takes port N. Whoever else is
there moves to the first free port, unless it also plays as N, by its
own player or, on Auto, by N's reservation naming it: then the
newcomer shares, from the highest free port below Max Users. An Auto
controller that N's reservation names shares the same way when port N
holds a controller set to N. An explicit player beats a reservation
naming the controller. With any reservation set, Auto controllers are
otherwise left to the reservations; with none, and a player set by an
entry, they take the first free port. With no player set nothing
changes.
The moves are for this run only, through input_driver_exchange_ports(),
now in every build: the configuration keeps the ports the user set. A
reservation's own placement made while the ports are in a runtime
layout is runtime as well, so it is not saved either; reservations
place again at every connect.
A joypad driver restart lets go of every claim, since its pads can come
back in other slots with no disconnect: a pad reported again where it
is plugged in takes back its entry, the rest take theirs in slot order.
While a player is in effect, the registry's restore of the ports is
left to placement. A config reload keeps each controller's entry while
it still names it, so two of a model keep theirs. The entries count
among the settings whose change starts the joypad driver again at a
video restart, and so does a config reload that undid runtime moves.
* Input: a controller playing as another's player shares its core port
A user whose controller plays as another user's player feeds that
user's core port through the port map: that user's Mapped Port, from
remap files or identity. Its own Mapped Port is never changed or
saved. Everything that reads a user's core port reads this effective
port, and the Quick Menu's Mapped Port row reads "Shares Player 1 (core
port 1)" for a sharer.
An Auto controller placed to share with the player a reservation names
keeps sharing while it stays connected, also once that player's
controller leaves. The poll rebuilds the map when who shares changes,
and the core is told its controllers again between frames, not while
netplay or a replay owns the ports. Under First Press a press on any
of a player's controllers gives that player's user its port.
* Menu: a controller's player in Input > Controllers and Port N Controls
Settings > Input > Controllers, after RetroPad Binds, has a row per
connected controller in port order, then those past Max Users, then
the Plays As entries of controllers that are gone. A row's value is
the controller's player or, on Auto with a port, "Auto (1)", the player
Auto gives it. The sublabel names the player first and a controller
port only by its page name: "Player 1", "Player 1, shared · binds in
Port 3 Controls" for a controller sharing a player from another port,
"Not playing: more controllers than Max Users", or "Not connected".
On a Controllers row, Left and Right step through Auto, 1 to Max Users;
OK opens them as a list. RetroPad Binds > Port N Controls starts with a
row for each connected controller playing as the player port N feeds,
the same setting, then Add Controller: the other connected controllers
with a port, each with the player it plays as now, where a pick moves
that controller to this player. One controller on several players stays
Device Index's.
A change to a connected controller marks the configuration changed and
places it again as its connect did, with any controller it pushes off a
port, and no driver restarted. The entry point is in task_autodetect.c,
beside the reservation placement it reuses. A controller whose change
left no player in effect goes to the first free port. While Plays As is
in use, a change of Max Users places again the controllers left with no
port and those whose player comes within it. With every entry taken a
new one is refused with a notice.
A controller set to a player above Max Users reads "Above Max Users:
plays as Auto (Player 2)". On Port N Controls a row's sublabel reads
"This port" or "Shared · binds in Port 3 Controls". Device Index's note
for a controller another port reads becomes "used by Port 2".
After Add Controller, Port N Controls links to Port M Controls for each
controller playing as player N from port M, such as "OpenXR Controllers
(Port 4 Controls)": that controller's binds are port M's. Quick Menu >
Controls > Port N Controls starts with the same links to the pages that
hold its remaps. Both lists and the Quick Menu's Port N Controls are
made again on hotplug, and Port N Controls after a Device Index change,
with the cursor kept on its row.
The menu lanes are in stereo_headset_test, which already links the
shipping menu objects and runs in CI's OpenXR jobs; a build without
OpenXR skips it.
* Input: a controller's own hotkey buttons
A Plays As entry keeps a button and an axis of its own for any hotkey,
input_plays_as_<hotkey>_btn_N and _axis_N, written only where set; an
entry that has one is kept on Auto. It counts neither as a player nor
among the settings whose change starts the joypad driver again. A
sharer reads its own first, then its profile's, never the set's; the
hotkey port's own controller reads its own, then the set's, then its
profile's. Button and axis go each on their own.
The bind values, "8", "h0up", "+1", are read and written by helpers in
input_driver.c that the configuration's own binds now use too.
* Input: Hotkey Enable pairs with the controller it is on
A sharing controller's hotkeys go by its own Hotkey Enable, its bind or
its profile's, whatever the hotkey user has: one with an Enable fires
while it holds it or the keyboard's, one with none fires freely. With
Hotkey Device Type Merge on and a keyboard Hotkey Enable bound, a
sharer with none waits for the keyboard's, as master makes user 1's pad
wait. Holding a sharer's Enable holds game input back as any Hotkey
Enable does, and opens only that sharer's hotkeys.
Keyboard and mouse hotkeys keep master's Hotkey Enable rule whatever
sharers are connected: a sharer's Enable does not open them. Nor does
it open an input_combo_N entry with a key; an entry of RetroPad buttons
alone opens with it, as those buttons are every port's. A sharer with
an Enable of its own needs it for the menu toggle too, and whether a
pending menu tap is kept is read from the sharer's own binds, not the
set's.
The hotkey user's pad Enable plays no part in a sharer's hotkeys: each
controller's hotkeys are its own, and so is its Enable.
With Hotkey Enable Tap Delay, the hotkey of a sharer with no Enable of
its own leaves the keyboard's tap alone, as the keyboard's Enable did
not open it. Under Hotkey Device Type Merge, where such a sharer waits
for the keyboard's Enable, its hotkey swallows the keyboard's tap.
* Menu: hotkeys bound from a sharing controller are its own
Binding a hotkey also listens to the controllers that fire the hotkeys,
those on the hotkey user's core port, and its prompt gains "Listening
to Player 1's controllers", for the player the hotkey user plays as. A
press on the keyboard, the mouse, user 1's pad or the hotkey user's
goes to the set as before; a sharing controller's press is kept as that
controller's own, with an entry made for it at its player, refused with
the full notice when none is free. A press from a controller that has
its own bind for the hotkey replaces that bind, user 1's pad included.
Any bind, RetroPad binds too, also hears the port's own controller when
it is in a slot at or past Max Users, where Plays As placement can put
it, so that port's binds can be set from it.
A hotkey's row shows each connected controller's own after the set's,
"Select · OpenXR Controllers: L3", and clearing it clears theirs too.
* OpenXR: the headset shares player 1 on Auto and moves no one
For the headset's controllers, Combined or each hand in Separate, Auto
means sharing player 1, so a VR player's pad and headset both drive
player 1 with no setup. They take the highest free slot and the highest
free port below Max Users, so pads keep the low ones, and share their
player whether anyone is on its port or not, moving no one; a pad
turned on later lands on port 1 and shares with them. Set to a number,
they do the same with that player. Every other controller's Auto takes
the first free port.
A reservation naming the headset beats its Auto, as for pads, and a
number beats a reservation. A pad set to the player whose port a
headset on Auto holds takes that port, and the headset moves to the
highest free port, or gets none. Any change of Max Users places the
headset again at once, on the highest free port. Wherever Plays As asks
whether a player is in effect, a connected headset on Auto counts as
player 1, as one set to 1 does.
OpenXR looks up each controller's player only when the devices or the
entries change, and places a controller again when it starts or stops
playing as a player, or when a slot below Max Users frees up for one
placed past it. A player's controller that a pad pushed out of its slot
is placed by its player rather than taking the port with it. A headset
entry names the controller by the name OpenXR looks it up by.
The laser answers the core once, for the core port the headset's
controllers play on, so it no longer mixes with another user's mouse;
in Separate each hand's player gets it, the pointing hand aiming and
firing and the other reading offscreen. A headset controller with no
player drives no pointer, and the Monado e2e cases follow the new
placement.
mapped_port_test is built a second time with OpenXR, as
mapped_port_openxr_test, and CI runs both.
* Menu: OpenXR Controller Layout under Input > Controllers
Combined or Separate is how the headset's controllers appear, so it
moves from the headset's video rows to the bottom of
Settings > Input > Controllers, named for what it sets. The key and its
values are unchanged. Switching it reconnects the headset's
controllers, and the cursor stays on the row.
* Input: Plays As in core, content directory and game overrides
Save Override now saves the controllers' own players and hotkeys. Each
controller whose player or own hotkeys differ from the main
configuration's (with --appendconfig) has its whole entry written, all
of its entries for two of a model, in the order a start hands them
out, one on Auto ahead of another of its model as an Auto entry; one
the main configuration gives an entry and the session none is written
as Auto. An entry the same as the main configuration's is not written,
so a controller set back drops out of the override.
An override's entries are read on their own and go over the ones read
before by controller, as entries are matched to controllers (vid:pid,
else name), not by number: the last file naming a controller gives it
its whole entry, in the place of the one it had, two of a model taking
a file's entries in order, an Auto one included, the connected ones
lowest slot first; a controller no override names keeps the main
configuration's. Auto takes one back to Auto, and a controller new to
the entries takes a free one, if any of the sixteen is. Until now the
files' keys merged one by one, so an entry could pair one file's
controller with another's player, and no override could take an entry
back to Auto.
Loading or unloading an override from the menu, or loading the active
ones again, places the controllers again at once, as a Player change
does: those whose player changed, or all of them where the reload read
the configured ports back over the moves. Content load and close leave
it to the joypad driver's restart, as before.
mapped_port_test covers the merge, what is saved and the placing
again; video_screens_test saves, unloads and loads a game override
with a Plays As change.
* Input: a controller keeps its Plays As entry while its twin is unplugged
Two controllers of one model share the entries that name them, each
taking the first one no other connected controller holds. With one of
the two unplugged, anything that made the other take an entry again
could hand it the unplugged one's: a Player or Max Users change,
loading or unloading an override, any re-read of the main config, a
joypad driver restart, and Online Updater > Update Controller
Profiles, which reports every connected controller again. The
controller left then played the other one's player, and that one came
back to its twin's.
An unplugged controller's entry now stays its own for the rest of the
session, and only a connect takes it: that controller coming back, or
another of its model. A reload keeps it while the entry in its place
is still of its model, and an override's entries pair with it as if
the unplugged controller still held it from its slot. So an override
loaded with a twin unplugged gives the other twin the entry it adds
and leaves the unplugged one its own. Save Configuration and Save
Override count it as that controller's too, so what they write pairs
the same way when it is loaded again.
Placed again for a Player or Max Users change, or by Update Controller
Profiles, a controller keeps the entry it holds, or none. Reported
again by its driver while still connected, it keeps the one it holds.
A reload is judged as a whole once its last override is read: a
controller takes an entry it did not hold before only where the
reload added it for its model, so one an override newly names still
gets that entry.
A joypad driver restart still lets every claim go and hands the
entries out again in the order the controllers are reported, as a
start does, but its reports leave an unplugged controller's entry
alone. A driver's report queued behind a profile update's is not
dropped as a repeat of it, since the update takes no entry, and the
update it supersedes shows no toast, so one shows, as before.
* OpenXR e2e: FRAME_BINDS catches up with the grip poses already bound
Every profile binds pointer/grip to both hands' grip poses, and
input-frame-profile matches FRAME_BINDS exactly, so the table now lists
the poses as they are bound.
* Menu: OpenXR touch settings on Input > Controllers
Touch Hands, Touch Button and Touch Passthrough are added for the hands
that touch a screen they point at, the button that touches, and whether
that button's other binds fire too, with Touch Hands None to turn touch
off for the headset's controllers; a later commit puts them to use.
They follow OpenXR Controller Layout, Laser Pointer and Laser Beam,
which move here from Stereo 3D & Screens, at the end of Settings >
Input > Controllers, shown only for a video driver with a headset path.
A hotplug keeps the cursor on any of these rows, and overrides save
them per game.
* OpenXR: the headset's menu button is button 16, bound to Menu Toggle
The menu action of each profile presses button 16 of the pad, the left
hand's in Separate, instead of being a hotkey of its own, and the
built-in profiles bind Menu Toggle to it and label it "Menu", as pad
profiles name their Guide or Home button. It opens the menu from the
same button as before and can be bound elsewhere like a pad's Guide
button. Touch's one menu button presses button 16 once held a second.
In Edit Screens the menu button always opens the menu, whatever is
bound and whether or not Hotkey Enable is held.
Sharing player 1, the button goes by Hotkey Enable as a sharer's Guide
does where its profile has none: it opens the menu whether or not
player 1's Hotkey Enable is held, and the enabler alone opens nothing.
mapped_port_test covers both.
With the menu open, a press of the controllers' buttons hides the
laser's dots until a trigger is pulled, so hand drift cannot undo their
navigation. The menu button navigates nothing, and with no content Menu
Toggle leaves the menu open, so its press does not count.
* OpenXR: the Touch Button's buttons as pointer actions of each hand
Grip, stick click, bumper and A to Y each have an action per hand in the
laser's action set, bound from a table per profile in the same
suggestion as the pad's rows, so either controller layout can read them;
the trigger is the pad's own. The Frame's exact binding list in the e2e
gains them.
A runtime that refuses a suggestion loses its palm first, then these
rows, then edit mode's, a smaller loss tried before a larger, and keeps
the rest of the profile. INPUT_OPENXR_MAX_BINDS goes from 48 to 64 to
hold these rows with edit mode's (Index 52, Touch 51, the Frame 58); a
profile whose pad and Touch Button rows pass it is not suggested at
all, and the log names it.
* OpenXR: touch with the chosen button and hands
The Touch Button, read from its own action or, for the trigger, from the
pad's, touches a live screen the hand points at; the trigger still
clicks the menu, and is L2 or R2 on a screen once it is not the Touch
Button. A hand touches only if Touch Hands has it and its controller has
the button, as read from each hand's interaction profile; only such
hands aim at the screens, become the pointer or show a dot there, and
with none the screens stay the mouse's. The others aim at the menu
alone, which video_xr_pick() now takes as a flag. Touch Passthrough lets
the trigger's L2 or R2 through its touch.
A hand takes a changed setting at its next press. A button held through
a change of Touch Button, Touch Hands or the hand's controller profile
is no press: the trigger goes by its own last read, and any other button
counts as held until it is let go.
* OpenXR: a touch holds back its button's other binds
OpenXR gives an input to every action bound to it, so a Touch Button
other than the trigger also reaches the pad. While it touches, or a
press of it would, the pad's buttons bound to the same input of that
hand in the profile's rows are let go, in Combined and in Separate: the
Index grip's L or R, the Frame right stick's menu button. Touch
Passthrough leaves them pressed.
With the menu open, a press of the controllers takes the laser from the
dots until a trigger pull. Like the triggers' L2 and R2, what a Touch
Button's input feeds does not count as such a press, with Touch
Passthrough on or off, so the button can still touch a live screen
there. Another button on the same hand still counts.
* Menu: Touch Button offers the buttons the controllers have
The dropdown lists the buttons either hand's controller has, as its
interaction profile names them, and the one chosen; left and right step
through the same, skipping the rest. A choice the controllers lack, as
from an override made on another controller, reads "(not on this
controller)". With no controller known every button is offered. The poll
publishes those buttons through an atomic, since with threaded video XMB
draws the value on the video thread.
* docs: OpenXR controllers, touching and the menu button
Where the headset's input rows are now, what Touch Hands, Touch Button
and Touch Passthrough do, None included, and that the menu button is a
Menu Toggle bind that Hotkey Enable applies to as to a pad's Guide; on
Touch controllers in Combined it is Start, and button 16 once held a
second. Laser Pointer's description says the Touch Button touches a
screen and the trigger clicks the menu, true with Touch Passthrough on
too.
The null input driver had no keys_down, so the frontend took the slow
fallback for it: every frame it asked for every hotkey and every
user's turbo and hold bind one at a time, since it could not know that
none of them can be pressed. With keys_down saying no key is down, the
hotkey pass and the poll skip those reads, as they do for the drivers
that give their keys.
Callgrind over the frontend harness's input-cost lane, which runs on
this driver: 40% fewer instructions in all, 83% in the hotkey pass and
50% in input_driver_poll. Every lane gives the same results.
The harness mocks that copy the null driver to answer keys through
input_state now clear keys_down, so they still test that kind of
driver.
verbosity.c mirrors each log line into the desktop companion under
HAVE_COMPANION_WIMP, which ui_companion_driver.h turns on for every
desktop Windows build, so each sample that links verbosity.c failed
to link there: undefined ui_companion_driver_log_active and
ui_companion_driver_log_msg. samples/ui_companion_log_stub.c gives
the six of them a companion that is never open.
The database sample's main.c used strlcpy() without <compat/strl.h>,
an error with GCC 14 and later.
With Driver Pacing on and nothing else holding the loop, the backend's
release keeps the content's frame period. On a driver without a backend
- gl, glcore, or a GPU without the extension - the setting did nothing,
and with vsync and audio sync off the loop ran unthrottled. The frame
limiter now holds the content's period in exactly the iterations a
backend would have owned: a content frame at normal speed, focused, the
menu down, and nothing else pacing. Driver Pacing off is unchanged.
The widget and autosave harnesses become samples of their own -
samples/gfx/widget_snapshot_ownership, samples/gfx/widget_task_lifetime
and samples/runloop/autosave_snapshot - each built and run by make
check like the rest, the task lifetime test with the widget defines its
stubs build against. The autoconf suite, its .ratst scripts and test
input configs move to samples/input/test_input; the workflows that ran
them follow. The SPIRV-Cross MSL binding testbench, which nothing built
or ran, goes.
Use null-driver callbacks for the synthetic OpenGL restart and restore the cached video selection before leaving the test. Assert that the null driver is restored so later lanes cannot inherit a forced GPU backend.
Line-buffer harness output so a crash does not hide the last completed lane.
Keep the Game Mode explanation under 509 characters so the MSVC6 stand-in compiles with pedantic errors enabled. Align the registry harness deadzone refresh with its loop scope.
A movable desc's geometry went to the video driver every poll, moved or
not. On D3D11 each call marks the overlay's vertex buffer dirty, so it
was mapped and rewritten every frame; under threaded video each was a
packet through the ring. The last delta sent is kept per desc and the
geometry is sent only when it differs - 23 times across an 8 second
on-screen run where it was about 500 - and reset wherever the whole
page's geometry is set.
The poll copied the last state and cleared the new one, about 150 bytes
each, every poll: the overlay now keeps both and flips between them
(INPUT_OVERLAY_ST()), clearing only the new one's keys, sticks and
buttons. The state each touch is tested into is cleared only there too,
its touches being unused.
Each finger was read with three calls into the pointer frame; the frame
store answers down-or-not and where in one look
(input_pointer_frame_finger), with the driver asked as before when the
frontend does not hold the pointer.
Where Hotkey Enable is also bound to a game button (Select, most
often), the game saw it for the first frames of every hotkey use.
With "Hotkey Enable Tap Delay" set, the game buttons sharing the
enabler's button or key are kept from the core until it is known
whether a hotkey follows:
- let go with no hotkey used, the press is given to the core then,
for as many frames as it was held (two at least);
- held past the delay (real milliseconds) with no hotkey, it is the
game's from then on, along with the other buttons;
- used with a hotkey, the core never sees it, or stops seeing it the
moment the hotkey fires.
Off by default; when off nothing runs. The core's read tests it with
the core hold's existing branch. A frontend harness lane covers taps,
holds and hotkeys.
A DualShock 4's or DualSense's touchpad clicks as one button, where a
PlayStation game tells its left half from its right, often as Select
and Start (#19369). Controller Touchpad has a fourth setting, "Halves as
Buttons": pressed in, the pad presses the RetroPad button set for the
half its finger is on - "Touchpad Left Half" and "Touchpad Right Half",
Select and Start by default - and both with a finger on each.
It needs no new button numbers, so no profile or core changes, and
works for every driver that reads a touchpad (hid, sdl2, sdl3, udev).
Once a poll, where macros and remapped buttons are pressed, the click
and the fingers are read where the driver keeps them; nothing is
copied that was not before, and with no port in the mode a frame has
nothing more to do. The frontend harness's touchpad lane holds it to
the halves, both at once, no click, a button set by hand, and nothing
in another mode.
A macro was not started with netplay on: hosting with nobody connected,
one that was started never reached the core. It does now. A macro's
steps go in where a remapped button does, and netplay takes the local
player's input from there, as it is given to the core; so the host's
or a client's macro is sent and replayed like buttons pressed by hand,
for every player alike.
The frontend harness's macro lane hosts netplay and holds the core to
being given the macro as it is written, frame for frame; with the
macro kept from the core under netplay it fails.
Defer recording until the first core frame after pending startup state loading. This lets the core publish its resulting AV information before the output is opened, preserving the requested filename instead of creating a header-only file and restarting into a dated recording. Cancel pending startup on stop, content close and quit. Exercise the actual startup functions and retain the auto-state-load harness link.
A core reads the touchpad of a port's controller with the pointer's
IDs: X/Y across the surface, PRESSED and COUNT, the index the touchpad
times 16 plus the finger down. It is not the screen, so a core can
read both - a handheld's front touchscreen and rear touchpad, say
(issue #19902).
The frontend answers it from the pad's state as the HID handlers lend
it, and the SDL3 driver hands its touchpads over through a new
get_touchpad op, taken once a poll. The capability bit is set where the
joypad driver can say. A core that reads the device has the touchpad
to itself: the port's Controller Touchpad mapping steps aside until the
core is unloaded.
A per-port "Controller Touchpad" setting says what the touchpad on the
port's pad is to its core, where the pad has one:
- Touchscreen, the default: each finger on the pad is a touch on the
pointer device at that place on the screen, two fingers two touches;
while no finger is on the pad, the screen's own touches are read.
- Mouse: moving a finger moves the port's mouse, added to the mouse's
own moves, and pressing the pad in clicks - a right click with two
fingers on it.
- Off.
The pad's state is read through the joypad driver's view, as the poll
left it, so the touch is as new as the buttons and nothing is copied.
A mouse's moves are worked out once a poll, at the first ask.
The DualShock 3, DualShock 4, DualSense and both Switch Pro handlers,
and the generic pads read from their report descriptors, now keep
their state as an input_pad_view_t, decoded straight into it and read
by their getters. hidraw and winusb hand reports to the handlers only
in their poll, so the state does not change between polls and they
lend it through a new hid_driver_t view(), as udev and winraw lend
theirs: the bridge reads it in place, with nothing copied.
The beside layer passes view() through, so a WinUSB pad beside
winraw is lent, and winraw's own pads stay lent while it is there
instead of falling back to copies. A generic pad's hat is part of its
view, which the drivers' hat reads now use.
connect_parity holds each converted handler's view to its getters
after every report, and the DualShock 3's axes to the copying
handler's; hidraw_hid, winusb_hid and the frontend harness's beside
lane check the lend.
The snapshot bridge copied each pad out of its joypad driver at the
first read after a poll: the buttons, then each axis and hat a reader
had asked for, a call into the driver for each. A driver whose state
changes only in its own poll needs no copy: what it holds is the same
until the next one.
Such a driver now lends it. The pad's state - buttons, the first
sixteen axes as the reads answer them, four hats' directions - is kept
by the driver in one layout, input_pad_view_t, as its events and
reports arrive, and a new view() hands the bridge a pointer to it once
a poll; the readers read it in place. udev keeps it with each event;
winraw keeps its pads in it outright, and lends them where its reports
are read in the poll. Other drivers are copied as before, and the copy
now walks only the axes and hats in it, not all twenty: under
callgrind that halved what taking it costs.
frontend_overhead's core-view lane runs its scenarios a third time
with the pad lent, and must give the same digests while asking the
driver for nothing but the view; joypad_bridge_checked holds udev's
lent state to its reads, winraw_joypad_newest winraw's.
With Driver Pacing in effect and nothing else holding the loop - vsync
and audio sync off, no rate control, Threaded Video Display Pacing off -
the minimum interval handed to the driver was 0, and the driver's
release, which never sleeps for a rate it was not given, let the core run
unthrottled. The interval is now the content's period there too, as it
already was under Display Pacing; the timer's period still goes where the
timer would hold, and nothing where vsync, audio or a scanline already
hold the loop. Fast-forward never reaches the driver's release.
The choice moves into runloop.h as runloop_pace_driver_interval(), and
samples/runloop/pacing checks it against the whole pace decision (the
"driver alone" row fails on the old expression).
An input driver that holds nothing of the video driver's is kept
running across a video restart and a content load, and its joypad
driver with it - but the raw input driver kept itself only with its
own joypad driver, so with the hid one every content load closed every
pad and opened it again, a DualSense's lights set afresh.
The joypad driver now answers for itself (survives_video in
rarch_joypad_driver), as the input driver does: raw input's when it is
read by the poll, as before, and the hid one always - its pads are
read on its own threads or at the poll, and nothing of it is the video
driver's. The input driver is kept when both can stay. Raw input's
answer is now its own alone.
The frontend harness's kept-input lane has an input driver that can
stay with a joypad driver that cannot: both are restarted, together.
Setting two ports to the same device index lets one pad act as two:
it is how GoldenEye and Perfect Dark get their two-stick control
styles with one controller, and how twin-stick games that read two
ports are played (#19875). Since July that no longer worked. The menu
swapped the other port away when one was stepped onto its pad - with
"All Users Control Menu" off this could leave port 1, the only port
that drives the menu, without one - and the mapping was "repaired" to
one pad per port when it was read, on every hotplug and after a
driver restart.
A repeated index is now the user's choice and is kept everywhere:
- The menu changes only the port being edited. A port on another
port's pad says so ("shared with Port 2"), so a share made by
accident is seen rather than silently undone.
- Reading the configuration puts right only an index out of range.
- A hotplug and a driver restart move the lowest port on each pad, as
before, and the ports sharing its pad follow it. A pad that comes
back in a slot no port reads is given to its port without
displacing anyone.
Nothing runs per frame. The frontend harness has a lane for it: an
override's 0 / 0 survives reading and saving, a menu step moves one
port, a reserved pad arriving and a driver restart reporting the pad
elsewhere keep the pair together. With the old menu swap put back the
lane fails. device_registry_test has the unread-slot case.
GLES requests are checked against one ceiling: the device's own
ro.opengles.version on Android, the build's GLES level elsewhere or
when the property cannot be read. The regular Android flavors now
grant OPENGLES_VERSION 3.1 and 3.2 on devices that have them, so a
core probing down from 3.2 stops at the version the context really
gives, and a device below a requested version still refuses it and
lets the core fall back. GLES2 builds take 3.0 as their ceiling, the
level they already grant through OPENGLES3.
samples/runloop/hw_context_request drives the request function under
a GLES2 build with the property stubbed, a GLES3.2 build and a GLES3
build.
Refs #19894
R4 asked whether input held from a command (SET_INPUT, a RUN_FRAMES
press) should go through SOCD cleaning and D-Pad rotation, and what a
hold means under netplay.
It stays exact: SOCD cleaning and rotation are for a person's hands on
a pad, and a script says what the game is to see - a TAS-style left
and right together, or up with the picture turned, means just that.
Under netplay a hold is the player's on this machine: netplay samples
the local device through the same reads, so what is held is sent like
a press and every peer runs the same frames.
That is now written where it is asked - SET_INPUT's description, which
the command list and the MCP tools show, and the handler - and the
frontend harness holds left and right with SOCD set to neutral and the
D-Pad turned, and checks the core is given both, unturned.
The shader backends read gyro, accelerometer and rest each frame from
the video thread. They were published under a seqlock, whose reader
started over - spinning - while the poll was mid-publication, and
whose nine floats went through nine atomic stores and loads.
They are now three slots of nine floats: the poll writes its own and
hands it over with one exchange for the middle slot; the reader, when
the middle is new, exchanges its slot for it and reads the floats
where they lie. Neither side ever waits or retries, and a read never
mixes two polls. The exchange lives in input_sensor_snap.h so a test
can drive the same code.
sensor_snap_test publishes as fast as it can from one thread while
another reads, and checks no read is mixed or goes backwards and the
newest is read once the writer stops; CI runs it under ASan, TSan and
Wine. The frontend harness's idle lane counts publications instead of
the sequence.
When a video restart freed the input driver, a key held at that
moment never got its release: the driver that would have reported it
was gone. The core's keyboard callback and the menu kept the key held
until it was pressed and let go again; polled keyboard state was not
affected.
The keys given out down are already kept for the key lane (and now
also without threads). When the input driver is freed with the video
driver, or a kept driver is dropped, whatever was still on its way is
taken first and every key still down is given out as released. A
driver kept across the restart is left alone; it reports its own
releases.
A frontend harness lane presses a key, restarts the video and input
drivers, and checks the core is given the release once.
In exclusive fullscreen, changing the refresh rate makes the Windows
Vulkan context restart the video and input drivers from its
check_window. With threaded video that runs on the video thread, in
the driver's alive(), so the input driver could be torn down while
the main thread was polling it; without threaded video it ran inside
the video driver's own call.
The restart is now asked for with command_event_later(), which any
thread may call: the command is carried out on the main thread when
it next retires finished tasks, a frame later.
A frontend harness lane asks for a command from another thread and
checks it is done on the main thread, not on the thread that asked.
A combination can now wait until its buttons are pushed in far
enough, chosen from 10% to 100%, so that analog triggers and
pressure-sensitive buttons (DualShock 2 and 3) only set off a hotkey,
command or macro when pressed hard. Response Time gains Press Depth
beside Instant and Hold For; a depth and a hold time can be combined,
and Instant clears both.
The depth is read as the core reads the button, through the trigger
deadzone and sensitivity. A button with no pressure reading, or one
held from the keyboard, counts as pushed all the way. It is written
in the configuration as, for example:
input_combo_4 = "l2+r2 pressed 80% : macro_1"
Only entries with a depth do the extra reads.
A DualShock 3 bound to WinUSB was only seen with the hid joypad
driver selected. It now works beside whichever joypad driver is
active (xinput, dinput, sdl2...), so it can be used together with
an Xbox pad.
The WinUSB source is started on the first poll of any joypad driver
other than hid. Its pads take device numbers from the shared device
pool, from the top down, and while it has any the joypad driver is
reached through a small stand-in that sends reads for those numbers
to the source and every other number to the driver. With no such
pad the driver is reached directly, so nothing changes per frame for
anyone without one. If the driver's own pad arrives on a number a
WinUSB pad holds, the WinUSB pad moves to another number.
With hid as the joypad driver, WinUSB pads keep their slot numbers
as before.
The limit is the frame rate times the fast-forward ratio, and while the
core runs nothing else recomputes it: a SET_SYSTEM_AV_INFO with a new
frame rate sets it from that rate.
A SET_SYSTEM_AV_INFO that keeps video - a new frame rate, base size or
sample rate - goes to the running audio driver in place: the rate is
recomputed with the frame rate's timing, and the threaded pipeline's
ring and slice, sized in video frames, follow a frame rate change
under the consumer's park. A full reinit remains the fallback for what
the in-place path cannot carry.
A driver reinit that included audio puts back what the audio reinit
restores after its teardown directly, instead of a second audio
reinit for the menu sounds; a reinit that did not run asks for none.
Threaded video's Display Pacing and Driver Pacing each decide when the
core's next frame starts, so with both on Driver Pacing stood down -
its status read "off: menu, pause, fast-forward or unfocused" - and
the presenter's hold, sleeping to its own estimate of core and render
time, kept the release.
The driver's sleep is now the hold's release: with both on and Driver
Pacing in effect, the runloop sleeps on the driver as it does without
Display Pacing, and the hold in the wrapper's frame handover stands
aside for a frame the driver released (one whose frame info carries a
release identity) instead of sleeping a second time. The cadence the
hold kept - the content's period, which on a display faster than the
content is not the display's - goes to the driver as the minimum
interval it is to keep. A release the driver could not give leaves the
frame to the hold as before, and where Driver Pacing stands aside (the
menu, pause, fast-forward) the hold holds. Scanline Sync still keeps
Driver Pacing out.
Off by default with Driver Pacing; with Display Pacing alone nothing
changes. The two settings' descriptions say how they combine.
The pacing table's display-pacing row now has the driver beside the
hold, and a new row keeps the menu to the hold alone. The threaded
harness lane checks, under Display Pacing, that every frame is
released by the driver, that the hold stood aside for them, that the
content's period reached the driver with the frame, that a failed
release leaves the hold to hold, and that turning Display Pacing off
leaves the driver alone with its own interval.
The presenter now owns the backend's frame side and the frame loop
only waits: each content frame carries its identity and the minimum
present interval in video_frame_info_t, the presenter marks, presents
and issues the one sleep after the frame's own present, and the frame
loop waits on the timeline semaphore for the identity it last handed
over before it samples the next frame's input. That is the same shape
on the direct path and under the threaded wrapper, which forwards the
three pokes to the driver unchanged, so Driver Pacing is no longer
turned off by Threaded Video; only Threaded Video Display Pacing, the
presenter's own hold, keeps the release to itself.
Any present after a frame's own - a repeat, a dark frame, a shader
sub-frame, a swap interval the driver emulates by presenting again -
is marked out of band on the same identity, which is what the
extension's attribution rules provide for, so Black Frame Insertion,
Shader Sub-frames and swap intervals above one no longer keep the mode
off; Scanline Sync, which paces the present itself, still does. The
Direct3D backends mark their presents the same way through a helper
around every Present, and take the interval from the frame on the
presenter's thread.
What crosses between the two threads in the Vulkan backend is
lock-free: the mode request and the presenter's answer are atomics
(a request the presenter has not seen reads as pending, never as in
effect); the swapchain the frame loop's input and simulation markers
may name is published under a generation, rewritten only while that
thread is seen outside a marker call, and a replaced swapchain is kept
until it is, so a marker never names a destroyed one. A wait the
driver does not satisfy four times running takes the mode off and
reports failure; asking again tries anew.
The fake-backend sample covers the sleep-after-present cadence, the
out-of-band markers, the cross-thread request and the kept swapchain;
the harness lane adds the threaded path through the wrapper on the
null driver and on Vulkan, and the pacing table pins the wrapper row.
Preserve the native bool type in the strict C89 geometry probe through an explicitly scoped Clang extension and exercise that compiler on Linux. Supply video state for deferred auto-state tests now reached by pacing input markers. Restrict replaceable fence-symbol injection to desktop GL and use framebuffer readback in GLES, where glGetTexImage does not exist. Keep GLES pixel and lending assertions enabled.
A new Driver Pacing setting (video_driver_pacing: Off, On, On + Boost;
Off by default, so nothing changes until it is asked for) lets the
graphics driver say when each core frame should start, through NVIDIA
Reflex's VK_NV_low_latency2 on the Vulkan driver. The runloop holds on
the driver's own timeline semaphore where Frame Delay used to sleep,
before the frame's input is sampled, and reports the input, simulation,
render and present markers on the frame's identity so the driver can
place the release. Where the gap limiter or the content-rate timer
would hold, its period goes to the driver as the minimum present
interval rather than being slept a second time; vsync and audio keep
holding where they do, since the release schedules the start of a frame
and not its presentation. The mode is a property of one swapchain: a
replaced one starts over, and nothing names one the mode was not set
on. A backend error, or a sleep the driver never signals within the
bound the runloop allows, falls back to the existing pacing for good.
The release is owned only for a content frame the core made, on the
unthreaded path, at normal speed, focused, with the menu down; pause,
the menu and fast-forward keep their pacing, and Black Frame Insertion,
Shader Sub-frames, Scanline Sync and a swap interval above one keep the
mode off until their present schedule is mapped onto the extension's
one sleep between presents. The driver pokes (driver_pacing_set, _wait,
_mark) are vendor-neutral so other backends can sit behind them; the
requested mode and the reason it is or is not in effect show on the
statistics overlay and in System Information. The runloop's decision is
pinned in samples/runloop/pacing, the Vulkan backend's state machine
runs on fake entry points in samples/gfx/vulkan_driver_pacing (a new CI
job), and the runloop's side runs against a fake backend as a lane of
the threaded-video harness, on the null driver and on Vulkan.
* menu: add an option to preserve text when editing
Prefill string settings and playlist names before opening built-in or native keyboards. Expose the behavior in Menu Controls, enabled by default, with a persisted menu_input_preserve_text setting.
* menu: distinguish new text input from editing
Use an explicit dialog type to preserve existing values when editing. New-entry, search and authentication dialogs start empty. Remove the menu option and configuration setting.
* input: select existing text when opening an editor
Typing replaces the initial selection, while arrow keys collapse it for editing. Draw the selection in each menu driver and select the initial text in native Android and iOS fields.
* text entry: no stored passwords on screen, and native buffers stay put
A password setting opens its editor empty rather than with the stored
secret selected in plain text.
The Android and iOS keyboards put a fixed 512-byte buffer under the
line. Opening it now sets the line's capacity to that buffer and marks
it fixed, so an append from a hardware or on-screen key stops at its
end instead of writing past it or moving it from under the keyboard.
frontend_overhead's text selection lane opens a string setting and a
password setting, types over a selection, and appends to a fixed line.
---------
Co-authored-by: LibretroAdmin <[email protected]>
The sample and test builds used full debug info. -g1 keeps what
sanitizer reports and backtraces need - function names, files and line
numbers - and drops the variable and type information nobody reads in
CI. Objects get about 30% smaller (rh265.o under ASan+UBSan: 5.1MB to
3.7MB), so the ccache caches hold more of each job's build and are
quicker to restore and save.
RARCH_LOG_V copies each formatted line into a ring of the last 256
lines once it is armed, which the MCP server's start or the first
GET_LOG does; unarmed, a log line costs one relaxed load more. Any
thread may log: a writer claims a line number, writes the slot as
atomic words between a busy mark and its stamp, and the reader keeps a
line only when the stamp matches before and after the copy, so a
reader never waits and never sees a torn line. GET_LOG answers the
lines since the one asked for, then NEXT; lines overwritten before they
were read are counted as SKIPPED. Desktop builds with the MCP server
only; samples/runloop/log_ring writes from four threads while reading,
under ASan and TSan.
SET_INPUT holds a RetroPad button or stick axis for a player, read by
the core over the player's own controller through the input state
wrapper, until it is set again or RELEASE_INPUT lets it go; replays
record it like any other input. A single flag tells the wrapper there
is anything to add, so with nothing held a query costs one test.
Achievements hardcore mode refuses it.
Do not cancel a pending menu toggle for a hotkey activated only by the same physical input as Hotkey Enable. Resolve controller profile fallbacks while retaining cancellation for independently held inputs and overlay or remote presses.
Add portable coverage of the production cancellation and release code, and controller chord checks in the frontend harness.
Fixes#19087.
* Defer auto state load for cores requiring initialization
* runloop: wait for deferred auto-state initialization
Keep the request pending until the primary core has run and reports a nonzero serialization size. Preserve one-shot loading without repeated state reads, reset execution tracking on lifecycle changes, and skip settings queries when no load is pending.
* tests: cover deferred auto-state readiness and cancellation
Drive the shipping runloop and command handler with a core that initializes over multiple frames. Check one-shot loading, idle overhead, cancellation, core reset, missing retro_run, paused frames, hardcore mode, and the unchanged non-quirk path. Add Linux sanitizer coverage and a Windows cross-build.
* runloop: avoid auto-state conflicts with master
Move deferred auto-state fields away from the lifecycle fields extended by master, and reset them after joining autosave workers. Keep the reset before core unloading so both the auto-state and audio teardown changes merge cleanly. Supply the new audio publication and between-frame polling dependencies in the regression harness.
---------
Co-authored-by: keycross <[email protected]>
Co-authored-by: Codex <[email protected]>
Samples compiled shared tree sources in place, so whichever sample ran
next linked those objects with the flags they were built with, as
unity_identity did with mic_handshake's ASan objects. Every sample that
builds a source from outside its directory now includes
sample_objdir.mk and maps those objects under its own obj/; its own
objects stay where they were. The rule for the mapped objects has a
slash in its target, so make 3.81 does not strip the directory.
tools/sample_objdir_check.py dry-runs every sample Makefile and fails
on an object written outside the sample; the Samples-Objdir-Check
workflow runs it.
Track whether the cached bind mask actually contains held keys. Clear it only after a held poll, while preserving release and bind-removal behavior. Extend the shipping frontend lane across idle, repress and removal.
Keep both publication increments defined across the signed boundary and the full counter lap. Exercise both boundaries through the shipping poll and snapshot reader.
Only a zero raw pull may use the digital-button fallback. A nonzero pull shaped to zero by the deadzone or sensitivity must remain zero. Preserve the existing fallback for zero raw pressure and digital-only binds.
Extend the frontend pressure lane with held digital buttons below and above the deadzone, and with zero sensitivity.
Analog Response Curve (input_analog_response_curve, 0.5 to 3.0 by 0.1,
1.0 by default) is how a stick's movement grows with its tilt: the
tilt past the deadzone, rescaled to the outer edge, to that power,
before the anti-deadzone lifts where it starts. Above 1 small tilts
move less, for finer control near the centre; below 1 they move more.
It is under Settings > Input with the deadzones, and with the rest of
them it is worked out into each port's values
(input_driver_deadzones_refresh()): a stick with only a curve set
takes the shaped path by a deadzone of a millionth.
The shaping is two halves now. input_stick_axis_scaled() stays inline
and, with no deadzone, does what it did; the shaped half -
deadzone, outer deadzone, curve, anti-deadzone - is
input_stick_axis_shaped(), out of line. With the curve's power in it,
the whole was too big to inline at all, and every stick read made four
calls: 156 instructions a frame more. Split, a stick with none of it
runs less than before - over 300 frames (X11), 3,436,392 instructions
with no controller and 4,097,054 with one become 3,432,136 and
4,092,809.
The frontend harness's lane on the deadzone numbers: a curve of 2
takes a tilt of 0.5 to 0.25 and of 0.5 a tilt of 0.25 to 0.5; past a
deadzone of 0.2 a curve of 2 takes 0.6 - halfway - to a quarter; full
tilt stays full. Every lane passes; the settings definitions and the
translation tables check.
The last step of the plan's section 23.