Commit Graph
81 Commits
Author SHA1 Message Date
sonninnos 2c80e1abd6 Add Bluetooth settings visibility option 2026-11-11 08:26:03 +02:00
LibretroAdmin 026a7aa690 iOS screen margins: keep iPad multitasking and iOS-only settings
Follow-up to the scene window and screen margin change.

Sizing from the window made the iPad multitasking test compare the
view with its own window, which it always fills, so Split View, Slide
Over and Stage Manager stopped being detected and the safe area was
applied to them. Compare the window with the screen.

The four settings were registered on every platform, adding keys to
every retroarch.cfg. Registering them under TARGET_OS_IOS, as
audio_respect_silent_mode is, keeps them off the others.

They also had no sublabel: the description never appeared because
sublabels are bound through the table in menu_cbs_sublabel.c.

The margins were copied into an NSData, queued to the main thread and
stored in an ivar, on a path borrowed from the screen orientation
setter. All of it runs on the main thread already, so the layout now
reads the settings directly and a change only asks for a layout. That
also stops every slider step from re-requesting the orientation, and
drops CMD_EVENT_VIDEO_APPLY_STATE_CHANGES, which ran before the
asynchronous layout it was meant to follow.

Margins are rounded to whole device pixels so the layer is not
resampled at a fractional position or size.
2026-11-09 03:45:40 +01:00
hada0127 ed38dd6394 iOS: preserve scene windows and add configurable screen margins 2026-11-09 03:45:40 +01:00
Davey Hughes 5756db8659 Input: Plays As, several controllers on one player, with hotkeys of their own (#19916)
* 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.
2026-11-08 16:16:19 +01:00
davey 18128e1fa5 menu: a display for each screen, Menu Follows, Menu Display and Notifications
On Android, Separate Screen Windows reads "Game Screens on Other
Displays". While it is on, Stereo 3D & Screens lists a Game Screen N
Display row for each screen, Menu Follows (Game Screen 1, Game Screen 2
and each further screen the core has, or Separate), Menu Display (only
when the menu follows no screen) and Notifications (With the Game or
With the Menu). A Menu Follows screen kept from a core with more
screens stays until it is changed. A display row offers Auto, each
display by name with its product and size, such as "Display: DP Screen
(DELL U2720Q 2560x1440)", and Off. OK opens a dropdown of the displays
listed now. "Game Screen" and "Display:" keep a game's second screen
apart from a panel Android calls Screen-2.

The name is what is saved, so the same port is found again whichever
monitor is on it; a display no longer listed shows as "Display: <name>"
and counts as Auto. video_screenN_display, video_menu_follows,
video_menu_display and video_notifications_window are saved in the
config and in core and content overrides. They replace
video_screenN_window_mode: an old Hidden loads as Off, Attached and
Separate as Auto, and the old key is no longer written. The mode setting
is gone, and the monitor row is listed on no platform;
video_screenN_window_monitor stays in the config for the desktop.

Until placement reads them, screen 1 stays in RetroArch's window and the
others go by Auto unless Off.
2026-11-07 21:28:27 +01:00
Davey Hughesandlibretroadmin 6ebf66b643 OpenXR: per-screen placement for headset screens, and Edit Screens to arrange them by hand (#19945)
* video: per-screen geometry for the headset's floating screens

Each floating screen, by the core's logical screen id, gets its own
offsets, yaw, pitch, roll, scale and visibility (video_screen_xr_t),
applied on top of where the layout puts it. video_views_xr_reset gives
the defaults and video_views_xr_normalize clamps each value to its
range: offsets to 10 m either way, angles to 180 degrees, scale from 0.1
to 10. A planner given no screens uses the defaults, so nothing moves
until a setting does.

* configuration: save per-screen headset geometry

video_screen<N>_xr_x, _y, _z, _yaw, _pitch, _roll, _scale and _visible
keep each floating screen's adjustments for screen ids 1 to 8, and
survive configuration overrides like the other video settings. A minimal
save writes only the values that differ from the defaults.

The video_screens settings sample checks the round trip and the override
cases, and runs in the OpenXR input and Wine views jobs. The defaults
fixture now links video_views.c, which the reset uses.

* menu: per-screen controls for the headset's floating screens

Stereo 3D & Screens gains Screen, which picks a floating screen by its
logical id among those in the current frame's map, and for it
Horizontal, Vertical and Depth Offset, Yaw, Pitch, Roll, Scale and
Visible, with Reset Screen and Reset All Screens. The rows apply live
and are hidden where they cannot apply, such as a tracked projection's
eyes. Stepping between screens reads one snapshot of the map and is
bounded, so a tracking change mid-step cannot leave it walking an empty
map.

docs/openxr.md describes the controls; the video_screens sample drives
them through the shipping menu actions.

* video: apply per-screen headset geometry to the frame snapshots

Each XR planner, Vulkan's and GL's, takes the normalized screen settings
with the frame snapshot, so a screen moves, turns, scales or hides as
its rows change, while paused too; recentering and tracked projections
are unaffected. Screen images are kept between frames, so shader
feedback survives a placement change.

A retained XR target that cannot be allocated is skipped quietly,
remembered by its size so it is not retried every frame; only the
desktop UI and canvas targets log the failure.

Covered by openxr_core_test, the video_screens sample, the e2e runner's
feedback shader case and the new vulkan_views_target sample.

* android: keep the OpenXR EGL context C89

gl_xr_egl_init declared its extension string after statements.

* video: pose inverse, layout spots and grab maths for headset edit mode

Each item's layout spot comes from one helper that placement uses and
the quad set records per slot, so a shown pose turns back into the
settings that placed it. Grab maths: the item follows the aim, level
unless tilting, offsets clamped to the settings' range; the stick pushes
along the ray and resizes. Edit picking sees every item and takes the
menu by a handle bar under it.

* video: a frame without a view map is screen 1 in the headset

A core without a views map (SNES, say) had no screen to select, so its
frame could not be moved. Its whole frame now takes screen 1's offsets,
turn, scale and visibility, and the selector lists Screen 1. gl's window
stays as laid out while the menu is open over it.

* video: the headset's menu in front of screen 1, or where it was put

The menu sat at a fixed spot, so moving screen 1 closer left the menu
behind it. It now floats 10 cm in front of screen 1, facing as it does,
at its default angular size and never nearer than 0.4 m, while screen 1
floats and shows, and is still submitted after the screens. A menu place
(video_openxr_menu_xr_* with video_openxr_menu_placed) overrides that;
it is saved like the per-screen keys, and Reset All Screens clears it.

* input: Edit Headset Screens mode for the OpenXR controllers

Edit Screens starts from a hotkey, a headset action, the
HEADSET_EDIT_SCREENS command or a Stereo 3D & Screens row, and stops on
the same, on opening the menu, on a video reinit or when the session
ends. It syncs its own action set in place of the game's, points the
lasers at every item whatever the Laser Pointer setting, keeps the
menu's quad up while editing, and with While Editing Screens set to
Pause pauses only content that was running. A runtime that refuses its
bindings keeps the game's and leaves edit mode unavailable.

The setting reaches the headset's poll with the others, and the pause
and resume are asked of the frontend as platform requests, so the input
driver issues no command and takes no settings itself. A Laser Pointer
value past its choices, which a hand-edited config can hold, reads as
Auto rather than as edit mode's.

* input: grab, tilt, push and resize headset screens in edit mode

A grip on an item takes it in the screens' frame: it keeps its pose
relative to the hand, stays level unless the trigger is held, and the
stick pushes it along the laser and resizes it, past a deadzone. Each
poll writes the item's offsets, turn and scale where the menu's rows
write them, through named frontend calls, clamped so it waits at a limit
and resumes from there. The menu is taken by its handle; a menu that
follows screen 1 is placed as large as it shows. The main button resets
a free item as Reset Screen does, or puts the menu back in front of
screen 1. A recenter, a screen leaving the map and leaving edit mode let
go, counted for the frame snapshot; an untracked hand holds its item
still.

* openxr: held screens follow the hand at the headset's rate

The main thread publishes what each hand holds through a seqlock; each
headset frame re-places a held item, both eyes of a stereo screen, from
that frame's own aim, so it no longer waits for the next video frame. An
item let go stays where the hand left it until a quad set planned at or
after its release (an edit count carried in the frame snapshot) shows
it, even once the hand holds another item, so nothing steps back. Lazy
Follow holds its glide while anything is held.

Edit mode outlines every item, brightly the one under a laser, and draws
the menu's handle bar under it, from the laser's image, now an atlas of
the dot, two outlines and two handle bars. They take only the layers the
screens leave after the dots.

* openxr: edit mode end-to-end cases and documentation

The test layer's aim can roll, and the e2e runner drives edit mode
against Monado: grabs that follow the hand on every headset frame and
stay where they are let go, level and tilt, the stick, resets of a
screen and of the menu, the menu taken by its handle and following
Screen 1 at its size, outlines and the handle, a no-map core, a views
core's fallback frame, Follow Head, Lazy Follow, recentering, two hands,
pausing, and what starts and ends edit mode, including the headset's
hotkey bits with no command interface. The cases whose work is a host's
or driver's own run on glcore and gl too. docs/openxr.md explains
editing screens in the headset and where the menu goes.

* openxr: a laser beam from each controller to its dot

A thin strip runs from each hand's aim to its dot, or 1.5 m into
nothing, turned about its own axis each headset frame to face the head
so it never shows edge on. It comes from the laser's image, so it needs
no new swapchain, and uses layers left after the dots and edit mode's
outlines, so it is the first to go when the runtime has few. The new
Laser Beam setting shows it in edit mode only (the default), always, or
never; outside edit mode, Laser Pointer and its hotkey hide it with the
dot.

* openxr: edit mode outlines in three bands, outside the item

A light band between two dark ones shows on white, black and busy
borders alike. The rim now sits wholly outside its item, 5 mm clear of
it even after filtering, so it never covers the picture; its region
grows to keep two texels a band at about the old thickness. Screens'
outlines are submitted before the screens, so a neighbour placed flush
covers the part of a rim over its picture, while the menu's outline and
handle bar stay on top. The handle bar moves 5 mm below the menu's
outline, which it would otherwise cover, and gets the same bands, faint
and bright.

* input: choose a headset edit item with Select Next and Previous

Edit mode steps from the laser through the screens it can move and the
menu. A chosen item is the one lit, either hand's grip takes it from
wherever the hand is, and Reset resets it, so a screen hidden behind
another can be moved. Whatever a hand holds stays lit.

A ray on the menu's handle bar takes the menu before any screen in front
of it. Within 3 cm of the bar, a ray that came from the bar keeps the
menu even over a screen, and one that came from elsewhere takes it only
where no screen is hit.

* openxr: snap a held headset screen to the screens near it

Snap to Screens, on by default, turns a held screen parallel or square
to a nearby screen and lines up its plane, edges or centre. Each pull is
exact within 3 cm or 5 degrees and fades out over as far again, so the
screen catches up with the hand rather than jumping, and snapping fades
in over the first 5 cm or 10 degrees after a grab, so taking a screen
never moves it. With several screens near, the snap that leaves the
least error wins, so a near screen's exact pull beats a far one's fading
pull whatever their order.

One stateless function inside the grab serves the main thread and the
headset's thread, so a screen is saved where it was shown. The holding
hand buzzes as a snap takes hold, once, and not again until the screen
has left that pull.

* input: turn a held headset item with the free hand's stick

A quarter turn of a held screen needed the controller turned a quarter
turn, which swung the screen round the hand on its long reach, so a
square snap could not be reached. Now the stick of the hand holding
nothing turns what the other hand holds about the vertical through its
middle, faster the further it is pushed, and snapping still catches it
parallel or square.

A carried item keeps its pitch and roll and its angle to the way the
hand points across the floor, so raising, lowering or twisting the hand
no longer tips or swivels it; the trigger lets it ride the hand rigidly.

* openxr: smooth the controller aim in edit mode

A screen held about 2 m out on the laser magnified every tremor of the
hand. In edit mode each hand's aim now goes through a One Euro filter,
which smooths a slow hand hard and a fast one little, with its rate
taken from the headset frame's own time step. The headset's thread
filters once a frame and hands the result to the main thread, which
grabs, moves and lets go from it, so a screen is saved where it was
shown. The new Edit Smoothing setting picks Off, Low, Medium (the
default) or High; outside edit mode the lasers are never smoothed.

* openxr: name the chosen edit item beside the controller

Edit mode now shows a faint tag just above the hand that last pressed
Select Next or Previous, reading Laser, Screen N or Menu, so what the
grip will take is plain without a notice. The words are drawn once into
the laser's image in the default font and the interface language, light
over a dark halo like the outlines so they read on any background, and
the tag is one more headset layer; when the runtime is short of layers
the beams are dropped before it.

On a runtime whose swapchains are too narrow for the words, the laser's
image keeps its old width and there is no tag.

* input: tilt a held headset item with the free hand's stick

In edit mode the free hand's stick already turns the item the other hand
holds. Its up and down now tilt that item too, about the item's own
left-right line through its middle, at the turn's rate and with no
limit: up tips the top back, as when pushing a monitor's top back. It
works whether the item is carried or rides the hand with the trigger
held, and snaps as the turn does.

Within about 25 degrees of left or right the stick only turns, and
within about 25 degrees of up or down it only tilts, so a push meant for
one does not drift into the other. Edit mode's starting notice lists the
turn and tilt and stays about 8 seconds.

* input: snap a turned or tilted headset item to angle steps

While the free hand's stick turns or tilts a held item in edit mode, the
item clicks into steps of the new Snap to Angles setting (15 degrees by
default; 30, 45, 90 or Off), so it is easy to set straight. The turn
counts from facing the user's head across the floor, the tilt from
upright. Within about 2 degrees of a step the item holds there and the
holding hand buzzes once; the stick's own angle keeps adding up, so
pushing on leaves the step.

Only the stick steps: carrying an item or tilting it with the trigger
never clicks, and a snap to another screen that would engage wins over a
step.

* openxr: show the back of a headset screen turned away

Runtimes draw only the front of a quad layer, so a screen or the menu
turned away from the head vanished. When the head is behind a quad's
plane, the frame now submits a plain dark panel in that quad's place: a
solid grey cell of the laser's image, at the quad's size, turned to
face the head. It takes the quad's own layer, so the layer count and
cap are unchanged. Edit mode picks either face, so an item turned away
stays grabbable, and its outline, handle and dot turn with its back.

* input: keep a screen snap's angle once the free stick lets go

A snap to another screen shows a held screen square or parallel to it,
but the item's own angle stays where the stick or the hand took it, up
to several degrees off. The next turn with the free hand's stick started
from that hidden angle, so the screen left the snap early on one side,
late on the other, and caught up with a lurch.

Now, as the free stick returns to its deadzone and as a push starts, a
snap engaged on the turn becomes the item's own angle: the carried turn,
or the orientation the trigger tilts. A snap still fading in never does,
since that would hop the screen into it. The place is left to the hand,
and carrying alone never keeps the angle, so pointing still pulls a
screen out of a snap. Letting go of the grip leaves the item where it
shows, as before.

* openxr: hinge a held headset screen to a neighbour's edge

Snap to Screens now also hinges a held screen to another along a
shared edge at any angle between them, so a DS or 3DS pair can be
opened at a slant: an edge within 5 degrees of parallel to one of the
other's, within 3 cm of its line and sharing some length with it,
turns exactly parallel and moves onto it, centres or ends level, its
angle left free. The pull fades in over the first 3 cm of shared
length, so a screen slides off the end of the other's edge without a
jump, and a diagonal neighbour never hinges. Screens that would
overlap do not hinge; the pull is whole from flat to square and fades
out folded shut.

Flat and square snap as before and report a hinge at 0 and 90. A turn
to the same screen pivots on the shared edge, so the edge stays on the
line as the screen leaves flat or square. The snap still keeps nothing
between calls; its result carries a hinge bit, and video_xr_snap_hinge
also says which edge and line, for the stick to swing about.

* input: swing a hinged headset screen about the shared edge

While a held screen is hinged to another, the free hand's stick axis
that turns nearest the shared edge, the tilt for a top or bottom edge
and the turn for a side one, now swings it about that edge, so it stays
attached; the other axis still turns or tilts it and can pull it off.
Where the swing leaves it, angle and place, it stays as the stick lets
go. Snap to Angles counts a hinged screen's steps from flat with the
other screen.

A hinge counts only while the screen's own edge is within its range,
not when other snaps alone hold it on the line, so a swing never
carries a screen round a line it is far from.

A square corner is a side hinge at 90 degrees, so the turn stick there
now swings the screen about the corner's edge. Main thread only;
nothing new reaches the frame loop, which still shows the grab through
video_xr_grab_pose.

* openxr: leave a gap between hinged headset screens

Edges that meet in edit mode, side by side, at a corner or hinged at any
angle, now click to a set gap from within 1 cm and otherwise keep the
gap the hand gives them, still hinged, up to 10 cm (letting go by 13).
Screens no longer have to touch, which made them overlap. Each grab
carries the gap, so the edit poll and the frame loop snap alike; until a
setting sets it, it is 0.

Pushed past touching, a hinged edge stops there for the first centimetre
of the push, the stop fading out by 3 cm, and ticks once per push.

A swing pivots on the held screen's own edge, so the gap keeps through
it, and a commit keeps the hand's gap unless the click holds it. After a
swing lets go past square, that stick axis and its steps keep pivoting
on the same edge until the edge leaves the hinge's reach, so a flick
past square and back does not creep.

Of two neighbours within the 5 degree turn snap, the nearer edge wins
the turn and the hinge, by one measure for every screen, so a screen
stacked behind, one off the ends of the edge or a side wall at a corner
never outranks a hinge the turn holds.

* menu: add Hinge Gap for headset screens

Hinge Gap (0, 1, 2, 3, 5 or 10 cm, default 2) sets the gap edit mode's
Snap to Screens leaves between screens that meet edge to edge, side by
side, at a corner or hinged. Edit mode takes it as it starts and gives
it to each grab; the click to it buzzes like the other snaps.

* menu: put Edit Screens under Headset Output

Stereo 3D & Screens now lists Edit Screens and its snaps right under the
switch that reveals them, then the headset's own setup, then where the
screens go, with the per-screen rows last and Visible first among them.

* openxr: track each held screen's hinge on the frame loop

The frame loop already re-snaps each held screen every frame. It now
also keeps, per hand, the hinge that snap holds whole, until the edge
leaves the hinge's 4.5 cm reach, where the frame draws it, so edit mode
can mark it. A kept hinge whose target no longer matches is dropped.

video_xr gains the hinge line's ends, along the overlap of the two real
edges and midway across the gap, an outline cut that drops one edge's
rim, and a green strip in the laser's image, before the tags.

* openxr: mark a hinged headset screen with a green line

While a held screen's hinge holds, the two edges that meet drop their
outline rim and a 6 mm green line runs along the hinge where they
overlap, midway across the gap, so it is clear when the hinge has taken
hold.

The line is drawn as the beam is, a centimetre nearer the eye than the
edges, since SteamVR does not keep the order of overlapping quad layers,
and comes after the outlines in the layer budget; hidden rims cost no
layer.

* input: buzz longer as a headset screen hinges

A hinge taking hold now gets one 150 ms buzz at 0.8 on the hand carrying
the screen, against the 20 ms snap tick, so the attach can be felt. It
fires again only after the edge leaves the hinge's reach, hinges
elsewhere, or the other hand takes the screen it hinges on, so a swing
past square and back does not repeat it. Ticks due while it plays are
skipped rather than cutting it short.

* openxr: find headset screens resting in a hinge

Two screens no hand holds rest in a hinge when their poses pass the
test that engages a carried screen's hinge, with either one carried,
and the hinge, held whole with nothing else pulling, leaves the carried
one where it is. So a hinge found at rest stays hinged when its screen
is taken. The engaged test moves into its own function so both use the
same one, and one target can pull whole whatever the grab's fade.

* openxr: keep a headset screen's hinge shown at rest

In Edit Screens, two screens no hand holds that rest in a hinge keep
the green line and their hidden rims, whether let go hinged or loaded
that way. The main thread counts them only when a resting screen
changes and sends them with the hands' record, so a release and its
resting pair arrive together; the frame loop places each line from
the screens it shows. A screen hinged at rest on two adjacent edges
still keeps one of those rims.

The hands' record now outgrows the quads, so the seqlock's copy is
sized to the larger of the two.

* openxr: pick up a hinged headset screen still hinged

Taking up a screen that rests in a hinge starts the carry on that hinge,
whole from the first poll, with no buzz and no jump, until it leaves the
hinge's reach, another hinge takes hold, or the other hand takes the
screen it hinges on. Until the grab's own snapping has faded in, the
kept hinge's target still pulls whole, so a straight pull out does not
hop.

The grab carries the hinge so both threads hold it the same; of several,
the one the snap ranks best carries over, and only where holding it
moves nothing. While the other screens fade in, the hinge's screen also
leads the turn, so a nearer one still fading does not take it.

* openxr: end a headset screen's hinge where a push passes the stop

Pushing a hinged screen into its neighbour holds the edge at touching
for 3 cm of push, fading out by 5.5 cm. Where it stops holding, the
hinge ends: the line goes, the rims come back, and the lid and buzz are
cleared, so screens pushed into each other show no hinge that a let-go
there would not rest in. Pulled back, the hinge takes hold again 5 mm
short of where it ended, with the long buzz, so a tremor there does not
flicker it.

The grab carries that through flag, and the frame loop keeps its line
until the main thread sets it, so the two threads never disagree for
more than a poll.

At Hinge Gap 1 a pair let go touching rests hinged and is taken up
hinged, as at every other gap. A screen swung into a square corner gets
the long buzz on arrival, and the other hand taking the target does not
hop the carried screen by the push's depth.

* input: let go of a headset screen where it was before the grip opened

Opening the grip twitches the hand, and the poll that sees the grip
open would move the screen by that twitched aim. Half a degree of pitch
moves a screen 1.8 m out by 1.6 cm, enough to knock it off its hinge's
gap or out of level, so it would not rest hinged.

Each hand keeps a short trail of where, turned and as large as its
screen showed, entries at least 10 ms apart, and a grip let go puts the
screen as it showed 80 ms before (INPUT_OPENXR_EDIT_REWIND_US), or as it
was taken if held for less. Edit mode's end and a recenter let go where
the screen shows.

* openxr: find where a let-go headset screen settles in its hinge

A screen let go while its hinge shows should land exactly in that hinge,
so the hinge still shows at rest and a pick-up is hinged.
video_xr_hinge_settle holds the hinge whole from the hand's pose, then
moves the edge to the set gap from within 3 cm of it (touching stays
where the stop holds it) and levels it along the edge within the level
pull's reach. Its result must pass the rest test, and it turns no
further than a grab's hinge engages, which shares that test.

A pair resting inside a settle band but off its point is no longer
listed at rest, since a pick-up and let-go would move it. A touching
rest is taken 5 mm deeper into the push stop, at every Hinge Gap, so a
hand that drifts less than that apart before letting go settles touching
rather than at the gap.

* openxr: settle a let-go headset screen into its hinge

A screen let go while its hinge shows now goes exactly into that hinge:
to the set gap from within 3 cm of it, touching from the stop's hold,
and level along the edge from within the level pull's reach. It then
rests there, listed and green, in the same publish, and is saved
there. Elsewhere it stays where it shows, as before.

Each trail entry also records where the hand put the screen with no
snap and whether a let-go there settles. At the let-go the rewound
entry is settled again against the screens as they are now, at that
entry's size, and put in one go.

* openxr: let a hinged headset screen rest at any fold

Snap to Screens' turn to flat, and its turn to square at a corner, is
whole within 5 degrees and fades out by 10, so a hinged screen held
there shows at twice its angle off less 10. No fold shown in that fade
read as a rest: the pair was not listed, a let-go there did not settle
into its hinge, and taking it up would have moved it.

Where a hand takes a rest, it now holds the screen at the angle the
turn's fade shows it from, the inverse of the turn's pull, so every
fold rests, is listed and is taken up unmoved. Held snapping is
unchanged.

* input: keep a headset screen's hinge through a shaking hand

Edit Screens' hinge buzz, lid and push stop followed the snap's own
engage and reach on every poll. A hand shaking half a degree (1.6 cm at
the screen) about a hinge's edge flipped them many times a second, each
flip a long buzz.

Each hold now keeps one mark: the hinge a let-go would settle into. It
takes hold at once, with the long buzz, and lets go only once polls have
seen the hand out of where a let-go would settle for 150 ms, each poll
counting at most 50 ms, so a stalled main thread or a spell untracked
never lets it go alone. The other hand taking its target, hinged
elsewhere or not, the target leaving and the let-go end it at once. The
lid waits for the mark to go, the push stop follows it, and a let-go
while it shows settles into it from where the hand last would have, edit
mode's end and a recenter included.

* openxr: draw a held screen's hinge as the main thread marks it

The grab carries the main thread's hinge mark, and the frame loop draws
a held screen's green line and cuts its rims from that mark alone,
through video_xr_grab_marked, while it snaps the screen every frame. A
frame loop taking the hinge by itself between polls would flicker the
line for a hand shaking at the edge of the hinge's range while the mark
held. A mark whose target the frame does not show cuts no rim.

The other hand taking the mark's target ends it, and the lid, on any
poll, tracked or not, so a lost hand's line does not come back when that
hand lets go.

video_xr_grab_near_hinge loses its past argument, which only the frame
loop's keep used.

* openxr: keep edit mode's hinge layers in place

SteamVR does not keep submission order for overlapping quad layers, so
a layer added, dropped or moved to another index can pop the edit
outlines in front of the screens, as a wobble at a hinge would do to
its line's layer.

The hinge lines are fixed slots: one for each hand holding a
screen, hand 0 first, then one for each pair at rest. A slot with no
line to show is drawn in place from its screen's middle, at rows of
the laser image that are empty either way up. The layer count and
every index change only at a grab, a let-go or a re-count of the pairs
at rest; a mark only changes crops and which rows a slot shows.

* input: date a let-go's trail by frame and stop it at a stall

Each trail entry is dated by the frame its aims are from, one a frame
and per hand, and the release poll records a new frame's aims first.
The rewind stops at a stall, entries more than 2.5 frames apart, a
frame counted as at least 20 ms so a fast headset's close entries never
read as one. Under load a stall of 0.1 to 0.3 s can come just before
the release; the entry from 80 ms back would then hold the pose from
before it and step the let-go back across all the stall's motion.

With Edit Smoothing on, which damps the twitch, a let-go that does not
settle into a hinge stays where and as large as it shows: a rewind there
would step back by what the smoothed aim still lags. The hinge decision
and the settle use the trail either way.

* openxr: cut edit outlines from cells instead of turning them

Cutting a top or bottom rim from the one outline image turns both
outline quads a quarter turn and swaps their width and height, and on
SteamVR that can blink every edit outline as the hinge takes hold or
lets go; side cuts, which never turn, do not.

The laser's image carries twelve pre-cut outline cells after the
tags, faint and bright, for a cut top, bottom, left, right, top and
bottom, or left and right, so a cut only picks a cell and the quads keep
their pose and size. The image is then 5582 texels wide. A runtime whose
swapchains are narrower, or that does not say how wide they can be,
keeps the turned and cropped outline at 2498 or 1208 wide. One log line
at the laser's creation gives the width chosen and the runtime's limit.
GL writes the top and bottom cells swapped for its bottom-up rows, and
Android's copy of the image is allocated rather than static.

The edit-hinge-3ds-narrow e2e case runs the turned cut at
RA_XR_LAYER_MAX_WIDTH=4096, on Vulkan and the GL EGL lanes.

* video: draw headset screens straight into the runtime's images

Each headset screen was drawn into an image of the driver's own and
copied from it into the runtime's on every new frame, Vulkan with a
render pass and GL3 with a blit. Screens are now drawn into the
runtime's image itself, as before per-screen placement, with no image
of the driver's and no copy.

A screen drawn again with no new frame - resized, made again or moved
while paused - replays the last frame's final pass from the chain:
build_viewport_pass_replay draws it at the new size from what it read
then, the passes' outputs, their feedback and the history that frame
saw, with that frame's count. Nothing else runs, feedback does not
swap and history stays, so a feedback or history preset holds still
while paused. A chain made to replay keeps one more history image, so
the frame's end writes where the last frame did not read; the window's
chains keep what they did. This also has movie frames reach the
headset, which the copy held at the first one, and draws a screen
resized while paused at its new size rather than stretching the old
image.

* openxr: leave what users who never edit see as it was

Screens seen from behind turn to their grey backs only while Edit
Screens is on, where screens are turned around; outside it nothing
changes and no head is located for them. The grabs carry whether
editing is on.

The menu opens at its own spot, straight ahead of the anchor, as it
did. Menu in Front of Screen 1, off by default, keeps it just in front
of screen 1 and moving with it.

The frame loop copies the main thread's grabs only once their stamp
moves, so outside edit mode no frame copies them. A new session clears
the frame loop's own copy and passes over what was published before
it, rather than publishing itself: the main thread stays the grabs'
one writer.

On Touch, the left menu button that opens the menu from edit mode is
spent until let go: held on as edit mode ends, it is no Start and does
not toggle the menu again after a second.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-30 05:40:25 +01:00
LibretroAdmin 5361e1bc8f netplay: lockstep for cores whose states are too large to roll back
Rollback keeps a savestate for every buffered frame and serializes every
frame, which a state in the tens of megabytes (Dolphin) cannot afford,
and needs states that restore deterministically enough to rewind on.

Lockstep runs a frame only once every player's real input for it has
arrived and never rewinds, so all delta frames share one state, used
only to hand a joiner the game or to resync. Desync checks hash the
core's system RAM, or the RAM in its memory map, rather than a whole
state; a state is serialized for them only when the core shows neither.
Input latency rises by a frame whenever lockstep has to wait and falls
again once input keeps arriving early, so it holds full speed over a
slow link. A host keeps sending while it waits on a joiner, grows its
buffers if the core's state has grown, and starts on the running game
instead of reloading it. A client takes a state labeled with a frame
whose input it already has, and loads it there.

Lockstep is a local choice and plays with rollback peers of this
protocol, and it is on by itself for cores past 32 MB of state and for
cores whose savestates load but cannot be rewound on, which netplay
used to refuse. The Lockstep setting asks for it everywhere. Older
clients cannot join a lockstep host.

samples/network/netplay_e2e runs a host and a client over the loopback
on a test core: sticks synced or not, lockstep, a lockstep host with a
rollback client, a desync mended and a 40 MB state.
2026-10-27 19:50:48 +01:00
LibretroAdmin a0d408f0ce netplay: sync the analog sticks of joypad ports
Netplay sent a joypad port's buttons only, so a core reading the sticks
of a joypad port (N64, GameCube and others) desynced or saw them at
rest on every remote player. The host now sends joypad ports as analog,
three words instead of one, with bit 30 marking them; each side hands
the core the joypad device it set, so nothing changes for the core.

This is protocol 8. A host with Sync Analog Sticks on, the default,
turns away older clients with the out-of-date message rather than
letting them desync; with it off, any version still joins.
2026-10-27 19:08:12 +01:00
LibretroAdmin 5c82bce0ed overlay: D-pad and face buttons read a finger's contact area (Android)
A touchscreen reports each finger as an ellipse, not a point; Android
gives its axes and orientation (AMotionEvent_getTouchMajor/Minor and
getOrientation). A thumb laid across up and right, or resting between
two face buttons, covers both - and read by its centre alone presses
one. After RetroArch-Lite's contact-area input.

"D-Pad Input Method" and "ABXY Input Method" choose how each reads a
touch: "Direction" - the way the centre lies, as always, and the
default - "Contact Area", every way the contact covers, or "Both".
Each tip of the ellipse's two axes presses the way it lies from the
area's middle, past a small dead zone there. "Contact Area Size"
scales how large a contact counts. Measured in pixels per axis against
the pointer's own space; RetroArch-Lite measured both against the
screen's height, which skews a contact on a wide screen.

Android's reader keeps each pointer's contact beside its position, and
the driver hands them to the frontend each poll
(input_driver_publish_touch_contacts). A finger with no contact size,
and every other driver, read by the centre's way. The rows show on
Android, where an overlay has the area they apply to.

overlay_contact_test holds the geometry: fingertips, thumbs across two
ways, upright and level contacts, magnification, wide screens.
2026-10-27 17:59:26 +01:00
Codex ed0a530640 settings: refresh the hotkey tap delay reference
Include the registered input_hotkey_tap_delay default in every emitted
configuration profile. Settings behavior is unchanged.
2026-10-27 12:41:46 +01:00
Codex e2e41947cc settings: refresh the stylus overlay default reference 2026-10-27 11:20:12 +01:00
LibretroAdmin ccffeb553a slang: drop vendored glslang and SPIRV-Cross
rslc is the slang compiler everywhere. ./configure --enable-glslang
--enable-spirv_cross links the system's glslang and SPIRV-Cross next to
it when both are found, and only then does Settings > Drivers offer a
Shader Compiler choice between the two: one setting read per stage
picks the path, with nothing copied.

The MSVC 2010 project builds Vulkan and rslc. Samples that used the
vendored copies run on rslc or the system's SPIRV-Cross.
2026-10-25 08:52:52 +01:00
Codex f377d2d271 settings: refresh the builtin media defaults reference
Record continuous playback and the opt-in CRT and refresh options in all three platform profiles. The emitted settings tables are unchanged; their committed reference now includes these intentional additions.

Validation: complete Linux samples/settings workflow passes, including table and unity checks.
2026-10-24 19:15:27 +02:00
Codex edd71e2cf0 media: load SubRip sidecars and render cues in fullscreen menus
Parse UTF-8 SubRip tracks in bounded task slices, retain active-session ownership and publish cue text without reading files on the render thread. Draw cues in texture menus and the RGUI framebuffer, including while transport controls are hidden or playback is paused. Add subtitle enable/delay settings and parser, ownership, drawing and four-menu sidecar coverage.
2026-10-24 11:00:47 +02:00
Davey Hughesandlibretroadmin 74b7b06fa5 Android second display output for video views (gl and vulkan) (#19905)
* android: drain the sensor queue with both sensors off

Losing focus disables the accelerometer and gyroscope, but an event
already in the sensor queue stays there, and android_input_poll_user()
only read the queue while a sensor was on. The queue's fd stays
readable, so ALooper_pollOnce() keeps returning LOOPER_ID_USER and
android_input_poll() spins at full CPU. The pause and stop
acknowledgements wait for the next runloop iteration, which never
comes: RetroArch logs "App thread did not acknowledge activity state",
the launcher shows late and the app hangs until it gets focus back.
A swipe home hits it often, since focus is lost before onPause.

Always drain the queue and apply the events only while a sensor is on.

* gl2: make a regrown pass texture as the pass's first one

gl2_recreate_fbo() gave the larger texture no filter, so it kept GL's
default GL_NEAREST_MIPMAP_LINEAR with a single level. On GLES that
texture is incomplete, and once a viewport-scaled pass grew, the next
pass sampled black; desktop GL with texture storage gets a complete
one-level texture. Make it with gl2_create_fbo_texture(), as at init, so
it also keeps the pass's filter, wrap mode and float or sRGB format. The
feedback texture is regrown the same way.

* video: per-screen window settings and the screens' window plan

Each screen gets window settings in the config,
video_screenN_window_mode, _x, _y, _width, _height, _monitor and
_fullscreen: Attached, Separate or Hidden, and where a window of its own
goes and how big it is. Screen 1 is always Attached; the others default
to Separate. video_windows_enable, off by default, is the switch that
lets screens leave the main window at all.

video_windows_plan() turns those settings into a frame's plan: the
screens the main window draws, the ones that go to windows of their own
and the hidden ones. A screen stays in the main window while the switch
is off, where the driver has no windows, or once the renderer has sent
it back. The views layout takes exclude_screens, so the main window lays
out only the screens it keeps, and a window only its own.

This is the base the desktop secondary windows and the Android second
display share. The video_windows harness, run in the Linux gfx samples
workflow, checks the plan and a layout with screens left out.

* video: contract for secondary screens' native windows

A context can offer windows of their own for secondary core screens
through an optional gfx_ctx_window_driver_t, the last member of
gfx_ctx_driver_t: create, update, poll and destroy, and bind and
present for GL contexts. Requests, native handles and events carry a
generation from video_driver_window_generation(), a frontend counter
that a driver or context restart does not reset, so an event that
outlives its window cannot be taken for its replacement's.

GFX_CTX_FLAGS_VIDEO_WINDOWS is what a video driver will report once it
draws to such windows; nothing sets it yet.

* egl: window surfaces for secondary screens

egl_window_surface_new/bind/swap/free give a secondary screen's window
a surface on the main context's display and config, with the main
window's attributes. Bind makes the context current on it at swap
interval 0, or on the main surface again; free puts the main surface
back first where the freed one is current. None of them log.

* video: share the secondary windows' event bookkeeping

What a native layer keeps of each secondary window for the frontend -
its owner, record, request, position, the events waiting one bit per
kind, and whether it closed - is a video_window_common_t at the start
of its own state, and the rules on it live once in gfx/video_windows.c:
marking a window closed (CLOSE, with FOCUS_LOSS where it had the focus,
a waiting gain or resume dropped), poll's drain in its fixed order with
whatever has no room left waiting, and the sweep of an owner's windows
at context teardown.

* video: one placement clamp for secondary windows

video_window_place_rect() puts saved bounds on a monitor or its work
area: bounds partly on it are moved onto it, automatic ones and those
not on it at all are centred, and the size is clamped. Whether bounds
are on it is judged as saved, before the size is clamped, so an
oversized window partly on it keeps its position on the other axis.

* video: frame snapshot for secondary windows

A frame presented with views now carries what a driver needs to make,
place and lay out the screens' own windows without reading settings:
each screen's window settings, the window plan, the core's own map and
frame size, the layout parameters and a restore count, from which
video_driver_window_layout() lays out one screen's window. The snapshot
lives in video_driver_state_t and video_frame_info_t points at it, NULL
while windows are off or the driver cannot make them; the threaded
wrapper copies it into the frame's slot, as it does the statistics text.

While the driver can make windows, the main window's layout leaves out
the screens that have one of their own or are hidden.
video_driver_restore_windows() clears the screens a close attached and
counts the request, and window generations can be taken on any thread.

* video: published layouts and translate for secondary windows

A renderer publishes each secondary window's layout as drawn, with the
core map it was made from and the window's client size, through
video_driver_publish_window_layout() from the thread that runs the
context. Latest wins with no lock: each window has three slots handed
over by index exchange, as the window title is, and NULL clears it.

video_driver_translate_window_point() maps a point in a window to the
core's frame: window 0 through the viewport, as before, and a secondary
through its published layout, scaled from client to drawn pixels. A
point in a window that has no layout, was cleared or has another
generation is not mapped.

* input: window-aware pointer and touch publication

input_driver_publish_window_pointers() and _touches() take each device's
and each touch's window: 0 for the main window, else a screen's own with
its generation. A device in a screen's window is placed through that
window's layout for the pointer and the lightgun, and has no place on
the main window's screen; one whose window is gone reads off-screen.
Relative motion, buttons and the ports' devices are as they were.

input_driver_publish_pointers() and _touches() publish into the main
window, so the input drivers that call them need no change.

* samples: video_screens harness for the screens' own windows

The shared windows base is tested against the shipping configuration,
video and input objects: per-screen window settings across saves and
overrides, a screen's own layout, the frame snapshot and its copy
under the threaded wrapper, published layouts and point translation,
window-aware pointers and touches, and window generations. build.py
links the test with a configured non-Qt build's objects; the OpenXR
desktop jobs, which make one, run it.

* video: the window plan can keep a screen other than 0 attached

video_windows_plan() takes the screen that stays in the main window
whatever its mode, and screen 0 takes that screen's mode. Callers pass
0, which plans as before. Android's external monitor rule needs it:
screen 0 goes to the monitor and screen 1 stays on the device, where
the touch screen is.

* video: route the separate screens to a platform's displays

video_windows_route() chooses the display a platform's Separate
screens go to, from a list the platform publishes. A screen's monitor
setting N names the display of ordinal N; 0 is Auto, which takes an
on, valid and physical display that is neither private nor the main
window's: an external one (DIRECT or TRANSITIVE) first, then the
lowest ordinal. On an external monitor Auto puts screen 0 there and
keeps screen 1, so the touch screen stays on the device. One display
takes screens at a time; a screen bound for another stays attached.
Each display also carries its size, for the menu's display picker.

* samples: a touch through a second output's layout

A touch on a second display's surface, mapped through the layout drawn
there, lands in the touched screen's part of the packed frame: one
screen on a 1240x1080 panel, two screens sharing it, and screen 0 on a
monitor; the letterbox reads off-screen when asked.

* samples: pin the attached screen, picker and pillar cases

The plan keeps a screen other than 1 attached with screen 0 in its
mode, and falls back to Attached with no settings. The picker refuses a
private display but takes one that is off, and Auto never takes an
entry without an ordinal. A touch in a pillar beside two screens, not
reported off-screen, is held to the nearer one.

* video: route the screens' windows through a platform's display list

A platform publishes its displays from its UI thread, latest wins,
through three slots as the window layouts are handed over. Each frame
takes the newest list, routes the Separate screens with
video_windows_route() and plans with the screen it keeps attached; a
screen with nowhere to go stays in the main window. All screens bound
for one display share one window, led by the lowest one, and the frame
says which screens each window shows. Taking a list restores the screens
a failed window sent back, and video_driver_attach_windows() lets a
renderer send them back from any thread. A request names its display,
and Android gets a native window kind.

A screenshot or GPU recording through the threaded wrapper draws the
cached frame again on the video thread. That redraw takes no list: it
keeps the app thread's last route, as it reads the forced and restore
state, so a renderer keeps the second display's window and the capture
shows the right screens. The route is written only when it changes, so
Win32, which routes nothing and whose modal redraw runs beside the app
thread, never writes it while it is read.

The video_screens harness redraws the cached frame through the real
threaded wrapper and checks that it keeps the frame's route and plan.

* video: let a platform take the secondary windows back

video_driver_windows_retire() has the renderer let go of every
secondary window, through a new optional poke hook. It is for a
platform command such as Android's second surface going away, never a
frame. With the threaded wrapper up it is queued to the video thread
behind the frame being drawn, and nothing waits for it: a hook the
caller passes runs there just before and just after, for the platform
to hold its own requests back and to hear when it is done. Without the
wrapper all of it runs before the call returns. One is queued at a
time; false says the video thread cannot take it yet.

video_screens checks that a retire queued behind a frame being drawn
returns at once, and runs after the frame, between the hook's calls.

* video: one slot hand-over for the window layouts and displays

The window layouts and the display list are handed over the same way,
through three slots. Two small helpers publish and take a slot for both,
with the same memory orders. No behaviour change.

The harness adds two display list checks: a longer list keeps its first
VIDEO_WINDOW_DISPLAYS_MAX, and a get of capacity 1 fills only the first
entry.

* android: native side of the second display

The Java controller hands native code the display list and the second
display's surface through three natives. The list goes to
video_driver_publish_window_displays(). A surface becomes a node holding
one ANativeWindow reference, exchanged in for the video thread to take,
with a generation the touches carry. A size change of the same window
keeps the generation; a different window first gives up the old one.
Giving one up uses the main window's ticket handoff: the UI thread waits
while the app thread drops the node and queues the renderer's letting go
of its windows. The thread that lets go of them acknowledges once it is
done, so the UI thread is not left waiting for the app thread's next
poll; a retire the video thread cannot take yet is tried again from the
input poll. Native code asks Java to show or hide the display through
setSecondDisplay(), which posts and never waits, and asks nothing while
the renderer is letting go of a surface. The app state gets a 64-record
ring for the display's touches.

The natives run on the UI thread and read its own app state,
g_android_early, which android_app_free() clears unless a re-created
activity has already replaced it, so a callback after onDestroy finds
none. A display name GetStringUTFChars cannot read leaves no exception
pending for the next JNI call.

* android: second display windows for the gl and vulkan contexts

Both Android contexts give the renderer the secondary-window table.
Creating a window asks Java to show the display it is routed to and
returns unconfigured. Until Java publishes its first display list a
Separate screen resolves to display 0, which names no display, so its
window is refused and the renderer keeps the screen attached until a
list arrives. An update takes only the request's settings; another
screen or request generation is a new window, the renderer's to make.
Its poll hands it the newest surface native code received, raising
GEOMETRY and publishing which window and surface the input poll maps
touches through. The EGL context makes the window's surface on the main
context the first time it is bound after a surface arrives, at swap
interval 0, with the native visual id queried once at init, since the
query logs on failure and binding sits on the present path; the Vulkan
context gives the renderer the ANativeWindow and leaves the surface to
it.

Each request native code sends Java carries a number, and each surface
comes back with the number of the request it was made for. Destroying
the window drops the surface it held, then asks Java to hide the
display. When it was the last window, that hide gives up every surface
made for an earlier request, including one that reaches native code
after it: no window takes one, and its surfaceDestroyed frees what is
left and returns without the ticket handoff. A hide native code starts,
for closing or switching content or reinitialising the video driver,
never holds the UI thread.

* android: touches on the second display reach the core

Each touch on the second display crosses from the UI thread in the ring
with the generation of the surface it was on. nativeTouch sits with the
other second display natives and reads the UI thread's own app state,
which android_app_free() clears. A down or a move goes in only when it
leaves room for an up of every finger the input driver tracks, and an up
only when its down went in, so a full ring never drops a lift and leaves
a finger pressed for the core. MAX_TOUCH moves to platform_unix.h for
the native and the input driver to share.

The input poll drops the touches of any surface but the one the renderer
shows, maps the rest through that window's published layout into the
core's frame, and keeps the fingers after the main display's in the
core's pointer list. They never move the mouse, count as a quick tap or
reach the frontend's own screen pointer, so the menu and overlay stay on
the main display.

* input: the second display's fingers on a device of their own

With an overlay's pointer on, or one of its buttons held, the frontend
answers RETRO_DEVICE_POINTER from the overlay or not at all, and a
windowed overlay polls that device as its own touches. So the second
display's fingers cannot share it: the Android driver answers them on a
frontend-only RARCH_DEVICE_POINTER_SECOND and keeps RETRO_DEVICE_POINTER
to the main display. The frontend lists them for the core after the main
list's pointers, whoever answers for those: the overlay, none while the
pointer is blocked, or the driver. A driver with no second list answers
with its main list alone.

The pointer_second harness checks those answers against a stand-in
driver with both lists, through input_state_device() and the overlay's
pointer answer extracted from input_driver.c.

* android: a Presentation for the second display

SecondDisplayController lists the displays for native code - whether
each is the Activity's own, valid, on, private, a real panel or monitor
(product info, API 31+), its connection, name and size - and shows a
Presentation with a SurfaceView on the display native code asks for. The
window is not focusable and not touch modal and the view is not
focusable, so touching it neither pauses the core nor takes the gamepad;
it keeps the screen on and, on API 29+, keeps system gestures off it. It
follows the Activity from onStart to onStop, holding a request made
while stopped, and a frame after a display or configuration change
publishes the list again when it changed. A display it cannot show on
stays invalid until the list changes, so the display events a
Presentation coming or going may fire do not retry the failing show in a
loop. Each Presentation carries the number of the show request it was
made for and hands its surfaces to native code with it; touches go with
the surface's generation.

* android: second display failures and Presentations Android dismisses

Every display a show failed on stays invalid until the list changes or
the Activity starts again. A single failed id would not do: of two
displays that both refuse a Presentation, marking one failed makes the
other valid again, native code routes back to it, and the two take turns
failing forever. Starting again retries a one-off failure when the app
returns.

Android cancels a Presentation itself when its display's metrics change
(API 17-29). A dismiss we did not make clears it, so the dead
Presentation does not leave its display counted as shown. While native
code still wants that display, it is shown on again if the dismissed
Presentation's view had a surface. One cancelled before that cancels
itself, for example on stale display metrics, and rebuilding it would
loop at message rate; it counts as a failed show, so its display is
published invalid until the list changes or the Activity starts again.

* video: which screen windows a renderer makes and lets go each frame

video_windows_steps() tells a renderer, from one frame's snapshot,
which of its windows to let go of (no screens now, or another
display) and which to make. A window that closed or failed is not
made again until the restore count moves, so frames already queued
do not bring it straight back. Renderers share the rule instead of
each keeping its own.

* gl2: views draw into a given output; the preset's targets can grow

gl2_views_render() takes the layout, size and canvas it draws with
instead of reading the main window's, and the loop that grows the
preset's targets on a resize becomes a function of its own. Nothing
changes for the main window; a screen's own window will draw through
both.

* gl2: draw the screens' own windows

With a context that can make windows of their own for screens, gl2
reports GFX_CTX_FLAGS_VIDEO_WINDOWS while it may draw views. Each frame
it makes and lets go of the windows the frame's snapshot asks for and
draws each one's screens through the views compositor, with the preset's
passes where one is loaded. A window's pass targets grow only once its
surface is bound, so a failed bind has nothing to undo. A window is
drawn after the main window's frame and swapped at interval 0 before the
main swap, which stays the only one that can wait. The menu, OSD,
widgets and overlay stay in the main window.

A window that cannot be made or drawn hands its screens back to the main
window until the next restore and logs once. A window its context closes
hands them back too, and a restore that lands in the same frame does not
make it again; one closed with no screens left is not marked failed, so
it can be made again when its screens come back before the next restore.
The poke's windows_retire lets go of every window at once, for a
platform that takes a surface back.

The gl2_pbo_readback and gl_last_pass_fbo samples include gl2.c; neither
draws windows, so their shared stubs answer the frontend's window calls
with no-ops.

* samples: views test core logs a second finger, and asks for GLES 3

With a finger on each of two displays the second display's finger is
pointer 1. The core reads, maps and logs pointers 0 and 1 through one
helper, each with its own last state; pointer 0's lines and crosshair
are unchanged. Android's gl driver only gives GLES contexts, so a gles3
value of video_views_test_hw lets the core render through hardware there
too. gles3 with the checker or noise pattern logs an error at load:
those patterns upload a BGRA texture, which GLES 3.0 does not take.

* vulkan: the window-system surface switch as a helper

vulkan_surface_create() made the VkSurfaceKHR for each window system
in line. The switch moves into vulkan_wsi_surface_create(), which
takes the instance and returns the surface, so a second output on the
same device can make its own surface the same way. VULKAN_WSI_DISPLAY
stays where it was, since it needs the context's GPU. No behaviour
change.

* vulkan: a second output on the context's device

A vulkan_output_t is a surface of its own with a swapchain on the
context's device and queue, for a screen shown on another display. It
never waits in a frame. The acquire takes no time and uses a fence,
never a semaphore, so nothing the second display's presentation engine
signals ever enters the queue the main output's frames share. The fence
is polled, and an image not yet free is kept for the next frame. Out of
date remakes the swapchain at once; suboptimal remakes it once and then
keeps it, as the main chain does. On Android the swapchain takes the
size the window reports, since the surface's currentExtent can lag an
in-place resize and Android scales any extent to the window. A lost
surface is dropped. A swapchain let go of is destroyed once a fence
queued behind its last present has signalled. Only teardown waits, on
the output's own fences.

The harness runs the real code against a fake device and checks that no
frame-path call waits, that only completed objects are destroyed, and
that everything made is freed.

* vulkan: draw screens in windows of their own

Where the context has a windows table (Android's second display), a
screen the plan sends to a window of its own is drawn there with its
views' chains, sized for the window, and the frame's own view copies.
The window is drawn in the frame's command buffer, after the main pass
and before the chains end their frame. Its image comes from a fence
already signalled, so the frame's submission waits on nothing new; it
signals the window's semaphore, and the window presents after the
submission and before the main present, which stays the only one that
waits. The menu, OSD, widgets and overlay stay in the main window.

A window that can't be made, drawn or kept puts its screens back in
the main window until the next restore. One whose output is not done
yet is let go of on a later frame; the poke's windows_retire and the
driver's teardown wait for it, on the output's own fences.

* video: separate screen windows on by default on Android

On Android a Separate screen goes to a second display, which is what a
device with two panels is for, so the switch starts on there. Desktop
windows stay opt-in. The settings table records the Android default;
the harness checks that an override carries the switch and a screen's
mode.

* menu: a window mode and a display for each screen from 2 on

Each screen past the first gets two settings rows: Attached, Separate or
Hidden, and the display it goes to when it is Separate. The display
picker offers Auto and the displays the platform has published, shown as
the monitor index is ("2 (Screen-2 1240x1080)"), and keeps a display set
elsewhere until it is changed. It widens only up to the most displays
that can be published, so a hand-edited value in the thousands cannot
build a list that long on the main thread. The row's sublabel says that
the display RetroArch is on keeps the screen attached. Each screen's
rows have their own enum, as the per-port input rows do, so the dropdown
and the sublabel find the right one. A change to any of the rows gives a
screen the renderer sent back to the main window another try.

The settings harness checks the rows, the left key's clamp, and that a
change to any of them restores closed windows.

* menu: show the screens' window rows where the driver has windows

Stereo 3D & Screens shows Separate Screen Windows when the video
driver reports secondary windows. While it is on, it also shows the
mode and display of screen 2 and of each further screen the core has.
On Android the switch reads "Show secondary screens on another
display". A screen row's dropdown takes that row's label as its
title.

* android: show the second display again after its metrics change

On Android 10, a density or size change on the second display left it
empty until the app restarted. Android cancels a Presentation when its
display's metrics change, and the one made to replace it got the
display's resources from a cache keyed by display id, still holding the
old metrics. Presentation.onStart() found them stale and cancelled it
before it drew, so the display was marked failed and both screens stayed
on the main display.

Bring the new Presentation's resources up to the display's current
metrics before showing it. The refresh runs only before Android 12; from
then on a Presentation has a window context of its own.

* android: read input queue keys and sticks through their fields

The key and stick helpers of the input queue take the few fields
they read instead of the event, and the default loop's slot lookup
and key path become functions of their own. Nothing changes for the
queue; the second display's window can then hand its keys and stick
moves to the same code.

* android: a focus rule for the Activity and the second display's window

Once the second display's window can take focus, RetroArch's focus
comes from two windows. Where each display has its own focus, the
second window keeps its display's focus while the keys go elsewhere,
so it counts only while in use: after a key, stick move or touch from
it, a focus it gained while the Activity had none, or a hand-off of
the one focus from the Activity. Any other change of the Activity's
focus ends that, so a notification shade still pauses as before.

The rule is a header without NDK types; a host test replays the
sequences the AYN Thor's focus modes and a one-focus system produce.

* android: keys and sticks from the second display's window

Keys and joystick moves the second display's window receives cross
from the UI thread in a ring of their fields; the input poll handles
them with the input queue's own code, so mapping, device ids, axes,
hats and hotplug behave the same. The window's focus comes in as two
commands, and the focus of both windows is acted on once per poll by
the focus rule, only when it changes.

A paused app thread sleeps in its looper, so writing to either of the
second display's rings wakes it, and the poll reads both rings after
the looper so the woken poll acts on what woke it. The app thread
keeps a reference on its looper for those wakes until the activity is
freed.

* android: the second display's window takes focus and passes keys on

The second display's Presentation can take focus, so a tap on it or a
focus mode that locks the gamepad to its display (the AYN Thor's Bottom)
gives it the pad. Its keys and joystick moves go to native input as the
Activity's do. Each key gets native code's answer, so Android makes no
fallback key (BUTTON_B as BACK, BUTTON_MODE as HOME) of a key RetroArch
uses. It is not cancelable, so BACK stays with native code, and it never
becomes the soft keyboard's target. The IME refuses the Activity's text
field while the second display's window holds the focus, so the Activity
asks for the soft keyboard again when it regains focus.

When RetroArch dismisses the Presentation while it holds the focus, it
first brings its own task to the front, which takes REORDER_TASKS
(granted at install). Android keeps one focus for all displays, so it
would otherwise pass to that display's launcher and leave RetroArch
without focus until the next tap on the main display.

* android: hand the second display's surface back without waiting

surfaceDestroyed and a window change on the second display return at
once. The renderer's own ANativeWindow references keep the window
alive; a frame still drawing into it gets an error from the abandoned
queue, and the next frame's poll finds the surface gone, drops it and
lets the EGL surface or Vulkan output go, the output on later frames
while the presentation engine still holds an image. Nothing waits on
the video thread or the GPU, so the UI thread can't stall into an ANR
behind a slow frame, and the window keeps its display for the next
surface. The going surface also gives the windows another try, so a
failure it caused is not held against the next.

vulkan_output_finish() is the driver's teardown only. An acquire it
gave up on keeps its fence, which is never destroyed while it may still
be signalled.

* video: build the screens' own windows only where a platform has them

HAVE_VIDEO_WINDOWS gates the secondary windows: their state in the
video driver and the threaded wrapper's slots, the drivers' window code
and its per-frame calls, the second output and EGL window surfaces,
the input layer's second pointer list and window places, and the
per-screen settings with their menu rows and config keys. Android
defines it; elsewhere ./configure --enable-video_windows does, and the
CI job that runs video_screens_test builds that way. A build without it
compiles none of this and carries none of its state, which is about
15 KB of the video driver's state alone.

* video: a window failure counts only for the restore it was drawn under

A renderer reports a window it could not make or keep with the restore
count of the frame it was drawing. Failures travel in one word, the
screens beside that count, and the main thread takes them only when
the count is still its own. Under threaded video a frame laid out
before a display or surface change can fail after that change; its
failure no longer keeps the screen on the main display until some
later event.

* android: the jelly-bean package may reorder tasks too

It builds the same second display controller, which brings RetroArch's
task back to the front when it dismisses the second display's window on
a one-focus system; without REORDER_TASKS that call throws and the focus
stays on that display's launcher.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-24 10:41:49 +02:00
d6a23c320a video: simulated input resolution for slang shaders (#19675)
Shader settings gain Simulated Input Resolution with a width and
height, off by default. While it is on, a slang preset's first pass is
told its source has that size, so resolution-sensitive shaders such as
CRT and edge-smoothing ones treat an upscaled frame as the small image
they were written for. Later passes keep their real SourceSize.

The size is recorded in the preset when it is loaded and every slang
backend reads it from there: glcore, Vulkan, D3D10/11/12, Metal and
gx2. Changing a setting reapplies the preset. The rows appear in the
shader quick menu only when the video driver takes slang.

slang_hlsl_backend checks that the size reaches the first pass alone
and survives the copy the D3D drivers make of a pass's semantics, and
runs in the gfx samples workflow.

Co-authored-by: libretroadmin <[email protected]>
Co-authored-by: hunterk <[email protected]>
2026-10-24 05:33:38 +02:00
Codex 650cf882db Add shared Media Player settings below File Browser
Expose fullscreen seek interval and controls auto-hide preferences through shared menu navigation. Preserve the existing five-second seek and three-second hide defaults, keep zero-delay controls visible, and publish the configured seek interval for matching button captions. Exercise actual navigation, editing, persistence and kiosk visibility in all four menu drivers.
2026-10-23 12:26:06 +02:00
libretroadmin b4204ab02b configuration: move label-keyed hand rows into the row tables
Hand-written bool and uint settings whose key is an unconditional
menu label and which carry a default become rows of config_bool_rows
and config_uint_rows, keyed by their label's blob offset like the
generated rows. Rows with an override, a sensitive flag, no default
or an #else branch stay as code. So do the float rows, whose table is
only added with the menu, and the int rows, whose trailing override
binds to the last row added.
2026-10-23 11:46:36 +02:00
Codex bf777848c6 Verify packed settings table keys
Resolve label offsets from preprocessed blob declarations instead of dropping generated settings rows. Refresh the reference for ten already-landed settings and microphone row ordering, with all existing keys and defaults preserved. Cover packed and literal keys, ordering and missing labels, and run table and unity checks in settings CI.
2026-10-23 03:47:16 +02:00
libretroadmin c13b93c757 d3d9: Direct3D 9Ex Presentation, the flip model and frame latency
A new setting, Direct3D 9Ex Presentation (video_d3d9ex_present), off by
default, has the D3D9 drivers on a Direct3D9Ex device present as D3D11
does. A window presents through the flip model (D3DSWAPEFFECT_FLIPEX),
which the compositor takes as it is instead of copying; a device that
will not present that way is made with the discard swap effect, as
before. With V-Sync and Waitable Swapchains on, Max Frame Latency is
honoured the way D3D11 takes it: 1 to 4 is the device's maximum frame
latency, 0 is a latency of 1 with a wait for the display after Present,
and -1 the wait before a Present that goes out at once, which only the
flip model has (elsewhere it waits after). A device that cannot wait
for the display (Wine's) keeps the latency and does not wait.

With the setting on, the D3D9 drivers report a customizable frame
latency, so Waitable Swapchains and Max Frame Latency show for them;
the setting itself shows under the D3D9 drivers. Off, or without Ex,
nothing changes: pacing users have calibrated around stays as it was.

samples/gfx/d3d9_ex makes a device in the flip model, sets its latency
through the Ex table d3d9_tex.h lays out and presents in each of
d3d9_present's ways; swfb.py, with SWFB_D3D9EX_PRESENT, checks that a
d3d9_hlsl window took the flip model and the latency, draws what D3D11
does, and that a device without Ex presents as before.
2026-10-14 22:18:47 +02:00
5103a232df OpenXR: one core for the desktop and Quest headsets, with head tracking, controllers, settings and desktop OpenGL (#19811)
* menu: the cursor stays on Headset Output when Stereo Mode comes and goes

Stereo 3D & Screens lists Stereo Mode above Headset Output only while
the headset is off. Turning it on moved Headset Output up a row and
left the cursor on the row below it, Headset Refresh Rate, so a second
OK meant to turn the headset off opened the rate list instead.

The cursor follows the row, as it does for Mapped Port when Device Type
comes and goes. samples/menu/stereo_headset checks it for OK, right
and Start.

Co-authored-by: Craig <[email protected]>

* openxr: a session without focus is not logged as controllers not read

xrSyncActions answers XR_SESSION_NOT_FOCUSED, a success code, while the
runtime's dashboard or another application has focus. It was logged as
a warning each time focus went; only a failure is now.

The test layer answers a sync with a scripted result (sync <n>), and
the headset suite gains input-sync-unfocused.

Co-authored-by: Craig <[email protected]>

* libretro: a TRACKED view map, and HMD is how a core finds a headset

A map's shape could not tell a head-tracked eye pair from a
stereoscopic screen sent alone. The new flag says which. HMD now means
tracking works, and a core learns of a headset from it only.
REQUEST_FLAT is a map without the flag, and OpenXR always gives real
eye poses, so the flag and the IPD hint go; nothing read either.

Co-authored-by: Craig <[email protected]>

* openxr: the runtime, session, swapchains and frame loop are shared units

The desktop Vulkan host's instance and headset, its session state
machine, its swapchain slots and its frame loop with pacing move into
gfx/common/openxr_*, which name no graphics API, so every headset host
can use them. vulkan_openxr keeps only what is Vulkan's.

Co-authored-by: Craig <[email protected]>

* video: the headset's host says how it is, and answers the pose calls

One host interface reports the headset's state on the desktop and the
Quest alike. HMD comes with PRESENTS and STEREO, and only while the
session runs. The pose calls go to the host, and a view map sent again
unchanged is not applied again.

Co-authored-by: Craig <[email protected]>

* openxr: a TRACKED map's eyes are the desktop headset's projection

Tracked content gets a space of its own, LOCAL or STAGE as the core
asks, and the poses sampled in it. The eyes the core draws are
submitted as a projection layer with the poses it was given, and the
screens a headset leaves to that projection are hidden. A recenter
moves a seated world with the screens; a standing one stays where the
room puts it, and the core is told RECENTERED. A paused core's tracked
world comes back after a driver reinit. The views test core can draw a
world-locked grid, for checks made in a headset.

Co-authored-by: Craig <[email protected]>

* vulkan: the Quest's headset runs on the shared OpenXR core

The runtime starts Android's loader and tells the instance of it. A
headset frame can be begun and ended inside the video frame, so
vulkan_openxr runs in-line for a headset's own context, which says so
in its flags. The units that name no graphics API build without Vulkan,
and a GLES binding is ready for the GL host. A space a core asks for
again is kept, so a cached context keeps its paused world.

Co-authored-by: Craig <[email protected]>

* gl: the Quest's headset runs on the shared OpenXR core

The Quest's ES3 context is its own in-line host: gl2 draws into a
window the context owns, shown as a screen in the room, and copies a
tracked map's eyes into the headset's projection. The window shows
until the first projection. The kept view pair lives in openxr_host, so
the GL host keeps a paused world across a reinit as Vulkan does. The
quest flavour builds with GLES=3. A stand-in runtime test runs the
in-line host.

Co-authored-by: Craig <[email protected]>

* input: one headset controller module for the desktop and the Quest

The headset input's hooks belong to the frame, so any host attaches the
module, and the Quest's own controller module goes. Touch's menu button
is Start from its press, and opens the RetroArch menu when let go after
a second. The menu's laser can point at a window shown whole, mapped
through the area the core is drawn in. The stand-in test runs the
shared module.

Co-authored-by: Craig <[email protected]>

* video: headset settings for every headset driver, and Screen Placement

The headset's rows show for every driver with a headset path. Recenter
works in the menu and reaches the Quest's GL host, and a seated
recenter moves a paused world with the screens in the same headset
frame. Screens are placed from an anchor of their own: Fixed in Room,
Follow Head or Lazy Follow. The Quest asks for a refresh rate that fits
the content and repeats each core frame for whole headset frames.

Co-authored-by: Craig <[email protected]>

* input: the headset's controllers are a controller

They take joypad slots with built-in profiles, so hotkeys, port binds,
remaps and rumble use the normal input path on the desktop and the
Quest. They take the next free slot like any pad, and keep their port
when a gamepad is plugged in, without saving that exchange. A saved
"Meta Quest Touch Plus Controller" profile and its remaps no longer
apply.

Co-authored-by: Craig <[email protected]>

* gl: headset output for the desktop OpenGL drivers

glcore and gl show a headset on Linux. The XR thread holds a context in
the window's share group, made by the X11, X11 EGL and Wayland context
drivers, and the session binds it through XR_KHR_opengl_enable or
XR_MNDX_egl_enable. The video thread draws each image into shared
textures and hands it over with a fence through a triple buffer; the XR
thread copies the newest into the swapchain and shows it with the quads
or the eye poses it was drawn with. glcore draws the screens and the menu
through chains of its own and a tracked map's eyes as the projection, as
Vulkan does. gl shows its window, whole or cut into an untracked map's
screens, as the Quest does, and tracked eyes as the projection. Both
follow the headset's pacing: the XR thread wakes the core for its next
frame after taking the last, so none is shown a headset frame early.
OpenXR configures with OpenGL alone, without Vulkan. The test layer
captures OpenGL swapchain images, and the headset end-to-end runner has
lanes for both drivers on GLX and EGL.

Co-authored-by: Craig <[email protected]>

* docs: OpenXR headset output, and how a headset or graphics API is added

What the headset path does and how to build and turn it on, what a core
sees, which units are shared and what each of the three hosts adds, and
the steps for another headset, graphics API or platform.

Co-authored-by: Craig <[email protected]>

* audio: pass the reinit flag to MMDevice watchers

XAudio2 and DirectSound must give mmdevice_thread the frontend atomic flag. Passing the driver object overwrites its watcher handle when an endpoint changes. Exercise both thread backends against the extracted production spawn functions.

* test: reject video backend fallback in the threaded harness

Fail early when HARNESS_VIDEO_DRIVER differs from the initialized backend so a missing driver cannot qualify another backend's lanes.

* Animated previews: keep high-bitrate audio fed across loops

Size the audio lookahead from the preview bitrate, prime it before playback, and refill the full window on rewind. The audio demuxer walks interleaved video packets too; a fixed 2 MiB window stalled on large H.264 packets and left high-bitrate previews silent or delayed after looping.

Add a real feeder/mixer regression using generated oversized video packets. Verify that audio continues through three complete loops; the previous feeder fails this test. Validate both reported 4K clips locally on Windows and Linux.

* wdmks: bound threaded writes by the device fragment

A small write can exhaust its request-sized wait budget before a healthy WaveRT pin's next notification. Size the threaded ring waits from rt_ahead instead, and cover writable waits and blocking writes against a 64 ms fragment.

* CI: views end to end on glcore and Vulkan under Xvfb

run.py gains RA_E2E_XVFB (Xvfb in place of gamescope, llvmpipe and
lavapipe) and RA_E2E_ONLY (a case filter). The validation job runs a
subset of the cases.

* Views: one compositor for gl3 and Vulkan

gfx/video_views_draw.h holds the chain pick, the placement loop with
the interlaced row shift, and the canvas and UI target lifetime. Each
driver includes it with its own calls bound in by macros, so nothing
goes through a function pointer. The UI layer's predicate, per-eye
test and dims swap move to video_views.h. Output is unchanged.

* Threaded video harness: the frame on screen is the core's newest

A frame lag lane checks, end to end, that what the driver draws is the
frame the core pushed last: the harness core stamps each frame with its
number in eight full-height bands that survive scaling and filtering,
and the lane reads back the driver's own upload (the cached frame
dropped first) and decodes it. Copied, lent and lent as a cropped
window, with threaded video off and on: with nothing in flight it must
be the newest pushed frame, and with frames still in the wrapper's ring
at most one behind. A zero-copy path that draws a buffer the core has
since rewritten, or skips an upload, or a wrapper that holds frames
back, fails it.

It runs on vulkan, gl and glcore, desktop and GLES 3, in the existing
harness jobs (release, ASan+UBSan, TSan, the validation layer).

* Fix MSVC preprocessing of video views UI checks

Move widget conditionals outside VIDEO_VIEWS_UI_WANTED calls in the GL3 and Vulkan drivers. The traditional MSVC preprocessor treats directives inside macro arguments as tokens, breaking the Windows build.

* Provide audio state in the DirectSound pump harness

The device notification thread now uses the audio state reinit request. Supply a persistent harness-owned state through audio_state_get_ptr so the standalone MinGW test links.

* Preserve RetroArch when building the threaded video harness

Request the link command with make -W instead of deleting the frontend executables. The later video views end-to-end tests need the existing RetroArch binary to launch.

* ATRAC3plus: accept implicit master code-table indices

Master channel code-table mode 3 has no payload and uses zero indices. Return before consuming the optional value count bit, rather than rejecting valid frames and losing bit alignment. Add a synthetic regression covering float, s16 and reset decoding.

* Expose hardware H.264 decoding with CPU planes to cores

Provide an experimental versioned frontend decoder interface with Vulkan and D3D11 I420 readback. Reserve private video queues through Vulkan device negotiation v2, and retain the decoder backend with cached devices across video driver reinitialization. Keep software fallback for unavailable or disabled hardware decoding and cover readback in decoder and GPU tests.

* ATRAC3plus: distinguish coded and declared subband flags

Use coded subbands for stereo swap and negation flags and declared subbands for window shapes. Add a synthetic audible stereo regression to detect parser misalignment when fewer subbands carry spectra.

* D3D11: views

The driver draws a core's views through video_views_draw.h, straight
from the frame as the stock chain draws it: placed in each eye's area,
blended through a canvas for anaglyph and interlaced, with the UI drawn
once into a layer composited into each eye. With a preset or HDR output
the frame is drawn whole.

Windows-wine-views.yml runs the views end to end on it: run.py gains
RA_E2E_WINE, for retroarch.exe under Wine with vkd3d's shader compiler,
and the test core builds as a DLL.

* D3D12: stock pipes made for the swap chain's format

The pipes were made before the swap chain, whose formats were still
unset, so every stock draw had an UNKNOWN target format: Windows drivers
draw it anyway, vkd3d (Wine) draws nothing. They are made after it now.

* D3D12: views

The driver draws a core's views through video_views_draw.h as the
D3D11 driver does: straight from the frame, through a canvas for
anaglyph and interlaced, with the UI drawn once into a layer composited
into each eye. Its SDR pipes keep coverage in alpha, which the layer is
composited by. With a preset or HDR output the frame is drawn whole.

Windows-wine-views.yml runs the views end to end on it as well, on
Wine's vkd3d over lavapipe.

* openxr: apply unified headset support to current master

Apply PR #19811 at 3d4ad57 to master at 204f03cfec, resolving its
conflicts with the shared views compositor and hardware video decoder.

Retain master's compositor and GL frame-memory interfaces while
preserving the PR's headset UI sizing, shader-chain cleanup, tracking
and controller support. Keep both sets of regression tests and the
hardware decoder callbacks alongside the OpenXR pose helper.

---------

Co-authored-by: Craig <[email protected]>
Co-authored-by: Codex <[email protected]>
Co-authored-by: libretroadmin <[email protected]>
2026-10-12 18:33:10 +02:00
885dc8da31 Recording/streaming: no more game slowdown, correct A/V timing, Twitch-ready presets (#19752)
* recording: add video_record_allow_frame_drop and video_record_fifo_frames

Wires two record_ffmpeg queue knobs through the settings_def
single-source tables, record_params and the Recording settings list
(shown for the ffmpeg driver only).

  video_record_allow_frame_drop  bool, default true
  video_record_fifo_frames       uint, default 32, 8..128 step 4
                                 (Lakka: advanced only)

Both are read when a recording starts; the driver side is the next
patch. msg_hash_lbl_str.h regenerated with tools/gen_lbl_str.py, keeping
only the two new rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: drop instead of stalling when the encoder falls behind

With the encoder behind, a full queue blocked the frontend in
push_video/push_audio, so a slow encoder slowed the game down.

With video_record_allow_frame_drop (default on):
- a frame that doesn't fit is dropped; the count rides with the next
  queued frame and the encoder leaves a pts gap for it, so video stays
  aligned with audio
- an audio chunk that doesn't fit is owed as silence and written ahead
  of the next chunk that does, and anything still owed is encoded at
  finalize, so the audio track keeps its length
- drops are reported every 64 frames and summarised at teardown

The video queue depth comes from video_record_fifo_frames (8..128).
The audio queue holds 2 s at the core's rate instead of ~0.36 s at
48 kHz. With drop off, both paths block as before.

ffmpeg_pipeline_test gains a drop-mode pass that checks the surviving
frames come out in order, pixel-exact and at their own timestamps.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: make RTMP streaming presets ingest-compliant

The Twitch/YouTube/Facebook/Kick modes reused the recording presets:
CRF (unbounded VBR), the encoder's default GOP (~4 s for x264 at
60 fps), audio at whatever rate was closest to the core's and no
audio bitrate. Ingest servers want CBR H.264, a 2 s keyframe interval
and AAC at 44.1/48 kHz.

For the FLV/RTMP modes:
- CBR video at 2500/4500/6000 kbps (low/med/high), 1 s VBV buffer,
  x264 nal-hrd=cbr, no B-frames
- a fixed 2 s keyframe interval: x264 scenecut=0, and keyframes are
  forced on time rather than counted in frames, since pts gaps from
  dropped frames or cores presenting every other tick stretch a
  frame-counted GOP
- AAC 160 kbps at 48 kHz

Rate control goes through the generic AVCodecContext fields so it
carries over to other encoders. Local/custom streaming and the
recording presets are unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: optional hardware H.264 encoder with libx264 fallback

New setting video_record_hw_encoder (default on for HAVE_LAKKA_SWITCH,
off elsewhere). When set and the built-in preset would use libx264,
these encoders are tried in order and the first that opens is used:

  h264_nvv4l2, h264_nvmpi, h264_v4l2m2m, h264_rkmpp, h264_nvenc,
  h264_amf, h264_qsv, h264_videotoolbox, h264_mediacodec

All take system-memory frames; VAAPI is skipped because it needs a
hw_frames_ctx upload path. Input is YUV420P if the encoder takes it,
else NV12 (via swscale). x264 private options are not passed to them;
rate control/GOP come from the generic AVCodecContext fields, plus
rc=cbr for the RTMP presets. CRF presets get a bitrate derived from
0.05/0.1/0.2 bits per pixel per frame (low/med/high), floor 500 kbps.
If none open, libx264 is used as before. Custom configs are untouched.

ffmpeg_init_video is split so one encoder attempt is a self-contained
alloc/configure/open that frees its context on failure. Also sets
AVCodecContext.framerate, which some hardware rate controllers need.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: run encoder threads at lower priority in drop mode

With a CPU-bound software core, libx264's worker threads, the
swscale/AAC work on the encoder thread and the core's main thread all
compete at the same priority, so recording slows the game even when
the queues never fill.

In drop-on-full mode, run everything encoder-side at nice +10:
- the driver's encoder thread renices itself on start
- threads spawned during the video/audio avcodec_open2() (x264's pool
  and lookahead) are found by diffing /proc/self/task and reniced
  afterwards; lowering the main thread around the open instead isn't
  reversible without CAP_SYS_NICE

The encoder then gets what the core leaves, and any shortfall shows up
as dropped frames rather than game slowdown. Linux only; skipped when
drop-on-full is off, since starving the encoder there means stalls.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: route libav* logging into RetroArch's log

FFmpeg logged to stderr through its default callback, so codec
messages never reached RetroArch's log or log file, which is what a
user can attach after a crash.

While recording, install an av_log callback that assembles lines
FFmpeg builds from several calls and forwards them as
RARCH_ERR/WARN/LOG/DBG by level, tagged [FFmpeg/lav]. A partial line
is flushed when the next piece comes from another context, so two
threads' messages are never spliced. FFmpeg's level follows the
frontend log level. The default callback is restored when the
recording is freed.

Adds verbosity_get_log_level().

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* video: BGRX readback for GPU recording (gl, vulkan)

GPU (post-shaded) recording, which hardware-rendered cores are forced
into, read every frame with read_viewport(), which converts the
32-bit readback to BGR24 on the main thread inside the frame budget.
The encoder thread then converts BGR24 to YUV again, and the frame is
queued at the viewport rounded up to powers of two.

- Add an optional video_driver_t::read_viewport_bgrx() that returns
  the viewport as 32-bit B,G,R,X plus its row order. It is the last
  slot, so positional driver tables leave it NULL.
- gl: desktop GL already reads back GL_BGRA, so rows are copied as-is
  (RGBA is swizzled on GLES), on both the PBO and synchronous paths.
- vulkan: B8G8R8A8 swapchains copy rows as-is, RGBA ones swizzle.
- Recording uses it when the driver has it, feeding the encoder
  ARGB8888: the main thread's work per frame becomes a plain copy and
  the encoder thread converts once. Little-endian hosts only, since
  FFEMU_PIX_ARGB8888 is a native-endian word. The threaded wrapper
  keeps its own readback on the video thread.
- The encoder queue is sized to the exact viewport (a resize already
  ends the recording): 1280x720 rather than 2048x1024 per frame.

The BGR24 path is unchanged for screenshots and other drivers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* video: BGRX readback for glcore, gl1, sdl2 and sdl3; fix sdl2 readback

Extend read_viewport_bgrx() to the other Linux-capable drivers.

- glcore: the async PBO readback reads GL_BGRA on desktop GL (GLES
  keeps RGBA), with the BGR24 scaler's input format to match, so
  recording gets a plain row copy as on gl.
- gl1: synchronous RGBA readback, swizzled to BGRX.
- sdl3: sdl3_capture_viewport() can emit BGRX, top row first.
- sdl2: read_viewport() converted the whole window surface
  (SDL_GetWindowSurface() on a window that has a renderer), copied
  surface->h * pitch into a buffer sized for the viewport, top row
  first where callers expect bottom row first, and leaked the
  converted surface on every call, so GPU recording and screenshots
  came out as garbage. Do what sdl3 does: redraw the last core frame
  into the game viewport and SDL_RenderReadPixels() exactly that, as
  BGRX or as flipped BGR24.

The drivers that pass OpenXR slots positionally get NULLs for them
so the new slot lands in place.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* recording: performance counters for the recording path

Register frontend performance counters for each stage of a recording,
reported at exit with perfcnt_enable, so its cost can be placed:

  record_readback      main thread: GPU readback
  record_push_video    main thread: queueing a frame for the encoder
  record_push_audio    audio path: queueing audio
  record_scale         encoder thread: colour conversion and scaling
  record_encode_video  encoder thread: video encode and mux
  record_encode_audio  encoder thread: audio resample, encode and mux

Counters are only touched with perfcnt_enable set, and each has a
single writer.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: timestamp video from the audio clock

Video pts was one tick per frame handed to the encoder, i.e. it
assumed the core delivers a frame on every retro_run(). Many don't: a
30 fps game on a 60 Hz core (Dolphin's Wind Waker, for one) only
calls the video callback for new frames, and frame skipping and
run-ahead do the same. Those recordings played their video at the
wrong speed: a 26-minute Wind Waker session produced 13 minutes of
video under 26 minutes of audio, and RetroArch's own player, which is
clocked by audio, hung once the video track ran out.

Count every audio frame the core produced, which is the core's real
elapsed time, and place each video frame at that point on the
timeline through the same pts skip dropped frames use. Frames that
arrive every other tick get pts 0, 2, 4, ... Without audio, the
one-tick-per-frame accounting is kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* recording: optional "Record Game Only" (no menu, OSD or menu time)

GPU recording reads the backbuffer at the end of the frame, so the
volume and other OSD messages, notification widgets, statistics,
input overlays and, with content running behind it, the menu end up
in the recording, and the menu's filler silence puts the time spent
in the menu into it too. That is what's on screen, which some users
expect, so this adds a switch rather than changing the default.

New setting video_record_game_only ("Record Game Only", default off).
It travels with the frame, like gpu_recording. When on:

- gl, glcore: the async PBO readback is issued right after the core's
  image (shader chain, PQ->SDR conversion) is in the backbuffer,
  before any UI is drawn. Under scRGB output the backbuffer only gets
  the image at the final composite, so that path reads back at the end.
- vulkan: after the final pass, end the render pass, copy the
  backbuffer to the readback staging buffer and resume with
  keep_render_pass (LOAD), so the UI draws over the unchanged image.
  HDR output keeps the end-of-frame readback, which needs its tonemap.
- audio_driver_menu_sample() doesn't push the menu's filler silence,
  so menu time is left out, as its frames already are.

When off, behaviour is as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* recording: apply the content rotation to raw (non-GPU) recordings

Raw recordings encoded the core's frames as handed over, ignoring the
rotation the display applies (core-requested or the user's video
rotation), so vertical games came out sideways. GPU recordings were
unaffected since they read back the already-rotated screen.

Pass the rotation to the recorder, describe the output with width and
height swapped for quarter turns (unless --size gave one), and rotate
each frame on the encoder thread before scaling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: scale frames smaller than the output size to fill it

ffmpeg_scale_input() clamped the scaling destination to the source
size whenever a frame was smaller than the output. That was meant for
the one pixel of padding added to reach even dimensions, but applied
to any smaller frame: frames below the core's base geometry landed in
the top-left corner of a black frame (Mupen64Plus-Next with Angrylion
and GPU recording off), and video_record_scale_factor > 1 had no
effect, since every frame is smaller than the scaled output.

Scale every frame to the output size; only a frame exactly one pixel
short of it in a dimension (the even-size padding) is copied 1:1.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: fill audio gaps when the core stops producing audio

A core that produces no audio for a while, rather than producing
silence, sent nothing to the recorder, so that time was missing from
the audio track and everything after it was out of sync (e.g.
Mupen64Plus' 64DD loading). With video timestamps taken from the
audio clock it would also stall the video timeline.

Once 4 video frames in a row arrive without any audio, owe one frame
period (sample_rate / fps, with the fraction carried) of silence per
video frame, backfilling the 4 already seen, through the existing
owed-silence mechanism. The silence goes ahead of later audio in both
drop and blocking mode.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* record_ffmpeg: drop the global 'ctx', close the output on failure

The muxer context also lived in a non-static global named 'ctx', an
exported symbol in every binary that links this driver, used by
finalize to close the output. Use handle->muxer.ctx throughout.

A recording that fails after the output was opened (an encoder that
won't open, say) never reached finalize, so its file stayed open.
ffmpeg_free() closes it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tpe3orhELpViSQpZcsTTjr

* recording: rotated raw aspect, quiet drop mode, lowered encoders by inheritance, frame format across a threading switch

- A raw recording of a turned picture takes the display's aspect as it
  is, which is already the turned one; only an aspect worked out from
  the output size follows the turn, and --size keeps its own shape
  (record_raw_geometry).
- Drop mode says nothing while it records: dropped frames are counted
  and the total is logged when the recording ends.
- In drop mode the encoders are opened on a thread that has lowered its
  own priority, so the threads they start run lowered and no other
  thread is touched.
- Only the frontend-thread perf counters are registered, as the menu
  reads and resets them live.
- A GPU recording opened for 32-bit frames gets them widened from BGR24
  when the readback in charge hands over BGR24 - the threaded wrapper's,
  or a driver without the 32-bit one - into its own buffer.
- At the end of a recording the real audio left in the queue is encoded
  ahead of the silence still owed; the gap fill counts every frame the
  core presents, frame_drop_ratio or not; the output is closed whenever
  it is open; an encoder that leaves the CBR request unused is named.

ffmpeg_pipeline_test checks the geometry, that drop mode drops frames
without a warning or an on-screen message, and that its encoder
threads run lowered. threaded_video's gpu record format lane records
on Vulkan unthreaded and threaded with a 32-bit recording open, and
reads every row of what it is handed.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-08 19:39:28 +02:00
Davey Hughesandlibretroadmin b9544ea208 OpenXR headset output for Vulkan on desktop Linux, (stacked on #19728) (#19729)
* gfx/video_views: check, lay out and map touch for a core's views

The rules behind a view map live in one module with no driver or
settings state: which maps are valid, how a frame is fitted to them,
where each view goes in every stereo mode and screen layout, which part
of a placement is drawn, and how a window point maps back into the
core's packed frame or into the coordinates a per-eye UI is laid out in.
Drivers and input share them, and a harness tests them without a GPU,
under the sanitizers in CI.

* menu: Stereo 3D & Screens settings

Stereo Mode (2D, side by side half and full, top-bottom, anaglyph,
interlaced), Swap Eyes and Screen Layout, under Settings > Video. They
apply only to cores that send a view map.

* video: accept a core's view map and hand drivers a layout

The map is checked when the core sends it. Each frame the main thread
lays it out for the current settings and passes the layout to the
driver in video_frame_info_t, so drivers never read settings, threaded
or not. A driver that can present views says so with
GFX_CTX_FLAGS_VIDEO_VIEWS; otherwise, or when a frame doesn't fit the
map, the packed frame is shown as before. A driver that draws a frame
with a map whole anyway reports it, and the menu, overlays and touch
follow the packed frame from the next frame. Under threaded video, a
cached frame larger than the core's declared maximum, redrawn for a
screenshot or a Win32 window drag, is now cropped as it was shown, so
it makes the same views decision at the same viewport. The drag's
redraw runs on the video thread beside the main one, so it lays out
what was last presented and writes none of the views state. A hardware
frame is no longer cropped: nothing is copied out of the core's image,
and a cropped size dropped its views.

View chains are built only through a new set_view_count poke, called
when the accepted map or the driver's answer changes. Under threaded
video the main thread waits while the video thread runs it, so preset
parsing, which reads settings and replaces the file-watch list, never
runs beside the main thread. A single frame that doesn't fit keeps the
chains.

Touch maps back through the layout into the core's packed frame, and a
CPU filter's output scales the map without changing each view's shape.
In side by side and top-bottom the menu and widgets are laid out at
one eye's size. The menu maps its mouse and touch position through the
layout, so either eye's copy is hit where it is drawn, and RGUI
converts against the viewport its texture is fitted to rather than the
whole window the driver reports while views are presented; the
on-screen keyboard is hit-tested at the UI's size.

A paused core asks for the views status only when it runs, so while
the menu is open a change of the status (Stereo Mode, a headset) asks
the core to update which of its options show, as an option change
does.

* input: overlays follow views

Side by side and top-bottom hide the overlay, so its hit test is
skipped there, while touches still reach the core, including through
pointer passthrough. Elsewhere a non-fullscreen overlay is drawn over
the whole window while views are presented, so it is hit-tested in
window coordinates.

* vulkan: draw a core's views

Each view is copied out of the frame, shaded by its own chain with its
own OriginalAspect, and drawn in its placement. A view's chain starts
its passes and history small and follows the view's size, not the
frame's declared maximum, which for a core like Azahar is 8192x8192
per buffer. A view shown in both eyes draws its final pass again with
the first draw's source, so its feedback swaps once. Anaglyph and
interlaced draw both eyes into an offscreen canvas and blend its
halves into the window. A placement larger than its eye's area, as a
custom viewport can make it, is cut there by a scissor on the chain's
final pass.

In side by side and top-bottom the UI is drawn once into a layer at
one eye's size and blended into both halves, at screen depth, and only
while there is UI to draw. The layer is FP16, drawn with the SDR
pipelines HDR output already builds; with HDR off their alpha
accumulates as coverage, so the layer composites as the UI drawn
directly would.

Views are off with HDR output or an HDR10 source. The driver publishes
that answer for the main thread rather than reading its context flags,
which the video thread rewrites each frame. A view that can't be
copied or given a target falls back to the packed frame, and reports
it.

* glcore: draw a core's views

Each view is blitted out of the frame's texture, keeping its
orientation and swizzle, shaded by its own chain and drawn in its
placement, following the same rules as Vulkan. Anaglyph and interlaced
blend the halves of a canvas twice the window's width. GL counts rows
from the bottom, so for interlaced with an even height the eyes take
the other halves, keeping the left eye on the window's even rows. The
blend programs are compiled on first use; without them the left eye is
drawn alone.

The UI is drawn per eye as in Vulkan, RGUI keeping its aspect inside
each eye. Views need the slang chain, and are off with HDR output or
an HDR10 source; get_flags reads published state only.

* samples: a test core and end-to-end check for video views

The core draws solid-colour views in the 3DS, DS and Virtual Boy shapes,
in software, through a GL core context with either origin, or into
Vulkan images of its own handed over with set_image, plus a frame larger
than its declared maximum, which threaded video crops unless it is a
hardware frame. It logs the views status, also when asked to update its
options' display, and the touches it receives. run.py drives it in
headless gamescope on vulkan and glcore through every stereo mode and
screen layout, and checks screenshot colours and the logged status. One
case runs a history preset over a core that declares a large maximum and
checks the GPU memory it holds. One case runs on gl, which doesn't
present views. The pointer cases move the pointer of gamescope's own X
server from a helper inside the session and check that the menu's
cursor, ozone's and RGUI's, is drawn under it in each eye's copy. It
refuses to run on the desktop display.

* vulkan, input: headset output through OpenXR

PC headsets through the OpenXR runtime: a session on the Vulkan
instance and device the runtime asks for, per-eye quad layers composed
from the core's view map, flat-screen stereo modes, laser and menu
pointer input, and the Monado end-to-end test infrastructure.

The runtime is reached through the loader built into the tree, not a
libopenxr_loader.so.1 opened at run time, so the same path works
wherever the loader does, and the OpenXR headers come from the
vendored SDK. The Vulkan instance and device wrappers route either
owner of the context, this session or the Android projection context,
with no cost where neither is built. OpenXR is on by default on Linux
with Vulkan and threads, and its C is in the C89 lane like the rest
of the tree.

* vulkan: one swapchain per eye for the headset's stereo screens

SteamVR on the Steam Frame ignores a quad layer's imageArrayIndex and
shows array layer 0, so a stereo screen showed the left eye's image in
both eyes and Swap Eyes changed nothing. Each layer of a slot now has a
swapchain of its own, and each eye's quad shows layer 0 of its own.
A slot's swapchains are acquired and released together, and its quads
show once every one of them has released an image.

The test layer can show layer 0 of every quad as the Frame does, and
the e2e suite runs the stereo and Swap Eyes cases that way too.

Revert this once SteamVR honours imageArrayIndex
(ValveSoftware/SteamVR-for-Linux#975), to go back to one
array swapchain per stereo screen.

* openxr: the desktop headset path is built and checked in CI

A job builds RetroArch on Linux with OpenXR on - the loader is in the
tree, so nothing is installed for it - and runs the Stereo 3D & Screens
menu harness, the API layer the Monado end-to-end check records
through, and the video_views core, all with the vendored headers. The
test layer no longer looks for installed OpenXR headers.

* vulkan: the PC headset session is not opened beside the Android projection context

* qb: OpenXR stays out of the C89 lane like the Vulkan it needs

The vendored Khronos headers are not C89; the tree's own OpenXR C is.

* video thread: the poke table keeps its place for the present wait

* vulkan: the headset session's handoffs are lock-free

What the XR thread tracks for the video thread (the predicted time,
the anchor, pixels per radian) and the quads the video thread
publishes for the XR thread and the pointer are each a seqlock over
their words, the way video_driver.c publishes its viewport
parameters, and the frame tick the video thread paces on is an
eventcount. Nothing waits on a lock on either side; slot creation and
destruction already happen under the queue lock.

---------

Co-authored-by: libretroadmin <[email protected]>
2026-10-07 20:44:05 +02:00
LibretroAdmin 0f445b547d tools: settings table reference follows the Scanline Sync row move
video_scanline_sync is emitted where the Scanline Sync refactor placed
it in the settings definitions.
2026-10-07 05:05:13 +02:00
sonninnos 501990e6c1 Scanline Sync refactor part 2 (#19710) 2026-10-06 16:11:00 +03:00
LibretroAdmin 40c3047b4e thread elevation: MMCSS on Windows, video and main thread priority
thread_elevation_raise_current() takes the kind of thread it raises.
An audio thread goes through the backend chain, with the MMCSS Pro
Audio class tried first on Windows. A Games thread - one that may run
flat out - is scheduled ahead of ordinary threads but never real time:
the MMCSS Games class on Windows, else THREAD_PRIORITY_HIGHEST; a lower
nice value on Linux and Android; user-interactive QoS on Apple systems;
nothing on the consoles. A token records what changed, and
thread_elevation_lower_current() undoes it; each thread releases its
registration when it ends.

Threading gains Elevate Video Thread Priority, on by default, and
Elevate Main Thread Priority, off by default and applied as soon as it
changes. samples/frontend/thread_elevation holds that a Games raise
never reaches the chain and that lowering puts the thread back.
2026-10-05 11:33:31 +02:00
LibretroAdmin 1e1a5a6e00 input: Full-Range Analog Triggers is on by default
A trigger that rests at one end of its axis is counted from where it
rests: a full pull gives the whole analog range, and half a pull
presses the button. With the setting off only the second half of the
pull counted.

It was added off, so that nothing changed for anyone. It is on by
default now: it only acts on an axis bound to L2 or R2 that is seen
resting at the far end from its bind, with nothing bound to its other
direction, and for those the old reading was the wrong one. A trigger
that rests in the middle and a combined axis with a trigger on each
direction are left as they were.

A configuration that has the setting saved keeps what it has.

One case is worse off: L2 or R2 bound by hand to one direction of a
stick, with nothing on the other. Pushing the stick all the way the
other way makes it look like such a trigger. Turning the setting off
puts that back.
2026-10-05 01:37:35 +02:00
LibretroAdmin 65331ad151 Network RetroPad: changes sent again, and First Sender Only
Two things for the controller sent over UDP.

The Remote RetroPad core sends a control's state when it changes, and
UDP may lose the message: a lost release left the control held at the
receiver until it changed again. The core now sends a change again 5,
15 and 30 frames after it. Nothing more is sent while nothing changes,
and an older receiver reads the repeats as it reads any message.

New setting "Network RetroPad: First Sender Only"
(network_remote_first_sender, off by default): each user's Network
RetroPad keeps to the address of the first device that sends to it and
ignores every other until RetroArch restarts, so a second device
cannot take the controller over. It does not stop a forged address.

samples/input/net_retropad_repeat drives the real core and reads its
messages back over loopback, on Linux and under Wine; the frontend
harness's network pad lane covers First Sender Only with a second
loopback address.
2026-10-04 09:55:06 +02:00
LibretroAdmin 7bda2e938b input: Full-Range Analog Triggers
An axis bind takes one half of an axis. A trigger on an axis that
rests at the bottom of its range, bound as "+N", therefore did nothing
for the first half of its pull: its analog value covered only the
second half, and its button came on at three quarters (issue #6920).

New setting "Full-Range Analog Triggers" (input_trigger_full_range,
off by default). With it on, an axis bound to L2 or R2 that is seen
resting at the far end from its bind has its pull counted from there:
a full pull is the whole analog range, half a pull presses the button.

Which axes these are is learned as they are read, per controller, and
forgotten when another controller takes the slot. An axis that rests
in the middle is left alone, and so is one with a trigger on each
direction, which rests in the middle too.

Controllers read through the snapshot bridge only, which is every
desktop joypad driver. The frontend harness gains a lane for it.
2026-10-04 09:03:54 +02:00
LibretroAdmin 5a467da4f8 input: Input Rotation, for the D-Pad and the sticks
New setting "Input Rotation" (input_rotation, OFF by default) turns
the D-Pad and both analog sticks a core sees by quarter turns, for
content shown rotated (issue #12796).

The angle is counted as Video Rotation counts it: with the picture a
quarter turn round, left on the controller is up in the game. 'Auto'
follows the Video Rotation setting; it does not follow a rotation the
core asks for, since a core that does so expects the picture to end
up upright.

The D-Pad is turned where a port's buttons are compiled for the frame,
before SOCD cleaning, and a stick where its axis is asked for. Netplay
and replay recording get the turned input. The menu is not affected.

The frontend harness gains a lane for it.
2026-10-04 08:32:14 +02:00
LibretroAdmin 898e2a0fb2 input: SOCD cleaning for the D-Pad
Two settings, off by default, for what a core sees when opposite D-Pad
directions are held together (issue #18345):

- "SOCD Cleaning (Left + Right)": OFF, Neutral, Last Input Priority,
  First Input Priority.
- "SOCD Cleaning (Up + Down)": the same, plus Up Priority.

OFF passes both directions on, as before. Two directions that come
down in the same frame are neutral under either priority mode.

The cleaning is done where a port's RetroPad buttons are compiled for
the frame, so it is one pass a port a frame, a core reading button by
button and one reading the mask see the same, and netplay and replay
recording get the cleaned state. It works on the buttons the core is
given, after remaps. With a mode set, the port's buttons are compiled
on the frame's first query, not its second.

The frontend harness gains an SOCD lane.
2026-10-04 08:04:22 +02:00
LibretroAdmin 5d2e7b943a input: first press tells a key from a controller button
With "Assign Ports on First Button Press" on, a key bound to a port's
controls assigned that port, and the notification then named the
port's controller, which had not been touched. It also looked the
controller up by the port's number, not by the controller the port
reads.

A port's keys and its controller are both that port's, as they always
were, so a key still assigns the port by default. The notification now
says "Keyboard assigned to core port N" for a key and names the right
controller for a button.

New setting "Keyboard on First Button Press"
(input_assign_ports_keyboard):

- "Assigns its Port" (default): as above.
- "Waits for its Controller": a key assigns nothing while the port's
  controller is connected; a button of that controller does. With no
  controller on the port, the key still assigns it.

The frontend harness's first-press lane covers both, with the keyboard
stood in for.
2026-10-04 07:20:38 +02:00
LibretroAdmin 64451324e3 input: assign ports on first button press
New setting "Assign Ports on First Button Press"
(input_assign_ports_on_button_press, off by default), after PR #18331.
When content starts no controller is mapped to a core port; each is
given the lowest free core port when a button is first pressed on it,
and a notification says so.

The press is looked for in the poll, only while the setting is on and
some user has no core port. The d-pad does not count, and a button
counts only once it has been seen released, so idle axes that read as
a direction and stuck buttons assign nothing. The mapping itself is
made after the frame's core run, not from the poll, and the core is
then told the port's controller; the device type set for that core
port is kept. Not active under netplay.

A remap file that stores mapped ports still decides them.

The frontend harness gains a first-press lane, and its core declares
two controller ports so the lane can check when it is told a port's
controller.
2026-10-04 06:31:35 +02:00
libretroadmin 79016ab24f Core updater: move the replaced core into the backups instead of compressing it
A setting, Compress Automatic Core Backups (core_updater_auto_backup_compress,
on by default), chooses how the automatic backup an update takes is stored.
Off, the new core is downloaded and extracted into a staging directory of its
own beside the cores (.<core>.staging) and the backup task, in a new install
mode (task_push_core_backup_install()), moves the replaced core into the
backups as it is - the rzip reader takes an uncompressed .lcbk, and the CRC is
in the name - moves the new core into place, and prunes the history: two
renames, with the installed core present throughout the download and the
extraction. A replaced core that cannot be moved is backed up as a compressed
copy; a new core that cannot be moved into place leaves the installed one as it
was; a core reached through a symbolic link is updated as before. The staging
directory is removed when its download is done, through path_rmdir(), which
libretro-common gains with retro_vfs_rmdir_impl() beside mkdir.

samples/tasks/core_updater/staged_install_test runs the move, history,
fallback, compress, failed and threaded lanes against the shipping updater,
decompress, archive, rzip and core backup code with real zips, and runs in
Linux-samples-tasks.yml under ASan, UBSan and TSan; vfs_v5_metadata_test
gains rmdir checks.
2026-10-03 01:41:02 +02:00
M00NR00STERandLibretroAdmin 4592b490bd Android: Samsung S-Pen stylus support (#19076)
* android: Add Samsung S-Pen stylus input support

Stylus input for the Android driver, squashed from PR #19076 and
rebased onto current master (three conflicts, all additive: the
hover-guard block in android_input.c, an include in runloop.c and the
stylus rows in tools/settings_table.reference).

- toolType-based stylus/finger classification with an input-source
  fallback
- hover, contact and click handled separately; hover can move the
  pointer without pressing
- phantom-touch suppression for Samsung pens (hover guard, proximity
  window)
- both barrel buttons read
- four Android-only settings: input_stylus_enable,
  input_stylus_require_contact_for_click,
  input_stylus_hover_moves_pointer, input_stylus_pressure_sensitivity
- android_mouse_calculate_deltas computed its fallback delta from the
  zeroed delta instead of the event position; fixed

Closes #15490
Closes #18194

* overlay: hide for a stylus without unloading the pack

The S-Pen support unloaded the on-screen overlay from the run loop
whenever a stylus became active and loaded it again two seconds after
the last pen event. A pen that leaves and returns does that over and
over, and each round is the pack's file and texture work, triggered by
input while a core runs.

It was a full unload because a soft-hidden overlay still took over
pointer input: with input_overlay_pointer_enable the overlay poll
tracks every touch as its own pointing device and sets
INP_FLAG_BLOCK_POINTER_INPUT, so input_state never reaches the driver
and the pen's native state (hover included) is lost.

Add INPUT_OVERLAY_STYLUS_HIDDEN and make it complete. While it is set
the overlay:
- is not drawn (alpha 0, as the gamepad soft-hide does);
- reads no touches, so the poll releases whatever a finger was holding
  the way lifting it would, and no hitbox or pointing device sees the
  pen;
- does not answer mouse, lightgun or pointer queries and does not
  block pointer input.

The run loop sets and clears the flag each frame on the current
overlay, the way the gamepad soft-hide is driven. Nothing is unloaded
or reloaded.

* android: report a pen as one pointer, its barrel button as the right mouse button

The S-Pen support gave pointer indices a meaning of its own: index 0
was the cursor, index 1 the tip pressing and index 2 the barrel
button, with the pointer count raised to 2 or 3 to match. Only a core
written for that contract can read it. Everywhere else an index is a
separate touch, so a pressing pen looked like two fingers and a press
with the barrel button like three - to cores that count pointers, to
the on-screen overlay, and to this driver's own lightgun mapping,
where two pointers are turbo and three are reload. With the barrel
button held but the tip not pressing, index 1 was stale and still
inside the count.

Map the pen onto the devices the driver already reports instead:

- position is pointer 0, and moves without a press while hovering
  (input_stylus_hover_moves_pointer);
- a press is pointer 0 pressed with a count of 1: the tip pressing
  past the pressure threshold, or, with
  input_stylus_require_contact_for_click off, the barrel button,
  touching or hovering;
- the barrel button is the right mouse button.

This also puts two settings to work. The contact-for-click option and
the pressure threshold only fed a latch nothing read; they now decide
the press, as their menu text says.

The pen no longer switches the driver into mouse mode
(mouse_activated). That flag is never cleared, and once set the
lightgun position is read from the mouse coordinates, which a pen does
not update, so lightgun aim went stale after the first pen contact.
The mouse_x_viewport/mouse_y_viewport values it wrote alongside are
not read anywhere.

A cancelled pen gesture now releases the press like a lift does;
before it fell through to the touchscreen path and left the pen's
contact latched.

Compiled with the NDK through the Android unity build. Not run on a
device: it needs a pass on S-Pen hardware before merging.

* android: let a mouse's hover events reach the mouse path again

The S-Pen support drops every hover event before the source is looked
at, so hover cannot be taken for a touch. A mouse moved with no button
held reports its motion as hover events too, and they were dropped
with the rest: the pointer only moved while a button was down.

Drop hover events after the mouse branch instead. Mouse motion is
handled as before the stylus work, and hover from any other source is
still kept out of the touchscreen path.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-10-02 05:48:02 +02:00
libretroadmin 74935dc166 menu: Menu Frame Rate replaces Throttle Menu Framerate
Menu Frame Rate sets the rate the menu runs at while content is loaded,
in every sync mode: 'Display Rate', the default, runs it at the refresh
rate of the monitor the window is on, and 'Content Rate' at the
content's frame rate. At 'Display Rate' the silence the menu feeds is
written without blocking and measured out by the clock, so audio no
longer holds the menu to the content's rate; a core left running behind
the menu keeps its own clock, running when its next frame is due while
the menu re-presents its last frame in between; and behind the threaded
video wrapper without the display pacing hold, the timer holds the menu
to the display's period. A configuration with Sync to Exact Content
Framerate and Throttle Menu Framerate on keeps the content rate it had,
and the old key is dropped on save.

Display servers gain get_window_refresh_rate, the refresh rate of the
output the window is on, for Win32, X11 and SDL, and Wayland reports it
from surface enter and leave; the frontend keeps the reading until the
window moves, its output changes mode or the menu opens. The pace
decision, the menu pacing table, the threaded video harness and the X11
live sample cover both rates.
2026-10-02 05:29:34 +02:00
LibretroAdmin 6e4d60d553 settings: remove the menu_show_help setting
'Show Help' (menu_show_help) gated a main menu Help entry that no
longer exists; nothing read the setting, and the previous commits
took it out of the Views list and removed the Help list wiring.
Drop the def row, its default, the settings_t field and the label
string, and regenerate tools/settings_table.reference (the key is
the only change). An old retroarch.cfg line for it is ignored.

The generated intl tables still name the toggle's VALUE and SUBLABEL
ids, so those two stay in msg_hash.h as retired rows until the next
Crowdin fetch regenerates the tables without them; a follow-up
deletes the two rows then. help_list stays: its "Help" string is the
title of the per-setting help popup.
2026-10-02 05:19:13 +02:00
LibretroAdmin a2fbc90900 Android: enable threaded video by default
DEFAULT_VIDEO_THREADED is now true for ANDROID as well as the Switch,
so a fresh Android install runs the core on one thread and presents on
another without the user having to turn it on.

Only the default changes. A config that already has video_threaded
saved keeps its value, and the option is still there under
Settings -> Video -> Output to turn it off.

tools/settings_table.reference regenerated; only the android profile's
video_threaded row changes.
2026-09-30 22:53:01 +02:00
LibretroAdmin ba0f7d1c04 Android: enable 'Use System Keyboard' by default
DEFAULT_INPUT_ANDROID_SYSTEM_KEYBOARD goes from false to true, so menu
text entry on Android opens the system (IME) keyboard out of the box
instead of the built-in on-screen keyboard. That gives clipboard paste
and password managers without the user having to find the option first.

Only the default changes. A config that already has
input_android_system_keyboard saved keeps its value, and devices whose
input method cannot be driven from a gamepad can still turn the option
off under Settings -> Input to get the navigable on-screen keyboard
back.

The key is registered on every platform but only read under ANDROID, so
the reference row changes in all three profiles. The SDL3 option of the
same name (input_sdl3_system_keyboard) is left as it was.

tools/settings_table.reference regenerated.
2026-09-30 22:52:58 +02:00
LibretroAdmin 8fbcec8fe4 settings_table.reference: add the smb_client_readahead row
The smb_client_readahead setting went in without the reference being
regenerated, so the "Settings table drift and griffin-order unity
compile" job fails on master:

  +uint	smb_client_readahead	1	0

in all three profiles. Regenerated with

  python3 tools/settings_check.py table --write tools/settings_table.reference

No code change.
2026-09-30 22:52:54 +02:00
LibretroAdmin 15eddd52b9 Video: add Video Filter Threads setting
CPU video filters always ran on one worker per detected core
(RARCH_SOFTFILTER_THREADS_AUTO). That suits the heavy filters (NTSC,
NTSC CRT, 2xBR) but over-parallelises the light ones: at 256x224 EPX
is ~26% slower on eight workers than on one, and LQ2x/2xSaI lose
their four-worker gain at eight.

video_filter_threads (Settings > Video > Output, under Video Filter;
advanced) picks the count: 0 is Automatic, the previous behaviour and
the default; 1-16 forces that many workers. Changing it while a filter
is running rebuilds the filter on the new count at once; with no filter
loaded the value applies at the next load.

video_driver_init_filter() now frees a live filter before building the
new one. Re-init while content was running (the new setting, and also
a video filter path change) previously overwrote state_filter and
state_buffer, leaking the old filter, its worker pool and its output
buffer.

tools/settings_table.reference is regenerated; it had drifted on
master (HDR, custom viewport and window size rows) independently of
this change.
2026-09-26 18:52:29 +02:00
RyunamandLibretroAdmin 1243747c4b Config: Convert Ozone 'Color Theme' setting to array (#19451)
* Config: Convert Ozone 'Color Theme' setting to array

* Ozone: keep the color theme list in one place

The array conversion left the list of themes spelled out three times
- the identifier-to-theme table in ozone_set_color_theme(), the
identifier-to-label table in menu_setting.c, and the hand-written
'|'-separated values string in settings_build_menu() - all of which
had to be kept in step by hand for every theme added.

Move the list into menu/drivers/ozone_color_themes.h as an X-macro
and expand it in both places; the values string is now built from
the same list.  The frozen legacy numeric table in configuration.c
stays separate on purpose.

While here:

- Left/right on a value that is not in the list (an out-of-range
  number from an old config, a hand edit) did nothing, leaving only
  the dropdown to get out.  Step from the default theme instead,
  which is what such a value renders as; the label shown for it is
  now derived from DEFAULT_OZONE_COLOR_THEME rather than hard-coded.

- Find the color theme row to turn into ST_STRING_OPTIONS by its
  enum rather than by assuming it is the first row of
  settings_def_ozone_sidebar.h.

* Ozone: drop stray struct member, guard a NULL color theme

- Remove the unused 'unsigned draw_entry_delay' that the array
  conversion's conflict resolution left in struct ozone_handle.

- ozone_set_color_theme() now strlcpy()s the identifier into
  last_color_theme, so a NULL would crash.  The frame path only runs
  while the menu is alive, when video_info carries the pointer, but
  fall back to the default identifier rather than depend on that.

---------

Co-authored-by: LibretroAdmin <[email protected]>
2026-09-23 00:12:51 +02:00
LibretroAdmin f26a0f09e8 menu: limit file extension display to content browsing
filebrowser_parse() backs every file picker, not just Load Content,
so Duplicates Only / Never also stripped extensions in the shader,
overlay, config, font and playlist pickers.  There the extension is
often the only thing telling files apart (crt.slangp vs crt.glslp),
so apply the setting only to content lists (DISPLAYLIST_DEFAULT and
DISPLAYLIST_CORES_DETECTED, minus the overlay, image, video font and
playlist-collection pickers that also come through there).  Say so
in the sublabel.

Also move the Always / Duplicates Only / Never strings in
msg_hash_us.h from after the multimedia group to just ahead of it,
beside the file browser group, and regenerate
tools/settings_table.reference for the new setting.
2026-09-22 23:53:33 +02:00
LibretroAdmin 050200bb62 settings: regenerate settings_table.reference
tools/settings_check.py table --check fails on master: two settings
were added without the reference being regenerated alongside, so the
"Settings table drift" CI job reports drift for every change that
follows, whatever it touches.

  bool  audio_fastforward_callback
  uint  menu_thumbnail_preview_threads

Regenerated with

  python3 tools/settings_check.py table --write tools/settings_table.reference

Those two rows, in each of the three configurations, are the whole
difference. table --check, unity (desktop and mobile) and
settings_migrate_selftest.py all pass.
2026-09-19 17:00:12 +02:00
LibretroAdmin 6c49147fe0 input: Background Controller Input setting
Controllers kept driving RetroArch after another window took focus
(issue #5004): "Pause Content When Not Active" stops the core, but the
menu still navigated and the controller hotkeys (menu toggle, quit,
close content, reset, ...) still fired, and with that pause off the
core took the input as well. The keyboard is already ignored while
unfocused; controllers had no equivalent.

New setting input_joypad_background, "Background Controller Input"
(Settings > Input), on by default so nothing changes for anyone who
drives several instances from one controller. With it off and the
window unfocused, every controller read sees an idle pad: the core,
the hotkey/menu input gathered by input_driver_collect_system_input(),
menu_event() and input_key_pressed().

The gate is one flag, INP_FLAG_JOYPAD_UNFOCUSED, published once per
poll, and one accessor, input_driver_joypad_for_read(), which hands
the read paths an idle stand-in joypad driver while the flag is set.
The real drivers are still polled every frame, so nothing held before
focus was lost is replayed afterwards. Rumble, sensors, device names,
binding and driver lifetime keep using the real drivers. Joypad and
input drivers that have their own notion of background input can
follow the same flag and setting.

tools/settings_table.reference is regenerated; it also picks up three
rows master already emitted but had not recorded
(audio_resampler_hq_oversampling, input_sdl3_system_keyboard,
audio_latency_floor).
2026-09-17 10:48:40 +02:00
davey 0ff51eaca7 shaders: hotkeys cycle the shader root with remember off
With Remember Last Used Shader Directory off and presets in the root
of the Video Shaders directory, Next/Previous Shader cycle those
presets, starting from the loaded preset when it is one of them. With
the setting on, or when the root holds only RetroArch's own
retroarch.* preset, they step from the loaded preset into neighbouring
folders, as before.

A retroarch.* or auto preset stands in for the preset it references,
so a core shader override in the config directory steps from the
preset that defines the shader rather than from itself. Cycling the
root is the exception: a retroarch.* preset there is one of the
presets cycled, so the reference is followed a single hop. A preset
loaded as itself anchors where it sits, whatever it references, as
Save Preset As writes a simple preset too and following those would
cycle a preset's source folder instead of the folder it was saved
into.

Once a hotkey has applied a preset from the list, the press after it
steps from that list rather than anchoring on the preset again: an
entry that is itself a simple preset would otherwise resolve back to
the entry it references, and the cycling would never leave the pair.
The list is dropped when the setting changes, so turning it on follows
a retroarch.* preset out of the root that was cycled while it was off.

The setting defaults to on. It also opens the shader file browser at
the last used folder.

samples/gfx/shader_dir_cycle covers which folder the hotkeys cycle for
a core override that references a preset in the Video Shaders root,
and for the setting changing mid-session.
2026-09-15 20:54:16 -07:00
LibretroAdmin b18b044c64 menu: add Hide Statistics In Menu, on by default
A new notification-visibility bool, statistics_hide_in_menu, sits under
Display Statistics and is shown only while that is on. With it off the
menu frame draws the statistics overlay on top of the menu through the
OSD font, in every driver, so the MENU UI counters can be read where
they are produced. Drivers keep drawing the overlay themselves only
when no menu is up, so the default is unchanged.
2026-09-14 13:33:21 +02:00
Codex 9914f465dd audio: expose optional pitch-preserving speed playback
Register default-off transport and low-pass settings with audio reinitialization. Prepare the automatic native transport before playback, preserving the ordinary pipeline on unsupported configurations or allocation failure. Keep cold startup out of line and preserve existing boolean field offsets.

Cover configured startup and failure in both native lanes and enter the real-wrapper automatic tests through the same settings helper.
2026-09-14 07:15:31 +02:00
LibretroAdmin f31daa899a settings: regenerate settings_table.reference and the label strings
tools/settings_check.py table --check has been failing on master
since audio_block_frames was removed and audio_headphone_virtual_surround,
audio_output_layout and save_compression_codec were added without
the reference being regenerated; tools/gen_lbl_str.py likewise had
three MENU_ENUM_LABEL_*_STR macros to emit for those rows. Both
regenerated, nothing else touched: the reference reflects the
settings definitions as they are, and the three label strings now
exist for the comparison chains that want them.
2026-09-11 03:11:24 +02:00
libretroadmin 851d640841 video: threaded video is off by default again
The default went on with the patch that took the last two per-API
switches away. The instruction was to make threaded video the default
for hardware-rendered cores - which those switches had gated, and
which now follow Threaded Video on every API with a ring - not to turn
Threaded Video itself on for everyone. This puts the default back where
it was: off, with the Switch keeping its own. Nothing else changes; a
config that sets video_threaded explicitly is unaffected either way.
2026-09-09 21:17:43 +02:00